Merge branch 'master' into feature/deduplicate-puppet-code-6842

Conflicts:
	.vagrant-puppet/manifests/default.pp
This commit is contained in:
Eric Lippmann 2014-08-22 11:37:40 +02:00
commit 96dcc79f11
87 changed files with 2451 additions and 1078 deletions

View File

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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -40,7 +40,7 @@ class casperjs(
require => Class['wget'] require => Class['wget']
} }
$tld = inline_template('<%= File.basename(output, ".tar.bz2") %>') $tld = inline_template('<%= File.basename(@output, ".tar.bz2") %>')
$src = "${cwd}/casperjs" $src = "${cwd}/casperjs"
exec { 'extract-casperjs': exec { 'extract-casperjs':

View File

@ -51,7 +51,7 @@ define cmmi(
require => Class['wget'] require => Class['wget']
} }
$tld = inline_template('<%= File.basename(output, ".tar.gz") %>') $tld = inline_template('<%= File.basename(@output, ".tar.gz") %>')
$src = "${cwd}/${name}/${tld}" $src = "${cwd}/${name}/${tld}"
exec { "extract-${name}": exec { "extract-${name}":

View File

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

View File

@ -35,12 +35,17 @@ class AuthenticationController extends ActionController
public function loginAction() public function loginAction()
{ {
$auth = $this->Auth(); $auth = $this->Auth();
$this->view->form = new LoginForm(); $this->view->form = $form = new LoginForm();
$this->view->form->setRequest($this->_request); $form->setRequest($this->_request);
$this->view->title = $this->translate('Icingaweb Login'); $this->view->title = $this->translate('Icingaweb Login');
try { try {
$redirectUrl = Url::fromPath($this->params->get('redirect', 'dashboard')); $redirectUrl = $this->view->form->getValue('redirect');
if ($redirectUrl) {
$redirectUrl = Url::fromPath($redirectUrl);
} else {
$redirectUrl = Url::fromPath('dashboard');
}
if ($auth->isAuthenticated()) { if ($auth->isAuthenticated()) {
$this->rerenderLayout()->redirectNow($redirectUrl); $this->rerenderLayout()->redirectNow($redirectUrl);
@ -49,13 +54,9 @@ class AuthenticationController extends ActionController
try { try {
$config = Config::app('authentication'); $config = Config::app('authentication');
} catch (NotReadableError $e) { } catch (NotReadableError $e) {
Logger::error(
new Exception('Cannot load authentication configuration. An exception was thrown:', 0, $e)
);
throw new ConfigurationError( throw new ConfigurationError(
t( $this->translate(
'No authentication methods available. Authentication configuration could not be loaded.' 'Could not read your authentiction.ini, no authentication methods are available.'
. ' Please check the system log or Icinga Web 2 log for more information'
) )
); );
} }
@ -72,12 +73,20 @@ class AuthenticationController extends ActionController
} }
} }
} }
} elseif ($this->view->form->isSubmittedAndValid()) { } elseif ($form->isSubmittedAndValid()) {
$user = new User($this->view->form->getValue('username')); $user = new User($form->getValue('username'));
$password = $this->view->form->getValue('password'); $password = $form->getValue('password');
$backendsTried = 0; $backendsTried = 0;
$backendsWithError = 0; $backendsWithError = 0;
$redirectUrl = $form->getValue('redirect');
if ($redirectUrl) {
$redirectUrl = Url::fromPath($redirectUrl);
} else {
$redirectUrl = Url::fromPath('dashboard');
}
foreach ($chain as $backend) { foreach ($chain as $backend) {
if ($backend instanceof AutoLoginBackend) { if ($backend instanceof AutoLoginBackend) {
continue; continue;
@ -97,29 +106,29 @@ class AuthenticationController extends ActionController
} }
if ($backendsTried === 0) { if ($backendsTried === 0) {
throw new ConfigurationError( throw new ConfigurationError(
t( $this->translate(
'No authentication methods available. It seems that no authentication method has been set' 'No authentication methods available. Did you create'
. ' up. Please check the system log or Icinga Web 2 log for more information' . ' authentication.ini when installing Icinga Web 2?'
) )
); );
} }
if ($backendsTried === $backendsWithError) { if ($backendsTried === $backendsWithError) {
throw new ConfigurationError( throw new ConfigurationError(
$this->translate( $this->translate(
'No authentication methods available. It seems that all set up authentication methods have' 'All configured authentication methods failed.'
. ' errors. Please check the system log or Icinga Web 2 log for more information' . ' Please check the system log or Icinga Web 2 log for more information.'
) )
); );
} }
if ($backendsWithError) { if ($backendsWithError) {
$this->view->form->addNote( $form->addNote(
$this->translate( $this->translate(
'Note that not all authentication backends are available for authentication because they' 'Please note that not all authentication methods where available.'
. ' have errors. Please check the system log or Icinga Web 2 log for more information' . ' Check the system log or Icinga Web 2 log for more information.'
) )
); );
} }
$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) { } catch (Exception $e) {
$this->view->errorInfo = $e->getMessage(); $this->view->errorInfo = $e->getMessage();

View File

@ -48,9 +48,6 @@ class ConfigController extends BaseConfigController
))->add('logging', array( ))->add('logging', array(
'title' => 'Logging', 'title' => 'Logging',
'url' => 'config/logging' 'url' => 'config/logging'
))->add('modules', array(
'title' => 'Modules',
'url' => 'config/modules'
)); ));
} }
@ -70,7 +67,7 @@ class ConfigController extends BaseConfigController
$form->setConfiguration(IcingaConfig::app()); $form->setConfiguration(IcingaConfig::app());
$form->setRequest($this->_request); $form->setRequest($this->_request);
if ($form->isSubmittedAndValid()) { if ($form->isSubmittedAndValid()) {
if (!$this->writeConfigFile($form->getConfig(), 'config')) { if (!$this->writeConfigFile($form->getConfig(), 'config')) {
return; return;
} }
Notification::success('New configuration has successfully been stored'); Notification::success('New configuration has successfully been stored');
@ -107,6 +104,11 @@ class ConfigController extends BaseConfigController
*/ */
public function modulesAction() public function modulesAction()
{ {
$this->view->tabs = Widget::create('tabs')->add('modules', array(
'title' => 'Modules',
'url' => 'config/modules'
));
$this->view->tabs->activate('modules'); $this->view->tabs->activate('modules');
$this->view->modules = Icinga::app()->getModuleManager()->select() $this->view->modules = Icinga::app()->getModuleManager()->select()
->from('modules') ->from('modules')

View File

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

View File

@ -30,8 +30,8 @@ class StaticController extends ActionController
public function gravatarAction() 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'); $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; echo $img;
} }

View File

@ -5,6 +5,7 @@
namespace Icinga\Form\Authentication; namespace Icinga\Form\Authentication;
use Icinga\Web\Form; use Icinga\Web\Form;
use Icinga\Web\Url;
/** /**
* Class LoginForm * Class LoginForm
@ -16,12 +17,19 @@ class LoginForm extends Form
*/ */
protected function create() protected function create()
{ {
$url = Url::fromRequest()->without('renderLayout');
$this->setName('form_login'); $this->setName('form_login');
$this->addElement('text', 'username', array( $this->addElement('text', 'username', array(
'label' => t('Username'), 'label' => t('Username'),
'placeholder' => t('Please enter your username...'), 'placeholder' => t('Please enter your username...'),
'required' => true, 'required' => true,
)); ));
$redir = $this->addElement('hidden', 'redirect');
$redirectUrl = $url->shift('redirect');
if ($redirectUrl) {
$this->setDefault('redirect', $redirectUrl);
}
$this->addElement('password', 'password', array( $this->addElement('password', 'password', array(
'label' => t('Password'), 'label' => t('Password'),
@ -34,6 +42,7 @@ class LoginForm extends Form
} else { } else {
$this->getElement('username')->setAttrib('class', 'autofocus'); $this->getElement('username')->setAttrib('class', 'autofocus');
} }
$this->setAction((string) $url);
$this->setSubmitLabel('Login'); $this->setSubmitLabel('Login');
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,2 @@
[security]
protected_customvars = "*pw*,*pass*,community"

View File

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

View File

@ -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"

View File

@ -40,11 +40,12 @@
%endif %endif
# SLE 11 = 1110 # SLE 11 = 1110
%if 0%{?suse_version} == 1110 %if 0%{?suse_version} == 1110
%define phpname php53
%define apache2modphpname apache2-mod_php53 %define apache2modphpname apache2-mod_php53
%define usermodparam -A %define usermodparam -A
%endif %endif
%if "%{_vendor}" == "redhat" || 0%{?suse_version} == 1110 %if "%{_vendor}" == "redhat"
%define phpname php %define phpname php
%define phpzendname php-ZendFramework %define phpzendname php-ZendFramework
%endif %endif

View File

@ -142,6 +142,7 @@ class Benchmark
// TODO: Move formatting to CSS file // TODO: Move formatting to CSS file
$html = '<table class="benchmark">' . "\n" . '<tr>'; $html = '<table class="benchmark">' . "\n" . '<tr>';
foreach ($data->columns as & $col) { foreach ($data->columns as & $col) {
if ($col->title === 'Time') continue;
$html .= sprintf( $html .= sprintf(
'<td align="%s">%s</td>', '<td align="%s">%s</td>',
$col->align, $col->align,
@ -153,6 +154,7 @@ class Benchmark
foreach ($data->rows as & $row) { foreach ($data->rows as & $row) {
$html .= '<tr>'; $html .= '<tr>';
foreach ($data->columns as $key => & $col) { foreach ($data->columns as $key => & $col) {
if ($col->title === 'Time') continue;
$html .= sprintf( $html .= sprintf(
'<td align="%s">%s</td>', '<td align="%s">%s</td>',
$col->align, $col->align,

View File

@ -40,9 +40,9 @@ class Loader
{ {
if (!is_dir($directory)) { if (!is_dir($directory)) {
throw new ProgrammingError(sprintf( throw new ProgrammingError(sprintf(
'Namespace directory "%s" for "%s" does not exist', 'Directory "%s" for namespace "%s" does not exist',
$namespace, $directory,
$directory $namespace
)); ));
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -254,10 +254,27 @@ class ActionController extends Zend_Controller_Action
* *
* @throws \Exception * @throws \Exception
*/ */
protected function redirectToLogin($afterLogin = '/dashboard') protected function redirectToLogin($afterLogin = null)
{ {
$redir = null;
if ($afterLogin !== null) {
if (! $afterLogin instanceof Url) {
$afterLogin = Url::fromPath($afterLogin);
}
if ($this->isXhr()) {
$redir = '__SELF__';
} else {
// TODO: Ignore /?
$redir = $afterLogin->getRelativeUrl();
}
}
$url = Url::fromPath('authentication/login'); $url = Url::fromPath('authentication/login');
$url->setParam('redirect', $afterLogin);
if ($redir) {
$url->setParam('redirect', $redir);
}
$this->rerenderLayout()->redirectNow($url); $this->rerenderLayout()->redirectNow($url);
} }
@ -273,6 +290,27 @@ class ActionController extends Zend_Controller_Action
return $this->getRequest()->isXmlHttpRequest(); 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 * Redirect to a specific url, updating the browsers URL field
* *
@ -280,26 +318,13 @@ class ActionController extends Zend_Controller_Action
**/ **/
public function redirectNow($url) public function redirectNow($url)
{ {
if (! $url instanceof Url) {
$url = Url::fromPath($url);
}
$url = preg_replace('~&amp;~', '&', $url);
if ($this->isXhr()) { if ($this->isXhr()) {
if ($this->rerenderLayout) { $this->redirectXhr($url);
$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;
} else { } else {
$this->_helper->Redirector->gotoUrlAndExit(Url::fromPath($url)->getRelativeUrl()); if (! $url instanceof Url) {
$url = Url::fromPath($url);
}
$this->_helper->Redirector->gotoUrlAndExit($url->getRelativeUrl());
} }
} }

View File

@ -127,10 +127,6 @@ class Url
$baseUrl = $request->getBaseUrl(); $baseUrl = $request->getBaseUrl();
$urlObject->setBaseUrl($baseUrl); $urlObject->setBaseUrl($baseUrl);
// Fetch fragment manually and remove it from the url, to 'help' the parse_url() function
// parsing the url properly. Otherwise calling the function with a fragment, but without a
// query will cause unpredictable behaviour.
$fragment = self::stripUrlFragment($url);
$urlParts = parse_url($url); $urlParts = parse_url($url);
if (isset($urlParts['path'])) { if (isset($urlParts['path'])) {
if ($baseUrl !== '' && strpos($urlParts['path'], $baseUrl) === 0) { if ($baseUrl !== '' && strpos($urlParts['path'], $baseUrl) === 0) {
@ -144,29 +140,14 @@ class Url
$params = UrlParams::fromQueryString($urlParts['query'])->mergeValues($params); $params = UrlParams::fromQueryString($urlParts['query'])->mergeValues($params);
} }
if ($fragment) { if (isset($urlParts['fragment'])) {
$urlObject->setAnchor($fragment); $urlObject->setAnchor($urlParts['fragment']);
} }
$urlObject->setParams($params); $urlObject->setParams($params);
return $urlObject; 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 * Overwrite the baseUrl
* *
@ -226,12 +207,12 @@ class Url
* *
* @return string * @return string
*/ */
public function getRelativeUrl() public function getRelativeUrl($separator = '&')
{ {
if ($this->params->isEmpty()) { if ($this->params->isEmpty()) {
return $this->path . $this->anchor; return $this->path . $this->anchor;
} else { } else {
return $this->path . '?' . $this->params->setSeparator('&amp;') . $this->anchor; return $this->path . '?' . $this->params->toString($separator) . $this->anchor;
} }
} }
@ -251,9 +232,9 @@ class Url
* *
* @return string * @return string
*/ */
public function getAbsoluteUrl() public function getAbsoluteUrl($separator = '&')
{ {
return $this->baseUrl . ($this->baseUrl !== '/' ? '/' : '') . $this->getRelativeUrl(); return $this->baseUrl . ($this->baseUrl !== '/' ? '/' : '') . $this->getRelativeUrl($separator);
} }
/** /**
@ -435,6 +416,6 @@ class Url
*/ */
public function __toString() public function __toString()
{ {
return $this->getAbsoluteUrl(); return $this->getAbsoluteUrl('&amp;');
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -2,44 +2,131 @@
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
use \Zend_Controller_Action_Exception;
use Icinga\Application\Icinga; use Icinga\Application\Icinga;
use Icinga\Module\Doc\Controller as DocController; use Icinga\Module\Doc\DocController;
use Icinga\Module\Doc\Exception\DocException;
class Doc_ModuleController extends DocController class Doc_ModuleController extends DocController
{ {
/** /**
* Display module documentations index * List modules which are enabled and having the 'doc' directory
*/ */
public function indexAction() public function indexAction()
{ {
$this->view->enabledModules = Icinga::app()->getModuleManager()->listEnabledModules(); $moduleManager = Icinga::app()->getModuleManager();
} $modules = array();
foreach (Icinga::app()->getModuleManager()->listEnabledModules() as $enabledModule) {
/** $docDir = $moduleManager->getModuleDir($enabledModule, '/doc');
* Display a module's documentation if (is_dir($docDir)) {
*/ $modules[] = $enabledModule;
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);
} }
$this->_helper->redirector->gotoSimpleAndExit('view', null, null, array('name' => $moduleName)); $this->view->modules = $modules;
}
/**
* Assert that the given module is enabled
*
* @param $moduleName
*
* @throws Zend_Controller_Action_Exception If the required parameter 'moduleName' is empty or either if the
* given module is neither installed nor enabled
*/
protected function assertModuleEnabled($moduleName)
{
if (empty($moduleName)) {
throw new Zend_Controller_Action_Exception(
$this->translate('Missing parameter \'moduleName\''),
404
);
}
$moduleManager = Icinga::app()->getModuleManager();
if (! $moduleManager->hasInstalled($moduleName)) {
throw new Zend_Controller_Action_Exception(
sprintf($this->translate('Module \'%s\' is not installed'), $moduleName),
404
);
}
if (! $moduleManager->hasEnabled($moduleName)) {
throw new Zend_Controller_Action_Exception(
sprintf($this->translate('Module \'%s\' is not enabled'), $moduleName),
404
);
}
}
/**
* View the toc of a module's documentation
*
* @see assertModuleEnabled()
*/
public function tocAction()
{
$moduleName = $this->getParam('moduleName');
$this->assertModuleEnabled($moduleName);
$moduleManager = Icinga::app()->getModuleManager();
try {
$this->renderToc(
$moduleManager->getModuleDir($moduleName, '/doc'),
$moduleName,
'doc/module/chapter',
array('moduleName' => $moduleName)
);
} catch (DocException $e) {
throw new Zend_Controller_Action_Exception($e->getMessage(), 404);
}
$this->view->moduleName = $moduleName;
}
/**
* View a chapter of a module's documentation
*
* @throws Zend_Controller_Action_Exception If the required parameter 'chapterId' is missing or if an error in
* the documentation module's library occurs
* @see assertModuleEnabled()
*/
public function chapterAction()
{
$moduleName = $this->getParam('moduleName');
$this->assertModuleEnabled($moduleName);
$chapterId = $this->getParam('chapterId');
if ($chapterId === null) {
throw new Zend_Controller_Action_Exception(
$this->translate('Missing parameter \'chapterId\''),
404
);
}
$moduleManager = Icinga::app()->getModuleManager();
try {
$this->renderChapter(
$moduleManager->getModuleDir($moduleName, '/doc'),
$chapterId,
$this->_helper->url->url(array('moduleName' => $moduleName), 'doc/module/toc'),
'doc/module/chapter',
array('moduleName' => $moduleName)
);
} catch (DocException $e) {
throw new Zend_Controller_Action_Exception($e->getMessage(), 404);
}
$this->view->moduleName = $moduleName;
}
/**
* View a module's documentation as PDF
*
* @see assertModuleEnabled()
*/
public function pdfAction()
{
$moduleName = $this->getParam('moduleName');
$this->assertModuleEnabled($moduleName);
$moduleManager = Icinga::app()->getModuleManager();
$this->renderPdf(
$moduleManager->getModuleDir($moduleName, '/doc'),
$moduleName,
'doc/module/chapter',
array('moduleName' => $moduleName)
);
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

@ -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\Backend\CreateBackendForm;
use Icinga\Module\Monitoring\Form\Config\Instance\EditInstanceForm; use Icinga\Module\Monitoring\Form\Config\Instance\EditInstanceForm;
use Icinga\Module\Monitoring\Form\Config\Instance\CreateInstanceForm; use Icinga\Module\Monitoring\Form\Config\Instance\CreateInstanceForm;
use Icinga\Module\Monitoring\Form\Config\SecurityForm;
use Icinga\Exception\NotReadableError; 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 * 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(); $target = $this->Config($file)->getConfigFile();
$writer = new PreservingIniWriter(array('filename' => $target, 'config' => $config)); $writer = new PreservingIniWriter(array('filename' => $target, 'config' => $config));
@ -258,4 +259,25 @@ class Monitoring_ConfigController extends ModuleActionController
$instanceCfg = $this->Config('instances'); $instanceCfg = $this->Config('instances');
return $instanceCfg && $instanceCfg->get($instance); 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;
}
} }

View File

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

View File

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

View File

@ -0,0 +1,60 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Monitoring\Form\Config;
use Zend_Config;
use Icinga\Web\Form;
class SecurityForm extends Form
{
/**
* The configuration to use for populating the form
*/
protected $config;
/**
* Create this form
*
* @see Icinga\Web\Form::create
*/
public function create()
{
$default = '*pw*,*pass*,community';
$this->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']
));
}
}

View File

@ -0,0 +1,6 @@
<div class="controls">
<?= $this->tabs ?>
</div>
<div class="content">
<?= $this->form ?>
</div>

View File

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

View File

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

View File

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

View File

@ -1,16 +1,8 @@
<?php <?php
if (! $object->customvars) { return; }
foreach ($object->customvars as $name => $value) { foreach ($object->customvars as $name => $value) {
$name = ucwords(str_replace('_', ' ', strtolower($name)));
if (preg_match('~(?:pw|pass|community)~', strtolower($name))) {
$value = '***';
}
printf( printf(
"<tr><th>%s</th><td>%s</td></tr>\n", "<tr><th>%s</th><td>%s</td></tr>\n",
$this->escape($name), $this->escape($name),
$this->escape($value) $this->escape($value)
); );
} }

View File

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

View File

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

View File

@ -12,4 +12,7 @@ $this->provideConfigTab('backends', array(
'title' => 'Backends', 'title' => 'Backends',
'url' => 'config' 'url' => 'config'
)); ));
$this->provideConfigTab('security', array(
'title' => 'Security',
'url' => 'config/security'
));

View File

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

View File

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

View File

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

View File

@ -30,6 +30,8 @@ class Downtime extends DataView
'downtime_internal_id', 'downtime_internal_id',
'downtime_host', 'downtime_host',
'downtime_service', 'downtime_service',
'downtime_host_state',
'downtiem_service_state'
); );
} }

View File

@ -20,6 +20,7 @@ use Icinga\Module\Monitoring\DataView\Comment;
use Icinga\Module\Monitoring\DataView\Servicegroup; use Icinga\Module\Monitoring\DataView\Servicegroup;
use Icinga\Module\Monitoring\DataView\Customvar; use Icinga\Module\Monitoring\DataView\Customvar;
use Icinga\Web\UrlParams; use Icinga\Web\UrlParams;
use Icinga\Application\Config;
abstract class AbstractObject abstract class AbstractObject
@ -120,6 +121,23 @@ abstract class AbstractObject
public function fetchCustomvars() public function fetchCustomvars()
{ {
$blacklist = array();
$blacklistPattern = '/^(.*pw.*|.*pass.*|community)$/';
if ($security = Config::module('monitoring')->get('security')) {
$blacklistConfig = $security->get('protected_customvars', '');
foreach (explode(',', $blacklistConfig) as $customvar) {
$nonWildcards = array();
foreach (explode('*', $customvar) as $nonWildcard) {
$nonWildcards[] = preg_quote($nonWildcard, '/');
}
$blacklist[] = implode('.*', $nonWildcards);
}
$blacklistPattern = '/^(' . implode('|', $blacklist) . ')$/i';
}
$query = Customvar::fromParams(array('backend' => null), array( $query = Customvar::fromParams(array('backend' => null), array(
'varname', 'varname',
'varvalue' 'varvalue'
@ -135,7 +153,15 @@ abstract class AbstractObject
->where('service_description', $this->service_description); ->where('service_description', $this->service_description);
} }
$this->customvars = $query->getQuery()->fetchPairs(); $customvars = $query->getQuery()->fetchPairs();
foreach ($customvars as $name => &$value) {
$name = ucwords(str_replace('_', ' ', strtolower($name)));
if ($blacklistPattern && preg_match($blacklistPattern, $name)) {
$value = '***';
}
$this->customvars[$name] = $value;
}
return $this; return $this;
} }

View File

@ -23,12 +23,8 @@ class TranslationCommand extends Command
*/ */
public function validateLocaleCode($code) public function validateLocaleCode($code)
{ {
$current = setlocale(LC_ALL, '0'); if (! preg_match('@[a-z]{2}_[A-Z]{2}@', $code)) {
$result = setlocale(LC_ALL, $code); throw new Exception("Locale code '$code' is not valid. Expected format is: ll_CC");
setlocale(LC_ALL, $current);
if ($result === false) {
throw new Exception("Locale code '$code' is not valid");
} }
return $code; return $code;

View File

@ -37,12 +37,19 @@ class GettextTranslationHelper
private $moduleMgr; private $moduleMgr;
/** /**
* The current version of IcingaWeb2 * The current version of Icingaweb 2 or of the module the catalog is being created for
* *
* @var string * @var string
*/ */
private $version; private $version;
/**
* The name of the module if any
*
* @var string
*/
private $moduleName;
/** /**
* The locale used by this helper * The locale used by this helper
* *
@ -93,8 +100,7 @@ class GettextTranslationHelper
*/ */
public function __construct(ApplicationBootstrap $bootstrap, $locale) public function __construct(ApplicationBootstrap $bootstrap, $locale)
{ {
$this->version = $bootstrap->getConfig()->app()->global->get('version', '0.1'); $this->moduleMgr = $bootstrap->getModuleManager()->loadEnabledModules();
$this->moduleMgr = $bootstrap->getModuleManager();
$this->appDir = $bootstrap->getApplicationDir(); $this->appDir = $bootstrap->getApplicationDir();
$this->locale = $locale; $this->locale = $locale;
} }
@ -106,6 +112,7 @@ class GettextTranslationHelper
{ {
$this->catalogPath = tempnam(sys_get_temp_dir(), 'IcingaTranslation_'); $this->catalogPath = tempnam(sys_get_temp_dir(), 'IcingaTranslation_');
$this->templatePath = tempnam(sys_get_temp_dir(), 'IcingaPot_'); $this->templatePath = tempnam(sys_get_temp_dir(), 'IcingaPot_');
$this->version = 'None'; // TODO: Access icinga version from a file or property
$this->moduleDir = null; $this->moduleDir = null;
$this->tablePath = implode( $this->tablePath = implode(
@ -133,6 +140,8 @@ class GettextTranslationHelper
{ {
$this->catalogPath = tempnam(sys_get_temp_dir(), 'IcingaTranslation_'); $this->catalogPath = tempnam(sys_get_temp_dir(), 'IcingaTranslation_');
$this->templatePath = tempnam(sys_get_temp_dir(), 'IcingaPot_'); $this->templatePath = tempnam(sys_get_temp_dir(), 'IcingaPot_');
$this->version = $this->moduleMgr->getModule($module)->getVersion();
$this->moduleName = $this->moduleMgr->getModule($module)->getName();
$this->moduleDir = $this->moduleMgr->getModuleDir($module); $this->moduleDir = $this->moduleMgr->getModuleDir($module);
$this->tablePath = implode( $this->tablePath = implode(
@ -251,7 +260,7 @@ class GettextTranslationHelper
'author_name' => 'FIRST AUTHOR', 'author_name' => 'FIRST AUTHOR',
'author_mail' => 'EMAIL@ADDRESS', 'author_mail' => 'EMAIL@ADDRESS',
'author_year' => 'YEAR', 'author_year' => 'YEAR',
'project_name' => 'Icinga Web 2', 'project_name' => $this->moduleName ? ucfirst($this->moduleName) . ' Module' : 'Icinga Web 2',
'project_version' => $this->version, 'project_version' => $this->version,
'project_bug_mail' => 'dev@icinga.org', 'project_bug_mail' => 'dev@icinga.org',
'pot_creation_date' => date('Y-m-d H:iO'), 'pot_creation_date' => date('Y-m-d H:iO'),

BIN
public/img/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

View File

@ -95,6 +95,13 @@ if (!Function.prototype.bind) {
'use strict'; 'use strict';
/* Whether a HTML tag has a specific attribute */
$.fn.hasAttr = function(name) {
// We have inconsistent behaviour across browsers (false VS undef)
var val = this.attr(name);
return typeof val !== 'undefined' && val !== false;
};
/* Get class list */ /* Get class list */
$.fn.classes = function (callback) { $.fn.classes = function (callback) {

View File

@ -474,12 +474,20 @@
return true; return true;
} }
// Ignore clicks on multiselect table inner links while key pressed // Special checks for link clicks in multiselect rows
if ((event.ctrlKey || event.metaKey || event.shiftKey) && if (! $a.is('tr[href]') && $a.closest('tr[href]').length > 0 && $a.closest('table.multiselect').length > 0) {
! $a.is('tr[href]') && $a.closest('table.multiselect').length > 0 &&
$a.closest('tr[href]').length > 0) // Forward clicks to ANY link with special key pressed to rowSelected
{ if (event.ctrlKey || event.metaKey || event.shiftKey)
return self.rowSelected.call($a.closest('tr[href]'), event); {
return self.rowSelected.call($a.closest('tr[href]'), event);
}
// Forward inner links matching the row URL to rowSelected
if ($a.attr('href') === $a.closest('tr[href]').attr('href'))
{
return self.rowSelected.call($a.closest('tr[href]'), event);
}
} }
// Let remote links pass through // Let remote links pass through
@ -493,6 +501,12 @@
return false; return false;
} }
// Ignore form elements in action rows
if ($(event.target).is('input') || $(event.target).is('button')) {
return;
}
// ignore multiselect table row clicks // ignore multiselect table row clicks
if ($a.is('tr') && $a.closest('table.multiselect').length > 0) { if ($a.is('tr') && $a.closest('table.multiselect').length > 0) {
return; return;

View File

@ -162,11 +162,18 @@
parts = document.location.hash.split(/#!/); parts = document.location.hash.split(/#!/);
if ($('#col2').data('icingaUrl') !== main) { if ($('#layout > #login').length) {
icinga.loader.loadUrl( // We are on the login page!
parts[1], $('#login form #redirect').val(
$('#col2') $('#login form #redirect').val() + '#!' + parts[1]
).historyTriggered = true; );
} else {
if ($('#col2').data('icingaUrl') !== main) {
icinga.loader.loadUrl(
parts[1],
$('#col2')
).historyTriggered = true;
}
} }
// TODO: Replace with dynamic columns // TODO: Replace with dynamic columns

View File

@ -250,14 +250,24 @@
var icinga = this.icinga; var icinga = this.icinga;
var redirect = req.getResponseHeader('X-Icinga-Redirect'); var redirect = req.getResponseHeader('X-Icinga-Redirect');
if (! redirect) return false; if (! redirect) return false;
redirect = decodeURIComponent(redirect);
if (redirect.match(/__SELF__/)) {
redirect = redirect.replace(/__SELF__/, encodeURIComponent(document.location.pathname + document.location.search + document.location.hash));
}
icinga.logger.debug( icinga.logger.debug(
'Got redirect for ', req.$target, ', URL was ' + redirect 'Got redirect for ', req.$target, ', URL was ' + redirect
); );
redirect = decodeURIComponent(redirect);
if (req.getResponseHeader('X-Icinga-Rerender-Layout')) { if (req.getResponseHeader('X-Icinga-Rerender-Layout')) {
var parts = redirect.split(/#!/);
redirect = parts.shift();
var redirectionUrl = this.addUrlFlag(redirect, 'renderLayout'); var redirectionUrl = this.addUrlFlag(redirect, 'renderLayout');
this.loadUrl(redirectionUrl, $('#layout')).url = redirect; var r = this.loadUrl(redirectionUrl, $('#layout'));
r.url = redirect;
if (parts.length) {
r.loadNext = parts;
}
} else { } else {
if (req.$target.attr('id') === 'col2') { // TODO: multicol if (req.$target.attr('id') === 'col2') { // TODO: multicol
if ($('#col1').data('icingaUrl') === redirect) { if ($('#col1').data('icingaUrl') === redirect) {
@ -556,6 +566,17 @@
req.$target.data('lastUpdate', (new Date()).getTime()); req.$target.data('lastUpdate', (new Date()).getTime());
delete this.requests[req.$target.attr('id')]; delete this.requests[req.$target.attr('id')];
this.icinga.ui.fadeNotificationsAway(); this.icinga.ui.fadeNotificationsAway();
if (typeof req.loadNext !== 'undefined') {
if ($('#col2').length) {
this.loadUrl(req.loadNext[0], $('#col2'));
this.icinga.ui.layout2col();
} else {
this.icinga.logger.error('Failed to load URL for #col2', req.loadNext);
}
}
this.icinga.ui.refreshDebug(); this.icinga.ui.refreshDebug();
}, },

View File

@ -100,7 +100,7 @@
icinga.logger.info('Reloading CSS'); icinga.logger.info('Reloading CSS');
$('link').each(function() { $('link').each(function() {
var $oldLink = $(this); var $oldLink = $(this);
if ($oldLink.attr('type').indexOf('css') > -1) { if ($oldLink.hasAttr('type') && $oldLink.attr('type').indexOf('css') > -1) {
var $newLink = $oldLink.clone().attr( var $newLink = $oldLink.clone().attr(
'href', 'href',
icinga.utils.addUrlParams( icinga.utils.addUrlParams(

View File

@ -19,8 +19,8 @@ class SlidingwithborderTest extends BaseTestCase
$pages = $scrollingStyle->getPages($paginator); $pages = $scrollingStyle->getPages($paginator);
$this->assertInternalType('array', $pages); $this->assertInternalType('array', $pages);
$this->assertCount(13, $pages); $this->assertCount(10, $pages);
$this->assertEquals('...', $pages[11]); $this->assertEquals('...', $pages[8]);
} }
public function testGetPages3() public function testGetPages3()
@ -31,9 +31,9 @@ class SlidingwithborderTest extends BaseTestCase
$pages = $scrollingStyle->getPages($paginator); $pages = $scrollingStyle->getPages($paginator);
$this->assertInternalType('array', $pages); $this->assertInternalType('array', $pages);
$this->assertCount(16, $pages); $this->assertCount(10, $pages);
$this->assertEquals('...', $pages[3]); $this->assertEquals('...', $pages[3]);
$this->assertEquals('...', $pages[14]); $this->assertEquals('...', $pages[12]);
} }
protected function getPaginatorAdapter() protected function getPaginatorAdapter()

View File

@ -21,7 +21,7 @@ class UrlTest extends BaseTestCase
$url = Url::fromRequest(); $url = Url::fromRequest();
$this->assertEquals( $this->assertEquals(
'/path/to/my/test/url.html?param1=value1&amp;param2=value2', '/path/to/my/test/url.html?param1=value1&amp;param2=value2',
$url->getAbsoluteUrl(), $url->getAbsoluteUrl('&amp;'),
'Url::fromRequest does not reassemble the correct url from the global request' 'Url::fromRequest does not reassemble the correct url from the global request'
); );
} }
@ -119,7 +119,7 @@ class UrlTest extends BaseTestCase
*/ */
public function testWhetherFromPathProperlyRecognizesAndDecodesQueryParameters() public function testWhetherFromPathProperlyRecognizesAndDecodesQueryParameters()
{ {
$url = Url::fromPath('/my/test/url.html?param1=%25arg1&param2=arg+2' $url = Url::fromPath('/my/test/url.html?param1=%25arg1&param2=arg%202'
. '&param3[]=1&param3[]=2&param3[]=3&param4[key1]=val1&param4[key2]=val2'); . '&param3[]=1&param3[]=2&param3[]=3&param4[key1]=val1&param4[key2]=val2');
$this->assertEquals( $this->assertEquals(