Merge branch 'master' into bugfix/styled-history-views-6637
This commit is contained in:
commit
09dfcca0bc
|
@ -1,6 +1,6 @@
|
|||
object CheckCommand "dummy-host" {
|
||||
import "plugin-check-command"
|
||||
command = [ PluginDir + "/libexec/test_hostcheck.pl" ]
|
||||
command = [ PluginDir + "/test_hostcheck.pl" ]
|
||||
arguments = {
|
||||
"--type" = "$check_type$"
|
||||
"--failchance" = "$check_failchance$"
|
||||
|
@ -18,7 +18,7 @@ object CheckCommand "dummy-host" {
|
|||
|
||||
object CheckCommand "dummy-service" {
|
||||
import "plugin-check-command"
|
||||
command = [ PluginDir + "/libexec/test_servicecheck.pl" ]
|
||||
command = [ PluginDir + "/test_servicecheck.pl" ]
|
||||
arguments = {
|
||||
"--total-critical-on-host" = "$check_critical_on_host$"
|
||||
"--total-warning-on-host" = "$check_warning_on_host$"
|
||||
|
|
|
@ -18,10 +18,15 @@ title = "Configuration"
|
|||
url = "config"
|
||||
priority = 300
|
||||
|
||||
[System.Modules]
|
||||
title = "Modules"
|
||||
url = "config/modules"
|
||||
priority = 400
|
||||
|
||||
[System.ApplicationLog]
|
||||
title = "Application log"
|
||||
url = "list/applicationlog"
|
||||
priority = 400
|
||||
priority = 500
|
||||
|
||||
[Logout]
|
||||
url = "authentication/logout"
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
[Documentation]
|
||||
title = "Documentation"
|
||||
icon = "img/icons/comment.png"
|
||||
url = "doc"
|
||||
priority = 80
|
|
@ -3,7 +3,7 @@ include mysql
|
|||
include pgsql
|
||||
include openldap
|
||||
|
||||
Exec { path => '/bin:/usr/bin:/sbin' }
|
||||
Exec { path => '/bin:/usr/bin:/sbin:/usr/sbin' }
|
||||
|
||||
$icingaVersion = '1.11.5'
|
||||
$icinga2Version = '2.0.1'
|
||||
|
@ -76,10 +76,10 @@ cmmi { 'icinga-mysql':
|
|||
--with-htmurl=/icinga-mysql --with-httpd-conf-file=/etc/httpd/conf.d/icinga-mysql.conf \
|
||||
--with-cgiurl=/icinga-mysql/cgi-bin \
|
||||
--with-http-auth-file=/usr/share/icinga/htpasswd.users \
|
||||
--with-plugin-dir=/usr/lib64/nagios/plugins/libexec',
|
||||
--with-plugin-dir=/usr/lib64/nagios/plugins',
|
||||
creates => '/usr/local/icinga-mysql',
|
||||
make => 'make all && make fullinstall install-config',
|
||||
require => [ User['icinga'], Cmmi['icinga-plugins'], Package['apache'] ],
|
||||
require => [ User['icinga'], Class['monitoring-plugins'], Package['apache'] ],
|
||||
notify => Service['apache']
|
||||
}
|
||||
|
||||
|
@ -102,10 +102,10 @@ cmmi { 'icinga-pgsql':
|
|||
--with-htmurl=/icinga-pgsql --with-httpd-conf-file=/etc/httpd/conf.d/icinga-pgsql.conf \
|
||||
--with-cgiurl=/icinga-pgsql/cgi-bin \
|
||||
--with-http-auth-file=/usr/share/icinga/htpasswd.users \
|
||||
--with-plugin-dir=/usr/lib64/nagios/plugins/libexec',
|
||||
--with-plugin-dir=/usr/lib64/nagios/plugins',
|
||||
creates => '/usr/local/icinga-pgsql',
|
||||
make => 'make all && make fullinstall install-config',
|
||||
require => [ User['icinga'], Cmmi['icinga-plugins'], Package['apache'] ],
|
||||
require => [ User['icinga'], Class['monitoring-plugins'], Package['apache'] ],
|
||||
notify => Service['apache']
|
||||
}
|
||||
|
||||
|
@ -210,16 +210,7 @@ exec { 'icinga-htpasswd':
|
|||
require => Class['apache']
|
||||
}
|
||||
|
||||
cmmi { 'icinga-plugins':
|
||||
url => "https://www.monitoring-plugins.org/download/monitoring-plugins-${pluginVersion}.tar.gz",
|
||||
output => "monitoring-plugins-${pluginVersion}.tar.gz",
|
||||
flags => '--prefix=/usr/lib64/nagios/plugins \
|
||||
--with-nagios-user=icinga --with-nagios-group=icinga \
|
||||
--with-cgiurl=/icinga-mysql/cgi-bin',
|
||||
creates => '/usr/lib64/nagios/plugins/libexec',
|
||||
make => 'make && make install',
|
||||
require => User['icinga']
|
||||
}
|
||||
include monitoring-plugins
|
||||
|
||||
cmmi { 'mk-livestatus':
|
||||
url => "http://mathias-kettner.de/download/mk-livestatus-${livestatusVersion}.tar.gz",
|
||||
|
@ -425,7 +416,7 @@ package { 'icinga2-ido-mysql':
|
|||
|
||||
exec { 'populate-icinga2-mysql-db':
|
||||
unless => 'mysql -uicinga2 -picinga2 icinga2 -e "SELECT * FROM icinga_dbversion;" &> /dev/null',
|
||||
command => 'mysql -uroot icinga2 < /usr/share/doc/icinga2-ido-mysql-$(rpm -q icinga2-ido-mysql | cut -d\'-\' -f4)/schema/mysql.sql',
|
||||
command => 'mysql -uroot icinga2 < /usr/share/icinga2-ido-mysql/schema/mysql.sql',
|
||||
require => [ Exec['create-mysql-icinga2-db'], Package['icinga2-ido-mysql'] ]
|
||||
}
|
||||
|
||||
|
@ -577,7 +568,7 @@ populate_monitoring_test_config { ['commands', 'contacts', 'dependencies',
|
|||
}
|
||||
|
||||
define populate_monitoring_test_config_plugins {
|
||||
file { "/usr/lib64/nagios/plugins/libexec/${name}":
|
||||
file { "/usr/lib64/nagios/plugins/${name}":
|
||||
owner => 'icinga',
|
||||
group => 'icinga',
|
||||
source => "/usr/local/share/misc/monitoring_test_config/plugins/${name}",
|
||||
|
@ -795,3 +786,15 @@ file { '/etc/bash_completion.d/icingacli':
|
|||
require => Exec['install bash-completion']
|
||||
}
|
||||
|
||||
file { '/etc/icingaweb/modules/doc/':
|
||||
ensure => 'directory',
|
||||
owner => 'apache',
|
||||
group => 'apache'
|
||||
}
|
||||
|
||||
file { '/etc/icingaweb/modules/doc/menu.ini':
|
||||
source => 'puppet:////vagrant/.vagrant-puppet/files/etc/icingaweb/modules/doc/menu.ini',
|
||||
owner => 'apache',
|
||||
group => 'apache',
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
class monitoring-plugins {
|
||||
include epel
|
||||
|
||||
# nagios plugins from epel
|
||||
package { 'nagios-plugins-all':
|
||||
ensure => installed,
|
||||
require => Class['epel']
|
||||
}
|
||||
}
|
|
@ -35,12 +35,17 @@ class AuthenticationController extends ActionController
|
|||
public function loginAction()
|
||||
{
|
||||
$auth = $this->Auth();
|
||||
$this->view->form = new LoginForm();
|
||||
$this->view->form->setRequest($this->_request);
|
||||
$this->view->form = $form = new LoginForm();
|
||||
$form->setRequest($this->_request);
|
||||
$this->view->title = $this->translate('Icingaweb Login');
|
||||
|
||||
try {
|
||||
$redirectUrl = Url::fromPath($this->params->get('redirect', 'dashboard'));
|
||||
$redirectUrl = $this->view->form->getValue('redirect');
|
||||
if ($redirectUrl) {
|
||||
$redirectUrl = Url::fromPath($redirectUrl);
|
||||
} else {
|
||||
$redirectUrl = Url::fromPath('dashboard');
|
||||
}
|
||||
|
||||
if ($auth->isAuthenticated()) {
|
||||
$this->rerenderLayout()->redirectNow($redirectUrl);
|
||||
|
@ -49,13 +54,9 @@ class AuthenticationController extends ActionController
|
|||
try {
|
||||
$config = Config::app('authentication');
|
||||
} catch (NotReadableError $e) {
|
||||
Logger::error(
|
||||
new Exception('Cannot load authentication configuration. An exception was thrown:', 0, $e)
|
||||
);
|
||||
throw new ConfigurationError(
|
||||
t(
|
||||
'No authentication methods available. Authentication configuration could not be loaded.'
|
||||
. ' Please check the system log or Icinga Web 2 log for more information'
|
||||
$this->translate(
|
||||
'Could not read your authentiction.ini, no authentication methods are available.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -72,12 +73,20 @@ class AuthenticationController extends ActionController
|
|||
}
|
||||
}
|
||||
}
|
||||
} elseif ($this->view->form->isSubmittedAndValid()) {
|
||||
$user = new User($this->view->form->getValue('username'));
|
||||
$password = $this->view->form->getValue('password');
|
||||
} elseif ($form->isSubmittedAndValid()) {
|
||||
$user = new User($form->getValue('username'));
|
||||
$password = $form->getValue('password');
|
||||
$backendsTried = 0;
|
||||
$backendsWithError = 0;
|
||||
|
||||
$redirectUrl = $form->getValue('redirect');
|
||||
|
||||
if ($redirectUrl) {
|
||||
$redirectUrl = Url::fromPath($redirectUrl);
|
||||
} else {
|
||||
$redirectUrl = Url::fromPath('dashboard');
|
||||
}
|
||||
|
||||
foreach ($chain as $backend) {
|
||||
if ($backend instanceof AutoLoginBackend) {
|
||||
continue;
|
||||
|
@ -97,29 +106,29 @@ class AuthenticationController extends ActionController
|
|||
}
|
||||
if ($backendsTried === 0) {
|
||||
throw new ConfigurationError(
|
||||
t(
|
||||
'No authentication methods available. It seems that no authentication method has been set'
|
||||
. ' up. Please check the system log or Icinga Web 2 log for more information'
|
||||
)
|
||||
$this->translate(
|
||||
'No authentication methods available. Did you create'
|
||||
. ' authentication.ini when installing Icinga Web 2?'
|
||||
)
|
||||
);
|
||||
}
|
||||
if ($backendsTried === $backendsWithError) {
|
||||
throw new ConfigurationError(
|
||||
$this->translate(
|
||||
'No authentication methods available. It seems that all set up authentication methods have'
|
||||
. ' errors. Please check the system log or Icinga Web 2 log for more information'
|
||||
'All configured authentication methods failed.'
|
||||
. ' Please check the system log or Icinga Web 2 log for more information.'
|
||||
)
|
||||
);
|
||||
}
|
||||
if ($backendsWithError) {
|
||||
$this->view->form->addNote(
|
||||
$form->addNote(
|
||||
$this->translate(
|
||||
'Note that not all authentication backends are available for authentication because they'
|
||||
. ' have errors. Please check the system log or Icinga Web 2 log for more information'
|
||||
'Please note that not all authentication methods where available.'
|
||||
. ' Check the system log or Icinga Web 2 log for more information.'
|
||||
)
|
||||
);
|
||||
}
|
||||
$this->view->form->getElement('password')->addError($this->translate('Incorrect username or password'));
|
||||
$form->getElement('password')->addError($this->translate('Incorrect username or password'));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->view->errorInfo = $e->getMessage();
|
||||
|
|
|
@ -48,9 +48,6 @@ class ConfigController extends BaseConfigController
|
|||
))->add('logging', array(
|
||||
'title' => 'Logging',
|
||||
'url' => 'config/logging'
|
||||
))->add('modules', array(
|
||||
'title' => 'Modules',
|
||||
'url' => 'config/modules'
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -70,7 +67,7 @@ class ConfigController extends BaseConfigController
|
|||
$form->setConfiguration(IcingaConfig::app());
|
||||
$form->setRequest($this->_request);
|
||||
if ($form->isSubmittedAndValid()) {
|
||||
if (!$this->writeConfigFile($form->getConfig(), 'config')) {
|
||||
if (!$this->writeConfigFile($form->getConfig(), 'config')) {
|
||||
return;
|
||||
}
|
||||
Notification::success('New configuration has successfully been stored');
|
||||
|
@ -107,6 +104,11 @@ class ConfigController extends BaseConfigController
|
|||
*/
|
||||
public function modulesAction()
|
||||
{
|
||||
$this->view->tabs = Widget::create('tabs')->add('modules', array(
|
||||
'title' => 'Modules',
|
||||
'url' => 'config/modules'
|
||||
));
|
||||
|
||||
$this->view->tabs->activate('modules');
|
||||
$this->view->modules = Icinga::app()->getModuleManager()->select()
|
||||
->from('modules')
|
||||
|
|
|
@ -18,7 +18,9 @@ class LayoutController extends ActionController
|
|||
*/
|
||||
public function menuAction()
|
||||
{
|
||||
$this->view->menuRenderer = new MenuRenderer(Menu::fromConfig()->order(), Url::fromRequest()->getRelativeUrl());
|
||||
$this->view->menuRenderer = new MenuRenderer(
|
||||
Menu::fromConfig()->order(), Url::fromRequest()->without('renderLayout')->getRelativeUrl()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
namespace Icinga\Form\Authentication;
|
||||
|
||||
use Icinga\Web\Form;
|
||||
use Icinga\Web\Url;
|
||||
|
||||
/**
|
||||
* Class LoginForm
|
||||
|
@ -16,12 +17,19 @@ class LoginForm extends Form
|
|||
*/
|
||||
protected function create()
|
||||
{
|
||||
$url = Url::fromRequest()->without('renderLayout');
|
||||
|
||||
$this->setName('form_login');
|
||||
$this->addElement('text', 'username', array(
|
||||
'label' => t('Username'),
|
||||
'placeholder' => t('Please enter your username...'),
|
||||
'required' => true,
|
||||
));
|
||||
$redir = $this->addElement('hidden', 'redirect');
|
||||
$redirectUrl = $url->shift('redirect');
|
||||
if ($redirectUrl) {
|
||||
$this->setDefault('redirect', $redirectUrl);
|
||||
}
|
||||
|
||||
$this->addElement('password', 'password', array(
|
||||
'label' => t('Password'),
|
||||
|
@ -34,6 +42,7 @@ class LoginForm extends Form
|
|||
} else {
|
||||
$this->getElement('username')->setAttrib('class', 'autofocus');
|
||||
}
|
||||
$this->setAction((string) $url);
|
||||
$this->setSubmitLabel('Login');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ if ($notifications->hasMessages()) {
|
|||
</div>
|
||||
<?php endif ?>
|
||||
<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') ?>
|
||||
</div>
|
||||
<div id="col2" class="container">
|
||||
|
|
|
@ -44,6 +44,7 @@ $iframeClass = $isIframe ? ' iframe' : '';
|
|||
<!--[if lt IE 9]>
|
||||
<script src="<?= $this->baseUrl('js/vendor/respond.min.js');?>"></script>
|
||||
<![endif]-->
|
||||
<link type="image/png" rel="shortcut icon" href="<?= $this->baseUrl('img/favicon.png') ?>" />
|
||||
|
||||
</head>
|
||||
<body id="body">
|
||||
|
|
|
@ -14,5 +14,5 @@ if (! $this->auth()->isAuthenticated()) {
|
|||
<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" />
|
||||
</form>
|
||||
<?= new MenuRenderer(Menu::fromConfig()->order(), Url::fromRequest()->getRelativeUrl()); ?>
|
||||
<?= new MenuRenderer(Menu::fromConfig()->order(), Url::fromRequest()->without('renderLayout')->getRelativeUrl()); ?>
|
||||
</div>
|
||||
|
|
|
@ -18,10 +18,15 @@ title = "Configuration"
|
|||
url = "config"
|
||||
priority = 300
|
||||
|
||||
[System.Modules]
|
||||
title = "Modules"
|
||||
url = "config/modules"
|
||||
priority = 400
|
||||
|
||||
[System.ApplicationLog]
|
||||
title = "Application log"
|
||||
url = "list/applicationlog"
|
||||
priority = 400
|
||||
priority = 500
|
||||
|
||||
[Logout]
|
||||
url = "authentication/logout"
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
[Documentation]
|
||||
title = "Documentation"
|
||||
icon = "img/icons/comment.png"
|
||||
url = "doc"
|
||||
priority = 80
|
|
@ -40,11 +40,12 @@
|
|||
%endif
|
||||
# SLE 11 = 1110
|
||||
%if 0%{?suse_version} == 1110
|
||||
%define phpname php53
|
||||
%define apache2modphpname apache2-mod_php53
|
||||
%define usermodparam -A
|
||||
%endif
|
||||
|
||||
%if "%{_vendor}" == "redhat" || 0%{?suse_version} == 1110
|
||||
%if "%{_vendor}" == "redhat"
|
||||
%define phpname php
|
||||
%define phpzendname php-ZendFramework
|
||||
%endif
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
namespace Icinga\Application\Modules;
|
||||
|
||||
use Exception;
|
||||
use Zend_Controller_Router_Route_Abstract;
|
||||
use Zend_Controller_Router_Route as Route;
|
||||
use Icinga\Application\ApplicationBootstrap;
|
||||
use Icinga\Application\Config;
|
||||
|
@ -135,6 +136,16 @@ class Module
|
|||
*/
|
||||
private $app;
|
||||
|
||||
|
||||
/**
|
||||
* Routes to add to the route chain
|
||||
*
|
||||
* @var array Array of name-route pairs
|
||||
*
|
||||
* @see addRoute()
|
||||
*/
|
||||
protected $routes = array();
|
||||
|
||||
/**
|
||||
* Create a new module object
|
||||
*
|
||||
|
@ -166,8 +177,7 @@ class Module
|
|||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->registerAutoloader()
|
||||
->registerWebIntegration();
|
||||
$this->registerAutoloader();
|
||||
try {
|
||||
$this->launchRunScript();
|
||||
} catch (Exception $e) {
|
||||
|
@ -179,6 +189,7 @@ class Module
|
|||
);
|
||||
return false;
|
||||
}
|
||||
$this->registerWebIntegration();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -658,24 +669,29 @@ class Module
|
|||
}
|
||||
|
||||
/**
|
||||
* Register routes for web access
|
||||
* Add routes for static content and any route added via addRoute() to the route chain
|
||||
*
|
||||
* @return self
|
||||
* @return self
|
||||
* @see addRoute()
|
||||
*/
|
||||
protected function registerRoutes()
|
||||
{
|
||||
$this->app->getFrontController()->getRouter()->addRoute(
|
||||
$router = $this->app->getFrontController()->getRouter();
|
||||
foreach ($this->routes as $name => $route) {
|
||||
$router->addRoute($name, $route);
|
||||
}
|
||||
$router->addRoute(
|
||||
$this->name . '_jsprovider',
|
||||
new Route(
|
||||
'js/' . $this->name . '/:file',
|
||||
array(
|
||||
'controller' => 'static',
|
||||
'action' =>'javascript',
|
||||
'module_name' => $this->name
|
||||
'module_name' => $this->name
|
||||
)
|
||||
)
|
||||
);
|
||||
$this->app->getFrontController()->getRouter()->addRoute(
|
||||
$router->addRoute(
|
||||
$this->name . '_img',
|
||||
new Route(
|
||||
'img/' . $this->name . '/:file',
|
||||
|
@ -750,4 +766,19 @@ class Module
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a route which will be added to the route chain
|
||||
*
|
||||
* @param string $name Name of the route
|
||||
* @param Zend_Controller_Router_Route_Abstract $route Instance of the route
|
||||
*
|
||||
* @return self
|
||||
* @see registerRoutes()
|
||||
*/
|
||||
protected function addRoute($name, Zend_Controller_Router_Route_Abstract $route)
|
||||
{
|
||||
$this->routes[$name] = $route;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ namespace Icinga\Cli;
|
|||
use Icinga\Cli\Screen;
|
||||
use Icinga\Util\Translator;
|
||||
use Icinga\Cli\Params;
|
||||
use Icinga\Application\Config;
|
||||
use Icinga\Application\ApplicationBootstrap as App;
|
||||
use Exception;
|
||||
|
||||
|
@ -23,6 +24,10 @@ abstract class Command
|
|||
protected $commandName;
|
||||
protected $actionName;
|
||||
|
||||
private $config;
|
||||
|
||||
private $configs;
|
||||
|
||||
protected $defaultActionName = 'default';
|
||||
|
||||
public function __construct(App $app, $moduleName, $commandName, $actionName, $initialize = true)
|
||||
|
@ -41,6 +46,51 @@ abstract class Command
|
|||
}
|
||||
}
|
||||
|
||||
public function Config($file = null)
|
||||
{
|
||||
if ($this->isModule()) {
|
||||
return $this->getModuleConfig($file);
|
||||
} else {
|
||||
return $this->getMainConfig($file);
|
||||
}
|
||||
}
|
||||
|
||||
private function getModuleConfig($file = null)
|
||||
{
|
||||
if ($file === null) {
|
||||
if ($this->config === null) {
|
||||
$this->config = Config::module($this->moduleName);
|
||||
}
|
||||
return $this->config;
|
||||
} else {
|
||||
if (! array_key_exists($file, $this->configs)) {
|
||||
$this->configs[$file] = Config::module($this->moduleName, $file);
|
||||
}
|
||||
return $this->configs[$file];
|
||||
}
|
||||
}
|
||||
|
||||
private function getMainConfig($file = null)
|
||||
{
|
||||
if ($file === null) {
|
||||
if ($this->config === null) {
|
||||
$this->config = Config::app();
|
||||
}
|
||||
return $this->config;
|
||||
} else {
|
||||
if (! array_key_exists($file, $this->configs)) {
|
||||
$this->configs[$file] = Config::module($module, $file);
|
||||
}
|
||||
return $this->configs[$file];
|
||||
}
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
public function isModule()
|
||||
{
|
||||
return substr(get_class($this), 0, 14) === 'Icinga\\Module\\';
|
||||
}
|
||||
|
||||
public function setParams(Params $params)
|
||||
{
|
||||
$this->params = $params;
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -254,10 +254,27 @@ class ActionController extends Zend_Controller_Action
|
|||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function redirectToLogin($afterLogin = '/dashboard')
|
||||
protected function redirectToLogin($afterLogin = null)
|
||||
{
|
||||
$redir = null;
|
||||
if ($afterLogin !== null) {
|
||||
if (! $afterLogin instanceof Url) {
|
||||
$afterLogin = Url::fromPath($afterLogin);
|
||||
}
|
||||
if ($this->isXhr()) {
|
||||
$redir = '__SELF__';
|
||||
} else {
|
||||
// TODO: Ignore /?
|
||||
$redir = $afterLogin->getRelativeUrl();
|
||||
}
|
||||
}
|
||||
|
||||
$url = Url::fromPath('authentication/login');
|
||||
$url->setParam('redirect', $afterLogin);
|
||||
|
||||
if ($redir) {
|
||||
$url->setParam('redirect', $redir);
|
||||
}
|
||||
|
||||
$this->rerenderLayout()->redirectNow($url);
|
||||
}
|
||||
|
||||
|
@ -273,6 +290,27 @@ class ActionController extends Zend_Controller_Action
|
|||
return $this->getRequest()->isXmlHttpRequest();
|
||||
}
|
||||
|
||||
protected function redirectXhr($url)
|
||||
{
|
||||
if (! $url instanceof Url) {
|
||||
$url = Url::fromPath($url);
|
||||
}
|
||||
|
||||
if ($this->rerenderLayout) {
|
||||
$this->getResponse()->setHeader('X-Icinga-Rerender-Layout', 'yes');
|
||||
}
|
||||
if ($this->reloadCss) {
|
||||
$this->getResponse()->setHeader('X-Icinga-Reload-Css', 'now');
|
||||
}
|
||||
|
||||
$this->getResponse()
|
||||
->setHeader('X-Icinga-Redirect', rawurlencode($url->getAbsoluteUrl()))
|
||||
->sendHeaders();
|
||||
|
||||
// TODO: Session shutdown?
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to a specific url, updating the browsers URL field
|
||||
*
|
||||
|
@ -280,26 +318,13 @@ class ActionController extends Zend_Controller_Action
|
|||
**/
|
||||
public function redirectNow($url)
|
||||
{
|
||||
if (! $url instanceof Url) {
|
||||
$url = Url::fromPath($url);
|
||||
}
|
||||
$url = preg_replace('~&~', '&', $url);
|
||||
if ($this->isXhr()) {
|
||||
if ($this->rerenderLayout) {
|
||||
$this->getResponse()->setHeader('X-Icinga-Rerender-Layout', 'yes');
|
||||
}
|
||||
if ($this->reloadCss) {
|
||||
$this->getResponse()->setHeader('X-Icinga-Reload-Css', 'now');
|
||||
}
|
||||
|
||||
$this->getResponse()
|
||||
->setHeader('X-Icinga-Redirect', rawurlencode($url))
|
||||
->sendHeaders();
|
||||
|
||||
// TODO: Session shutdown?
|
||||
exit;
|
||||
$this->redirectXhr($url);
|
||||
} else {
|
||||
$this->_helper->Redirector->gotoUrlAndExit(Url::fromPath($url)->getRelativeUrl());
|
||||
if (! $url instanceof Url) {
|
||||
$url = Url::fromPath($url);
|
||||
}
|
||||
$this->_helper->Redirector->gotoUrlAndExit($url->getRelativeUrl());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -127,10 +127,6 @@ class Url
|
|||
$baseUrl = $request->getBaseUrl();
|
||||
$urlObject->setBaseUrl($baseUrl);
|
||||
|
||||
// Fetch fragment manually and remove it from the url, to 'help' the parse_url() function
|
||||
// parsing the url properly. Otherwise calling the function with a fragment, but without a
|
||||
// query will cause unpredictable behaviour.
|
||||
$fragment = self::stripUrlFragment($url);
|
||||
$urlParts = parse_url($url);
|
||||
if (isset($urlParts['path'])) {
|
||||
if ($baseUrl !== '' && strpos($urlParts['path'], $baseUrl) === 0) {
|
||||
|
@ -144,29 +140,14 @@ class Url
|
|||
$params = UrlParams::fromQueryString($urlParts['query'])->mergeValues($params);
|
||||
}
|
||||
|
||||
if ($fragment) {
|
||||
$urlObject->setAnchor($fragment);
|
||||
if (isset($urlParts['fragment'])) {
|
||||
$urlObject->setAnchor($urlParts['fragment']);
|
||||
}
|
||||
|
||||
$urlObject->setParams($params);
|
||||
return $urlObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the fragment-part of a given url and return it
|
||||
*
|
||||
* @param string $url The url to strip its fragment from
|
||||
*
|
||||
* @return null|string The stripped fragment, without the '#'
|
||||
*/
|
||||
protected static function stripUrlFragment(&$url)
|
||||
{
|
||||
if (preg_match('@#(.*)$@', $url, $matches)) {
|
||||
$url = str_replace('#' . $matches[1], '', $url);
|
||||
return $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite the baseUrl
|
||||
*
|
||||
|
@ -226,12 +207,12 @@ class Url
|
|||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRelativeUrl()
|
||||
public function getRelativeUrl($separator = '&')
|
||||
{
|
||||
if ($this->params->isEmpty()) {
|
||||
return $this->path . $this->anchor;
|
||||
} else {
|
||||
return $this->path . '?' . $this->params->setSeparator('&') . $this->anchor;
|
||||
return $this->path . '?' . $this->params->toString($separator) . $this->anchor;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -251,9 +232,9 @@ class Url
|
|||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAbsoluteUrl()
|
||||
public function getAbsoluteUrl($separator = '&')
|
||||
{
|
||||
return $this->baseUrl . ($this->baseUrl !== '/' ? '/' : '') . $this->getRelativeUrl();
|
||||
return $this->baseUrl . ($this->baseUrl !== '/' ? '/' : '') . $this->getRelativeUrl($separator);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -435,6 +416,6 @@ class Url
|
|||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getAbsoluteUrl();
|
||||
return $this->getAbsoluteUrl('&');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,6 +114,18 @@ class UrlParams
|
|||
return $ret;
|
||||
}
|
||||
|
||||
public function addEncoded($param, $value = true)
|
||||
{
|
||||
$this->params[] = array($param, $this->cleanupValue($value));
|
||||
$this->indexLastOne();
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function urlEncode($value)
|
||||
{
|
||||
return rawurlencode((string) $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given parameter with the given value
|
||||
*
|
||||
|
@ -127,9 +139,7 @@ class UrlParams
|
|||
*/
|
||||
public function add($param, $value = true)
|
||||
{
|
||||
$this->params[] = array($param, $this->cleanupValue($value));
|
||||
$this->indexLastOne();
|
||||
return $this;
|
||||
return $this->addEncoded($this->urlEncode($param), $this->urlEncode($value));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -198,7 +208,7 @@ class UrlParams
|
|||
*/
|
||||
public function unshift($param, $value)
|
||||
{
|
||||
array_unshift($this->params, array($param, $this->cleanupValue($value)));
|
||||
array_unshift($this->params, array($this->urlEncode($param), $this->urlEncode($value)));
|
||||
$this->reIndexAll();
|
||||
return $this;
|
||||
}
|
||||
|
@ -224,7 +234,10 @@ class UrlParams
|
|||
unset($this->params[$remove]);
|
||||
}
|
||||
|
||||
$this->params[$this->index[$param][0]] = array($param, $this->cleanupValue($value));
|
||||
$this->params[$this->index[$param][0]] = array(
|
||||
$this->urlEncode($param),
|
||||
$this->urlEncode($this->cleanupValue($value))
|
||||
);
|
||||
$this->reIndexAll();
|
||||
|
||||
return $this;
|
||||
|
@ -243,7 +256,7 @@ class UrlParams
|
|||
foreach ($this->index[$p] as $key) {
|
||||
unset($this->params[$key]);
|
||||
}
|
||||
$this->changed = true;
|
||||
$changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -303,10 +316,10 @@ class UrlParams
|
|||
protected function parseQueryStringPart($part)
|
||||
{
|
||||
if (strpos($part, '=') === false) {
|
||||
$this->add($part, true);
|
||||
$this->addEncoded($part, true);
|
||||
} else {
|
||||
list($key, $val) = preg_split('/=/', $part, 2);
|
||||
$this->add($key, $val);
|
||||
$this->addEncoded($key, $val);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -315,8 +328,11 @@ class UrlParams
|
|||
return $this->params;
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
public function toString($separator = null)
|
||||
{
|
||||
if ($separator === null) {
|
||||
$separator = $this->separator;
|
||||
}
|
||||
$parts = array();
|
||||
foreach ($this->params as $p) {
|
||||
if ($p[1] === true) {
|
||||
|
@ -325,13 +341,18 @@ class UrlParams
|
|||
$parts[] = $p[0] . '=' . $p[1];
|
||||
}
|
||||
}
|
||||
return implode($this->separator, $parts);
|
||||
return implode($separator, $parts);
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->toString();
|
||||
}
|
||||
|
||||
public static function fromQueryString($queryString = null)
|
||||
{
|
||||
if ($queryString === null) {
|
||||
$queryString = $_SERVER['QUERY_STRING'];
|
||||
$queryString = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
|
||||
}
|
||||
$params = new static();
|
||||
$params->parseQueryString($queryString);
|
||||
|
|
|
@ -81,7 +81,7 @@ class Limiter extends AbstractWidget
|
|||
$this->url->setParam('limit', $limit),
|
||||
null,
|
||||
array(
|
||||
'title' => t(sprintf('Show %s rows on one page', $caption))
|
||||
'title' => sprintf(t('Show %s rows on one page'), $caption)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -2,34 +2,9 @@
|
|||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
use Icinga\Module\Doc\Controller as DocController;
|
||||
|
||||
use Icinga\Module\Doc\DocParser;
|
||||
use Icinga\Module\Doc\DocController;
|
||||
|
||||
class Doc_IndexController extends DocController
|
||||
{
|
||||
protected $parser;
|
||||
|
||||
|
||||
public function init()
|
||||
{
|
||||
$module = null;
|
||||
$this->parser = new DocParser($module);
|
||||
}
|
||||
|
||||
|
||||
public function tocAction()
|
||||
{
|
||||
// Temporary workaround
|
||||
list($html, $toc) = $this->parser->getDocumentation();
|
||||
$this->view->toc = $toc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the application's documentation
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
$this->populateView();
|
||||
}
|
||||
public function indexAction() {}
|
||||
}
|
||||
|
|
|
@ -2,44 +2,131 @@
|
|||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
use \Zend_Controller_Action_Exception;
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Module\Doc\Controller as DocController;
|
||||
use Icinga\Module\Doc\DocController;
|
||||
use Icinga\Module\Doc\Exception\DocException;
|
||||
|
||||
class Doc_ModuleController extends DocController
|
||||
{
|
||||
/**
|
||||
* Display module documentations index
|
||||
* List modules which are enabled and having the 'doc' directory
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
$this->view->enabledModules = Icinga::app()->getModuleManager()->listEnabledModules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a module's documentation
|
||||
*/
|
||||
public function viewAction()
|
||||
{
|
||||
$this->populateView($this->getParam('name'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide run-time dispatching of module documentation
|
||||
*
|
||||
* @param string $methodName
|
||||
* @param array $args
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($methodName, $args)
|
||||
{
|
||||
// TODO(el): Setup routing to retrieve module name as param and point route to moduleAction
|
||||
$moduleManager = Icinga::app()->getModuleManager();
|
||||
$moduleName = substr($methodName, 0, -6); // Strip 'Action' suffix
|
||||
if (!$moduleManager->hasEnabled($moduleName)) {
|
||||
// TODO(el): Throw a not found exception once the code has been moved to the moduleAction (see TODO above)
|
||||
return parent::__call($methodName, $args);
|
||||
$moduleManager = Icinga::app()->getModuleManager();
|
||||
$modules = array();
|
||||
foreach (Icinga::app()->getModuleManager()->listEnabledModules() as $enabledModule) {
|
||||
$docDir = $moduleManager->getModuleDir($enabledModule, '/doc');
|
||||
if (is_dir($docDir)) {
|
||||
$modules[] = $enabledModule;
|
||||
}
|
||||
}
|
||||
$this->_helper->redirector->gotoSimpleAndExit('view', null, null, array('name' => $moduleName));
|
||||
$this->view->modules = $modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given module is enabled
|
||||
*
|
||||
* @param $moduleName
|
||||
*
|
||||
* @throws Zend_Controller_Action_Exception If the required parameter 'moduleName' is empty or either if the
|
||||
* given module is neither installed nor enabled
|
||||
*/
|
||||
protected function assertModuleEnabled($moduleName)
|
||||
{
|
||||
if (empty($moduleName)) {
|
||||
throw new Zend_Controller_Action_Exception(
|
||||
$this->translate('Missing parameter \'moduleName\''),
|
||||
404
|
||||
);
|
||||
}
|
||||
$moduleManager = Icinga::app()->getModuleManager();
|
||||
if (! $moduleManager->hasInstalled($moduleName)) {
|
||||
throw new Zend_Controller_Action_Exception(
|
||||
sprintf($this->translate('Module \'%s\' is not installed'), $moduleName),
|
||||
404
|
||||
);
|
||||
}
|
||||
if (! $moduleManager->hasEnabled($moduleName)) {
|
||||
throw new Zend_Controller_Action_Exception(
|
||||
sprintf($this->translate('Module \'%s\' is not enabled'), $moduleName),
|
||||
404
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* View the toc of a module's documentation
|
||||
*
|
||||
* @see assertModuleEnabled()
|
||||
*/
|
||||
public function tocAction()
|
||||
{
|
||||
$moduleName = $this->getParam('moduleName');
|
||||
$this->assertModuleEnabled($moduleName);
|
||||
$moduleManager = Icinga::app()->getModuleManager();
|
||||
try {
|
||||
$this->renderToc(
|
||||
$moduleManager->getModuleDir($moduleName, '/doc'),
|
||||
$moduleName,
|
||||
'doc/module/chapter',
|
||||
array('moduleName' => $moduleName)
|
||||
);
|
||||
} catch (DocException $e) {
|
||||
throw new Zend_Controller_Action_Exception($e->getMessage(), 404);
|
||||
}
|
||||
$this->view->moduleName = $moduleName;
|
||||
}
|
||||
|
||||
/**
|
||||
* View a chapter of a module's documentation
|
||||
*
|
||||
* @throws Zend_Controller_Action_Exception If the required parameter 'chapterId' is missing or if an error in
|
||||
* the documentation module's library occurs
|
||||
* @see assertModuleEnabled()
|
||||
*/
|
||||
public function chapterAction()
|
||||
{
|
||||
$moduleName = $this->getParam('moduleName');
|
||||
$this->assertModuleEnabled($moduleName);
|
||||
$chapterId = $this->getParam('chapterId');
|
||||
if ($chapterId === null) {
|
||||
throw new Zend_Controller_Action_Exception(
|
||||
$this->translate('Missing parameter \'chapterId\''),
|
||||
404
|
||||
);
|
||||
}
|
||||
$moduleManager = Icinga::app()->getModuleManager();
|
||||
try {
|
||||
$this->renderChapter(
|
||||
$moduleManager->getModuleDir($moduleName, '/doc'),
|
||||
$chapterId,
|
||||
$this->_helper->url->url(array('moduleName' => $moduleName), 'doc/module/toc'),
|
||||
'doc/module/chapter',
|
||||
array('moduleName' => $moduleName)
|
||||
);
|
||||
} catch (DocException $e) {
|
||||
throw new Zend_Controller_Action_Exception($e->getMessage(), 404);
|
||||
}
|
||||
$this->view->moduleName = $moduleName;
|
||||
}
|
||||
|
||||
/**
|
||||
* View a module's documentation as PDF
|
||||
*
|
||||
* @see assertModuleEnabled()
|
||||
*/
|
||||
public function pdfAction()
|
||||
{
|
||||
$moduleName = $this->getParam('moduleName');
|
||||
$this->assertModuleEnabled($moduleName);
|
||||
$moduleManager = Icinga::app()->getModuleManager();
|
||||
$this->renderPdf(
|
||||
$moduleManager->getModuleDir($moduleName, '/doc'),
|
||||
$moduleName,
|
||||
'doc/module/chapter',
|
||||
array('moduleName' => $moduleName)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<div class="chapter">
|
||||
<?= $sectionRenderer->render($this, $this->getHelper('Url')); ?>
|
||||
</div>
|
|
@ -1,5 +1,6 @@
|
|||
<h1>Icinga 2 Documentation</h1>
|
||||
<?= $this->partial('module/view.phtml', 'doc', array(
|
||||
'toc' => $toc,
|
||||
'html' => $html
|
||||
)); ?>
|
||||
<div class="controls"></div>
|
||||
<h1><?= $this->translate('Available documentations'); ?></h1>
|
||||
<ul>
|
||||
<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>
|
||||
|
|
|
@ -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>
|
|
@ -1,6 +1,10 @@
|
|||
<h1>Module documentations</h1>
|
||||
<h1><?= $this->translate('Module documentations'); ?></h1>
|
||||
<ul>
|
||||
<?php foreach ($enabledModules as $module): ?>
|
||||
<li><a href="<?= $this->href('doc/module/view', array('name' => $module)); ?>"><?= $module ?></a></li>
|
||||
<?php endforeach ?>
|
||||
<?php foreach ($modules as $module): ?>
|
||||
<li>
|
||||
<a href="<?= $this->getHelper('Url')->url(array('moduleName' => $module), 'doc/module/toc', false, false); ?>">
|
||||
<?= $module ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
<?php if ($html === null): ?>
|
||||
<p>No documentation available.</p>
|
||||
<?php else: ?>
|
||||
<div class="content">
|
||||
<?= $html ?>
|
||||
</div>
|
||||
<?php endif ?>
|
|
@ -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>
|
|
@ -0,0 +1,6 @@
|
|||
<div class="controls">
|
||||
<h1><?= $title ?></h1>
|
||||
</div>
|
||||
<div class="content toc">
|
||||
<?= $tocRenderer->render($this, $this->getHelper('Url')); ?>
|
||||
</div>
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
namespace Icinga\Module\Doc;
|
||||
|
||||
use \Exception;
|
||||
|
||||
class DocException extends Exception
|
||||
{
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -4,146 +4,65 @@
|
|||
|
||||
namespace Icinga\Module\Doc;
|
||||
|
||||
use RecursiveIteratorIterator;
|
||||
use RecursiveDirectoryIterator;
|
||||
use Parsedown;
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Web\Menu;
|
||||
use Icinga\Web\Url;
|
||||
|
||||
require_once 'IcingaVendor/Parsedown/Parsedown.php';
|
||||
use SplDoublyLinkedList;
|
||||
use Icinga\Exception\NotReadableError;
|
||||
use Icinga\Module\Doc\Exception\DocEmptyException;
|
||||
use Icinga\Module\Doc\Exception\DocException;
|
||||
|
||||
/**
|
||||
* Parser for documentation written in Markdown
|
||||
*/
|
||||
class DocParser
|
||||
{
|
||||
protected $dir;
|
||||
|
||||
protected $module;
|
||||
/**
|
||||
* Path to the documentation
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* Create a new documentation parser for the given module or the application
|
||||
* Iterator over documentation files
|
||||
*
|
||||
* @param string $module
|
||||
*
|
||||
* @throws DocException
|
||||
* @var DocIterator
|
||||
*/
|
||||
public function __construct($module = null)
|
||||
{
|
||||
if ($module === null) {
|
||||
$dir = Icinga::app()->getApplicationDir('/../doc');
|
||||
} else {
|
||||
$mm = Icinga::app()->getModuleManager();
|
||||
if (!$mm->hasInstalled($module)) {
|
||||
throw new DocException('Module is not installed');
|
||||
}
|
||||
if (!$mm->hasEnabled($module)) {
|
||||
throw new DocException('Module is not enabled');
|
||||
}
|
||||
$dir = $mm->getModuleDir($module, '/doc');
|
||||
}
|
||||
if (!is_dir($dir)) {
|
||||
throw new DocException('Doc directory does not exist');
|
||||
}
|
||||
$this->dir = $dir;
|
||||
$this->module = $module;
|
||||
}
|
||||
protected $docIterator;
|
||||
|
||||
/**
|
||||
* Retrieve table of contents and HTML converted from markdown files sorted by filename
|
||||
* Create a new documentation parser for the given path
|
||||
*
|
||||
* @return array
|
||||
* @throws DocException
|
||||
* @param string $path Path to the documentation
|
||||
*
|
||||
* @throws DocException If the documentation directory does not exist
|
||||
* @throws NotReadableError If the documentation directory is not readable
|
||||
* @throws DocEmptyException If the documentation directory is empty
|
||||
*/
|
||||
public function getDocumentation()
|
||||
public function __construct($path)
|
||||
{
|
||||
$iter = new RecursiveIteratorIterator(
|
||||
new MarkdownFileIterator(
|
||||
new RecursiveDirectoryIterator($this->dir)
|
||||
)
|
||||
);
|
||||
$fileInfos = iterator_to_array($iter);
|
||||
natcasesort($fileInfos);
|
||||
$cat = array();
|
||||
$toc = array((object) array(
|
||||
'level' => 0,
|
||||
'item' => new Menu('doc')
|
||||
));
|
||||
$itemPriority = 1;
|
||||
foreach ($fileInfos as $fileInfo) {
|
||||
try {
|
||||
$fileObject = $fileInfo->openFile();
|
||||
} catch (RuntimeException $e) {
|
||||
throw new DocException($e->getMessage());
|
||||
}
|
||||
if ($fileObject->flock(LOCK_SH) === false) {
|
||||
throw new DocException('Couldn\'t get the lock');
|
||||
}
|
||||
$line = null;
|
||||
while (!$fileObject->eof()) {
|
||||
// Save last line for setext-style headers
|
||||
$lastLine = $line;
|
||||
$line = $fileObject->fgets();
|
||||
$header = $this->extractHeader($line, $lastLine);
|
||||
if ($header !== null) {
|
||||
list($header, $level) = $header;
|
||||
$id = $this->extractHeaderId($header);
|
||||
$attribs = array();
|
||||
$this->reduceToc($toc, $level);
|
||||
if ($id === null) {
|
||||
$path = array();
|
||||
foreach (array_slice($toc, 1) as $entry) {
|
||||
$path[] = $entry->item->getTitle();
|
||||
}
|
||||
$path[] = $header;
|
||||
$id = implode('-', $path);
|
||||
$attribs['rel'] = 'nofollow';
|
||||
}
|
||||
$id = urlencode(str_replace('.', '.', strip_tags($id)));
|
||||
$item = end($toc)->item->addChild(
|
||||
$id,
|
||||
array(
|
||||
'url' => Url::fromPath(
|
||||
'doc/module/view',
|
||||
array(
|
||||
'name' => $this->module
|
||||
)
|
||||
)->setAnchor($id)->getRelativeUrl(),
|
||||
'title' => htmlspecialchars($header),
|
||||
'priority' => $itemPriority++,
|
||||
'attribs' => $attribs
|
||||
)
|
||||
);
|
||||
$toc[] = ((object) array(
|
||||
'level' => $level,
|
||||
'item' => $item
|
||||
));
|
||||
$line = '<a name="' . $id . '"></a>' . PHP_EOL . $line;
|
||||
}
|
||||
$cat[] = $line;
|
||||
}
|
||||
$fileObject->flock(LOCK_UN);
|
||||
if (! is_dir($path)) {
|
||||
throw new DocException(
|
||||
sprintf(mt('doc', 'Documentation directory \'%s\' does not exist'), $path)
|
||||
);
|
||||
}
|
||||
$html = Parsedown::instance()->parse(implode('', $cat));
|
||||
$html = preg_replace_callback(
|
||||
'#<pre><code class="language-php">(.*?)\</code></pre>#s',
|
||||
array($this, 'highlight'),
|
||||
$html
|
||||
);
|
||||
return array($html, $toc[0]->item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Syntax highlighting for PHP code
|
||||
*
|
||||
* @param $match
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function highlight($match)
|
||||
{
|
||||
return highlight_string(htmlspecialchars_decode($match[1]), true);
|
||||
if (! is_readable($path)) {
|
||||
throw new DocException(
|
||||
sprintf(mt('doc', 'Documentation directory \'%s\' is not readable'), $path)
|
||||
);
|
||||
}
|
||||
$docIterator = new DocIterator($path);
|
||||
if ($docIterator->count() === 0) {
|
||||
throw new DocEmptyException(
|
||||
sprintf(
|
||||
mt(
|
||||
'doc',
|
||||
'Documentation directory \'%s\' does not contain any non-empty Markdown file (\'.md\' suffix)'
|
||||
),
|
||||
$path
|
||||
)
|
||||
);
|
||||
}
|
||||
$this->path = $path;
|
||||
$this->docIterator = $docIterator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -156,28 +75,28 @@ class DocParser
|
|||
*/
|
||||
protected function extractHeader($line, $lastLine)
|
||||
{
|
||||
if (!$line) {
|
||||
if (! $line) {
|
||||
return null;
|
||||
}
|
||||
$header = null;
|
||||
if ($line &&
|
||||
$line[0] === '#' &&
|
||||
preg_match('/^#+/', $line, $match) === 1
|
||||
if ($line
|
||||
&& $line[0] === '#'
|
||||
&& preg_match('/^#+/', $line, $match) === 1
|
||||
) {
|
||||
// Atx-style
|
||||
// Atx
|
||||
$level = strlen($match[0]);
|
||||
$header = trim(substr($line, $level));
|
||||
if (!$header) {
|
||||
if (! $header) {
|
||||
return null;
|
||||
}
|
||||
} elseif (
|
||||
$line &&
|
||||
($line[0] === '=' || $line[0] === '-') &&
|
||||
preg_match('/^[=-]+\s*$/', $line, $match) === 1
|
||||
$line
|
||||
&& ($line[0] === '=' || $line[0] === '-')
|
||||
&& preg_match('/^[=-]+\s*$/', $line, $match) === 1
|
||||
) {
|
||||
// Setext
|
||||
$header = trim($lastLine);
|
||||
if (!$header) {
|
||||
if (! $header) {
|
||||
return null;
|
||||
}
|
||||
if ($match[0][0] === '=') {
|
||||
|
@ -189,36 +108,67 @@ class DocParser
|
|||
if ($header === null) {
|
||||
return null;
|
||||
}
|
||||
return array($header, $level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract header id in an a or a span tag
|
||||
*
|
||||
* @param string &$header
|
||||
*
|
||||
* @return id|null
|
||||
*/
|
||||
protected function extractHeaderId(&$header)
|
||||
{
|
||||
if ($header[0] === '<' &&
|
||||
preg_match('#(?:<(?P<tag>a|span) id="(?P<id>.+)"></(?P=tag)>)#u', $header, $match)
|
||||
if ($header[0] === '<'
|
||||
&& preg_match('#(?:<(?P<tag>a|span) (?:id|name)="(?P<id>.+)"></(?P=tag)>)\s*#u', $header, $match)
|
||||
) {
|
||||
$header = str_replace($match[0], '', $header);
|
||||
return $match['id'];
|
||||
$id = $match['id'];
|
||||
} else {
|
||||
$id = null;
|
||||
}
|
||||
return null;
|
||||
return array($header, $id, $level);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce the toc to the given level
|
||||
* Get the documentation tree
|
||||
*
|
||||
* @param array &$toc
|
||||
* @param int $level
|
||||
* @return DocTree
|
||||
*/
|
||||
protected function reduceToc(array &$toc, $level) {
|
||||
while (end($toc)->level >= $level) {
|
||||
array_pop($toc);
|
||||
public function getDocTree()
|
||||
{
|
||||
$tree = new DocTree();
|
||||
$stack = new SplDoublyLinkedList();
|
||||
foreach ($this->docIterator as $fileInfo) {
|
||||
/* @var $file \SplFileInfo */
|
||||
$file = $fileInfo->openFile();
|
||||
/* @var $file \SplFileObject */
|
||||
$lastLine = null;
|
||||
foreach ($file as $line) {
|
||||
$header = $this->extractHeader($line, $lastLine);
|
||||
if ($header !== null) {
|
||||
list($title, $id, $level) = $header;
|
||||
while (! $stack->isEmpty() && $stack->top()->getLevel() >= $level) {
|
||||
$stack->pop();
|
||||
}
|
||||
if ($id === null) {
|
||||
$path = array();
|
||||
foreach ($stack as $section) {
|
||||
/* @var $section Section */
|
||||
$path[] = $section->getTitle();
|
||||
}
|
||||
$path[] = $title;
|
||||
$id = implode('-', $path);
|
||||
$noFollow = true;
|
||||
} else {
|
||||
$noFollow = false;
|
||||
}
|
||||
if ($stack->isEmpty()) {
|
||||
$chapterId = $id;
|
||||
$section = new Section($id, $title, $level, $noFollow, $chapterId);
|
||||
$tree->addRoot($section);
|
||||
} else {
|
||||
$chapterId = $stack->bottom()->getId();
|
||||
$section = new Section($id, $title, $level, $noFollow, $chapterId);
|
||||
$tree->addChild($section, $stack->top());
|
||||
}
|
||||
$stack->push($section);
|
||||
} else {
|
||||
$stack->top()->appendContent($line);
|
||||
}
|
||||
// Save last line for setext-style headers
|
||||
$lastLine = $line;
|
||||
}
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -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 {}
|
|
@ -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 {}
|
|
@ -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 {}
|
|
@ -4,15 +4,15 @@
|
|||
|
||||
namespace Icinga\Module\Doc;
|
||||
|
||||
use \RecursiveFilterIterator;
|
||||
use RecursiveFilterIterator;
|
||||
|
||||
/**
|
||||
* Iterator over Markdown files recursively
|
||||
* Recursive iterator over Markdown files
|
||||
*/
|
||||
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
|
||||
* through this filter
|
||||
|
@ -20,7 +20,8 @@ class MarkdownFileIterator extends RecursiveFilterIterator
|
|||
public function accept()
|
||||
{
|
||||
$current = $this->getInnerIterator()->current();
|
||||
if (!$current->isFile()) {
|
||||
/* @var $current \SplFileInfo */
|
||||
if (! $current->isFile()) {
|
||||
return false;
|
||||
}
|
||||
$filename = $current->getFilename();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
@ -94,7 +94,6 @@ class Monitoring_ListController extends Controller
|
|||
'host_last_check',
|
||||
'host_last_state_change' => $stateChangeColumn,
|
||||
'host_notifications_enabled',
|
||||
// 'host_unhandled_service_count',
|
||||
'host_unhandled_services',
|
||||
'host_action_url',
|
||||
'host_notes_url',
|
||||
|
|
|
@ -24,7 +24,6 @@ class Monitoring_MultiController extends Controller
|
|||
array(
|
||||
'host_name',
|
||||
'host_in_downtime',
|
||||
'host_unhandled_service_count',
|
||||
'host_passive_checks_enabled',
|
||||
'host_obsessing',
|
||||
'host_state',
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 451 B |
|
@ -95,6 +95,13 @@ if (!Function.prototype.bind) {
|
|||
|
||||
'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 */
|
||||
$.fn.classes = function (callback) {
|
||||
|
||||
|
|
|
@ -474,12 +474,20 @@
|
|||
return true;
|
||||
}
|
||||
|
||||
// Ignore clicks on multiselect table inner links while key pressed
|
||||
if ((event.ctrlKey || event.metaKey || event.shiftKey) &&
|
||||
! $a.is('tr[href]') && $a.closest('table.multiselect').length > 0 &&
|
||||
$a.closest('tr[href]').length > 0)
|
||||
{
|
||||
return self.rowSelected.call($a.closest('tr[href]'), event);
|
||||
// Special checks for link clicks in multiselect rows
|
||||
if (! $a.is('tr[href]') && $a.closest('tr[href]').length > 0 && $a.closest('table.multiselect').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);
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -493,6 +501,12 @@
|
|||
return false;
|
||||
}
|
||||
|
||||
// Ignore form elements in action rows
|
||||
if ($(event.target).is('input') || $(event.target).is('button')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// ignore multiselect table row clicks
|
||||
if ($a.is('tr') && $a.closest('table.multiselect').length > 0) {
|
||||
return;
|
||||
|
|
|
@ -162,11 +162,18 @@
|
|||
|
||||
parts = document.location.hash.split(/#!/);
|
||||
|
||||
if ($('#col2').data('icingaUrl') !== main) {
|
||||
icinga.loader.loadUrl(
|
||||
parts[1],
|
||||
$('#col2')
|
||||
).historyTriggered = true;
|
||||
if ($('#layout > #login').length) {
|
||||
// We are on the login page!
|
||||
$('#login form #redirect').val(
|
||||
$('#login form #redirect').val() + '#!' + parts[1]
|
||||
);
|
||||
} else {
|
||||
if ($('#col2').data('icingaUrl') !== main) {
|
||||
icinga.loader.loadUrl(
|
||||
parts[1],
|
||||
$('#col2')
|
||||
).historyTriggered = true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Replace with dynamic columns
|
||||
|
|
|
@ -250,14 +250,24 @@
|
|||
var icinga = this.icinga;
|
||||
var redirect = req.getResponseHeader('X-Icinga-Redirect');
|
||||
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(
|
||||
'Got redirect for ', req.$target, ', URL was ' + redirect
|
||||
);
|
||||
redirect = decodeURIComponent(redirect);
|
||||
|
||||
if (req.getResponseHeader('X-Icinga-Rerender-Layout')) {
|
||||
var parts = redirect.split(/#!/);
|
||||
redirect = parts.shift();
|
||||
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 {
|
||||
if (req.$target.attr('id') === 'col2') { // TODO: multicol
|
||||
if ($('#col1').data('icingaUrl') === redirect) {
|
||||
|
@ -556,6 +566,17 @@
|
|||
req.$target.data('lastUpdate', (new Date()).getTime());
|
||||
delete this.requests[req.$target.attr('id')];
|
||||
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();
|
||||
},
|
||||
|
||||
|
|
|
@ -100,7 +100,7 @@
|
|||
icinga.logger.info('Reloading CSS');
|
||||
$('link').each(function() {
|
||||
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(
|
||||
'href',
|
||||
icinga.utils.addUrlParams(
|
||||
|
|
|
@ -19,8 +19,8 @@ class SlidingwithborderTest extends BaseTestCase
|
|||
|
||||
$pages = $scrollingStyle->getPages($paginator);
|
||||
$this->assertInternalType('array', $pages);
|
||||
$this->assertCount(13, $pages);
|
||||
$this->assertEquals('...', $pages[11]);
|
||||
$this->assertCount(10, $pages);
|
||||
$this->assertEquals('...', $pages[8]);
|
||||
}
|
||||
|
||||
public function testGetPages3()
|
||||
|
@ -31,9 +31,9 @@ class SlidingwithborderTest extends BaseTestCase
|
|||
|
||||
$pages = $scrollingStyle->getPages($paginator);
|
||||
$this->assertInternalType('array', $pages);
|
||||
$this->assertCount(16, $pages);
|
||||
$this->assertCount(10, $pages);
|
||||
$this->assertEquals('...', $pages[3]);
|
||||
$this->assertEquals('...', $pages[14]);
|
||||
$this->assertEquals('...', $pages[12]);
|
||||
}
|
||||
|
||||
protected function getPaginatorAdapter()
|
||||
|
|
|
@ -21,7 +21,7 @@ class UrlTest extends BaseTestCase
|
|||
$url = Url::fromRequest();
|
||||
$this->assertEquals(
|
||||
'/path/to/my/test/url.html?param1=value1&param2=value2',
|
||||
$url->getAbsoluteUrl(),
|
||||
$url->getAbsoluteUrl('&'),
|
||||
'Url::fromRequest does not reassemble the correct url from the global request'
|
||||
);
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ class UrlTest extends BaseTestCase
|
|||
*/
|
||||
public function testWhetherFromPathProperlyRecognizesAndDecodesQueryParameters()
|
||||
{
|
||||
$url = Url::fromPath('/my/test/url.html?param1=%25arg1¶m2=arg+2'
|
||||
$url = Url::fromPath('/my/test/url.html?param1=%25arg1¶m2=arg%202'
|
||||
. '¶m3[]=1¶m3[]=2¶m3[]=3¶m4[key1]=val1¶m4[key2]=val2');
|
||||
|
||||
$this->assertEquals(
|
||||
|
|
Loading…
Reference in New Issue