Merge branch 'feature/hook-and-topbar-5597'

resolves 
This commit is contained in:
Marius Hein 2014-02-11 17:14:44 +01:00
commit ddafae6271
36 changed files with 864 additions and 298 deletions
application
doc
library/Icinga
modules
test/php
application/forms/Config
library/Icinga/Application

View File

@ -0,0 +1,46 @@
<?php
// @codingStandardsIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
use Icinga\Web\Controller\ActionController;
use Icinga\Web\Hook;
use Icinga\Web\Menu;
use Icinga\Web\Url;
/**
* Create complex layout parts
*/
class LayoutController extends ActionController
{
/**
* Render the menu
*/
public function menuAction()
{
$this->view->url = Url::fromRequest()->getRelativeUrl();
$this->view->items = Menu::fromConfig()->getChildren();
$this->view->sub = false;
}
/**
* Render the top bar
*/
public function topbarAction()
{
$topbarHtmlParts = array();
/** @var Hook\Layout\TopBar $hook */
$hook = null;
foreach (Hook::all(Hook::TARGET_LAYOUT_TOPBAR) as $hook) {
$topbarHtmlParts[] = $hook->getHtml($this->getRequest(), $this->view);
}
$this->view->topbarHtmlParts = $topbarHtmlParts;
$this->renderScript('parts/topbar.phtml');
}
}
// @codingStandardsIgnoreEnd

View File

@ -1,9 +1,13 @@
<?php echo $this->render('parts/topbar.phtml') ?>
<div class="row">
<?= $this->action('topbar', 'layout'); ?>
<div class="row">
<!-- Only required for left/right tabs -->
<div class="col-sm-12 col-xs-12 col-md-2 col-lg-2" id="menu">
<?php echo $this->render('parts/navigation.phtml') ?>
<div class="col-sm-12 col-xs-12 col-md-2 col-lg-2">
<?= $this->partial('layout/menu.phtml', 'default', array(
'url' => \Icinga\Web\Url::fromRequest()->getRelativeUrl(),
'items' => \Icinga\Web\Menu::fromConfig()->getChildren(),
'sub' => false
)); ?>
</div>
<div class="col-sm-12 col-xs-12 col-md-10 col-lg-10">

View File

@ -1,28 +0,0 @@
<ul>
<?php
foreach ($this->items as $item) {
printf(
' <li%s><a href="%s">%s%s</a>',
$this->href($this->url) === $this->href($item->getUrl()) ? ' class="active"' : '',
$item->getUrl() ? $this->href($item->getUrl()) : '#',
$item->getIcon() ? $this->img(
$item->getIcon(),
array('height' => '16px', 'width' => '16px')
) . ' ' : '',
$item->getTitle()
);
if ($item->hasChildren()) {
echo $this->partial(
'parts/menu.phtml',
array('items' => $item->getChildren(), 'url' => $this->url)
);
}
echo "</li>\n";
}
?>
</ul>

View File

@ -1,7 +1,5 @@
<?php
// determine current key
$url = Icinga\Web\Url::fromRequest()->getRelativeUrl();
$menu = Icinga\Web\Menu::fromConfig();
if ($this->auth()->isAuthenticated()) {
echo $this->partial(

View File

@ -1,14 +1,15 @@
<?php
use Icinga\Web\Topbar;
?>
<nav class="navbar-fixed-top" id="icingatopbar" role="navigation">
<div class="navbar-header">
<a href="<?= $this->baseUrl('/') ?>" class="navbar-brand icinga-logo">Icinga</a>
</div>
<?php foreach (Topbar::getPartials() as $partial) {
echo $this->partial($partial['viewScriptName'], $partial['moduleName'], $partial['data']);
} ?>
<?php if (count($this->topbarHtmlParts)): ?>
<ul class="nav navbar-nav">
<?php foreach ($this->topbarHtmlParts as $part): ?>
<li><?= $part; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<?php if ($this->auth()->isAuthenticated()): ?>
<ul class="nav navbar-nav pull-right" >

View File

@ -0,0 +1,39 @@
<?= $sub ? '<ul>' : '<ul class="nav nav-stacked">' ?>
<?php foreach ($items as $item): ?>
<?php
$itemClass = '';
if ($sub) {
$itemClass .= 'submenu ';
}
if ($this->href($url) === $this->href($item->getUrl())) {
$itemClass .= 'active ';
}
?>
<li <?php if (!empty($itemClass)): ?>class="<?= $itemClass ?>"<?php endif ?>>
<?php if($item->getUrl()): ?>
<a href="<?= $this->href($item->getUrl()); ?>" <?php foreach($item->getAttribs() as $attrib => $value): ?> <?= $attrib ?>="<?= $value ?>"<?php endforeach?>>
<?php endif; ?>
<?php
if ($icon = $item->getIcon()) {
echo $this->img($icon, array('height' => 16, 'width' => 16));
}
?>
<?php if ($iconClass = $item->getIconClass()): ?>
<i class="<?= $iconClass ?>"></i>
<?php endif ?>
<?= $item->getTitle();?>
<?php if($item->getUrl()): ?>
</a>
<?php endif; ?>
<?php
if($item->hasChildren()) {
echo $this->partial(
'layout/menu.phtml',
'default',
array('items' => $item->getChildren(), 'sub' => true, 'url' => $this->url)
);
}
?>
</li>
<?php endforeach ?>
</ul>

View File

@ -8,21 +8,23 @@ INI files as source it enables you to store configuration in a familiar format.
defines some configuration files for its own purposes. Please note that both modules and framework
keep their main configuration in the INI file called config.ini. Here's some example code:
<?php
use \Icinga\Application\Config as IcingaConfig;
```php
<?php
use \Icinga\Application\Config as IcingaConfig;
// Retrieve the default timezone using 'Europe/Berlin' in case it is not set
IcingaConfig::app()->global->get('defaultTimezone', 'Europe/Berlin');
// Retrieve the default timezone using 'Europe/Berlin' in case it is not set
IcingaConfig::app()->global->get('defaultTimezone', 'Europe/Berlin');
// If you don't pass a configuration name to IcingaConfig::app it tries to load values from the
// application's config.ini. For using other files you have to pass this parameter though.
// The following example loads a section from the application's authentication.ini:
IcingaConfig::app('authentication')->get('ldap-authentication');
// If you don't pass a configuration name to IcingaConfig::app it tries to load values from the
// application's config.ini. For using other files you have to pass this parameter though.
// The following example loads a section from the application's authentication.ini:
IcingaConfig::app('authentication')->get('ldap-authentication');
// If you don't pass a configuration name to IcingaConfig::module it tries to load values from
// the module's config.ini. For using other files you have to pass this parameter though.
// The following example loads values from the example module's extra.ini:
IcingaConfig::module('example', 'extra')->logging->get('enabled', true);
// If you don't pass a configuration name to IcingaConfig::module it tries to load values from
// the module's config.ini. For using other files you have to pass this parameter though.
// The following example loads values from the example module's extra.ini:
IcingaConfig::module('example', 'extra')->logging->get('enabled', true);
```
## Reload from disk

View File

@ -29,7 +29,7 @@ A component name consists of two parts: the namespace and the name of the compon
is named exactly like its JavaScript file, while the namespace is the name of the Icinga2-Web module that contains
the component. Each Icinga2-Web module can contain its own components in the folder *public/js*.
<module>/<component>
<module>/<component>
NOTE: The namespace used for modules defined in the Icinga2-Web core application is "app". In opposition to
@ -45,10 +45,10 @@ The full name for the component *modules/monitoring/public/js/someComponent.js*
"monitoring/someComponent"
The full component name for the component *public/js/icinga/components/datetime.js* in the Icinga2-Web
core application would:
The full component name for the component *public/js/icinga/components/datetime.js* in the Icinga2-Web core application
would:
"app/datetime"
"app/datetime"
## Creating a component
@ -95,7 +95,7 @@ with right time-format available.
### Component ids
When an ID is assigned to the HTML element, it will be used by the component loader to reference this
component. Otherwise an ID in the form "icinga-component-<ID>" will be created and the ID attribute in the
component. Otherwise an ID in the form "icinga-component-&lt;ID&gt;" will be created and the ID attribute in the
HTML Element will be updated accordingly.

View File

@ -40,4 +40,4 @@ By default, the config/dashboard/dashboard.ini is used for storing dashboards in
url = "/monitoring/show/host/host1" ; ...and define it's url
[dashboards1]: res/Dashboard.png
[dashboards1]: res/Dashboard.png

View File

@ -73,15 +73,15 @@ code from special directories:
<table>
<tr>
<th>Class name</th>
<th>File path</tg>
<th>File path</th>
</tr>
<tr>
<td>\Icinga\Form\Test\MyForm</td>
</td>application/forms/Test/MyForm.php</td>
<td>application/forms/Test/MyForm.php</td>
</tr>
<tr>
<td>\MyModule\Form\Test</td>
</td>modules/forms/Test.php</td>
<td>modules/forms/Test.php</td>
</tr>
</table>
@ -219,4 +219,4 @@ request dummy which can be passed to your form.
* [Zend API documentation](http://framework.zend.com/apidoc/1.10/_Form.html#Zend_Form)
[form1]: res/Form.png
[form1]: res/Form.png

View File

@ -101,7 +101,7 @@ is a timestamp. The above DateTimePicker utilizes this validator and should be u
### ConditionalHidden Decorator
The `Icinga\Web\Form\Decorator\ConditionalHidden` allows you to hide a form element with the 'conditional' attribute for
users that don't have JavaScript enabled (the form is rendered in a \<noscript> tag when conditional is 1). Users with
users that don't have JavaScript enabled (the form is rendered in a &lt;noscript&gt; tag when conditional is 1). Users with
javascript won't see the elements, users with javascript will see it. This is useful in a lot of cases to allow icingaweb
to be fully functional without JavaScript: Forms can show only sensible forms for most users (and, for example hide the
debug log filepath input when debugging is disabled) and automatically reload the form as soon as the forms should be
@ -133,6 +133,6 @@ the 'helptext' property in your form elements.
### BootstrapForm Decorator
`Icinga\Web\Form\Decorator\BoostrapForm` is the decorator we use for our forms.
It causes the forms to be rendered in a bootstrap friendly manner instead of the \<dd> \<dt> encapsulated way Zend normally
It causes the forms to be rendered in a bootstrap friendly manner instead of the &lt;dd&gt; &lt;dt&gt; encapsulated way Zend normally
renders the forms. You usually don't have to work with this decorator as our Form implementation automatically uses it,
but it's always good to know why forms look how they look.

View File

@ -72,7 +72,7 @@ create all database tables. You will find the installation guides for the differ
> **Note**
>
> RPM packages install the schema into /usr/share/doc/icingaweb-<version>/schema
> RPM packages install the schema into /usr/share/doc/icingaweb-&lt;version&gt;/schema
bash$ mysql -u root -p icingaweb < etc/schema/accounts.mysql.sql
@ -105,7 +105,7 @@ And restart your database ('service postgresql restart' or '/etc/init.d/postgres
> **Note**
>
> RPM packages install the schema into /usr/share/doc/icingaweb-<version>/schema
> RPM packages install the schema into /usr/share/doc/icingaweb-&lt;version&gt;/schema
bash$ psql -U icingaweb -a -f etc/schema/accounts.pgsql.sql

View File

@ -244,18 +244,22 @@ class Manager
)
);
}
clearstatcache(true);
$target = $this->installedBaseDirs[$name];
$link = $this->enableDir . '/' . $name;
if (!is_writable($this->enableDir)) {
throw new SystemPermissionException(
'Can not enable module "' . $name . '". '
. 'Insufficient system permissions for enabling modules.'
);
}
if (file_exists($link) && is_link($link)) {
return $this;
}
if (!@symlink($target, $link)) {
$error = error_get_last();
if (strstr($error["message"], "File exists") === false) {
@ -266,7 +270,12 @@ class Manager
);
}
}
$this->enabledDirs[$name] = $link;
$this->loadModule($name);
$this->getModule($name)->launchRegisterScript();
return $this;
}

View File

@ -101,6 +101,13 @@ class Module
*/
private $runScript;
/**
* Module initialization script
*
* @var string
*/
private $registerScript;
/**
* Module configuration script
*
@ -145,17 +152,18 @@ class Module
*/
public function __construct(ApplicationBootstrap $app, $name, $basedir)
{
$this->app = $app;
$this->name = $name;
$this->basedir = $basedir;
$this->cssdir = $basedir. '/public/css';
$this->libdir = $basedir. '/library';
$this->configdir = $basedir. '/config';
$this->localedir = $basedir. '/application/locale';
$this->formdir = $basedir. '/application/forms';
$this->controllerdir = $basedir. '/application/controllers';
$this->runScript = $basedir. '/register.php';
$this->configScript = $basedir. '/configuration.php';
$this->app = $app;
$this->name = $name;
$this->basedir = $basedir;
$this->cssdir = $basedir . '/public/css';
$this->libdir = $basedir . '/library';
$this->configdir = $basedir . '/config';
$this->localedir = $basedir . '/application/locale';
$this->formdir = $basedir . '/application/forms';
$this->controllerdir = $basedir . '/application/controllers';
$this->runScript = $basedir . '/run.php';
$this->registerScript = $basedir . '/register.php';
$this->configScript = $basedir . '/configuration.php';
}
/**
@ -503,6 +511,16 @@ class Module
return $this;
}
/**
* Run module registration script
*
* @return self
*/
public function launchRegisterScript()
{
return $this->includeScript($this->registerScript);
}
/**
* Run module bootstrap script
*
@ -510,13 +528,26 @@ class Module
*/
protected function launchRunScript()
{
if (file_exists($this->runScript)
&& is_readable($this->runScript)) {
include($this->runScript);
return $this->includeScript($this->runScript);
}
/**
* Include a php script if it is readable
*
* @param string $file File to include
*
* @return self
*/
protected function includeScript($file)
{
if (is_readable($file) === true) {
include($file);
}
return $this;
}
/**
* Run module config script
*/

View File

@ -266,8 +266,6 @@ class ActionController extends Zend_Controller_Action
return call_user_func_array(array($this, $deprecatedMethod), $params);
}
parent::__call($name, $params);
return null;
return parent::__call($name, $params);
}
}

View File

@ -45,6 +45,11 @@ use \Icinga\Exception\ProgrammingError;
*/
class Hook
{
/**
* Hook name
*/
const TARGET_LAYOUT_TOPBAR = 'Layout\\TopBar';
/**
* Our hook name registry
*

View File

@ -27,23 +27,23 @@
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Web;
namespace Icinga\Web\Hook\Layout;
class Topbar
use \Icinga\Web\Request;
use \Zend_View;
/**
* Hook to extend topbar items
*/
interface TopBar
{
private static $partials = array();
public static function addPartial($viewScriptName, $moduleName, $data)
{
self::$partials[] = array(
'viewScriptName' => $viewScriptName,
'moduleName' => $moduleName,
'data' => $data
);
}
public static function getPartials()
{
return self::$partials;
}
/**
* Function to generate top bar content
*
* @param Request $request
* @param Zend_View $view
*
* @return string
*/
public function getHtml($request, $view);
}

View File

@ -61,6 +61,8 @@ class MenuItem
*/
private $children = array();
private $attribs = array();
/**
* Create a new MenuItem
@ -308,6 +310,28 @@ class MenuItem
throw new ProgrammingError(sprintf('Trying to get invalid Menu child "%s"', $id));
}
/**
* Set HTML a tag attributes
*
* @param array $attribs
*
* @return self
*/
public function setAttribs(array $attribs)
{
$this->attribs = $attribs;
return $this;
}
/**
* Get HTML a tag attributes
*
* @return array
*/
public function getAttribs()
{
return $this->attribs;
}
/**
* Compare children based on priority and title

View File

@ -358,11 +358,14 @@ class Url
/**
* Set the url anchor-part
*
* @param $anchor The site's anchor string without the '#'
* @param $anchor The site's anchor string without the '#'
*
* @return self
*/
public function setAnchor($anchor)
{
$this->anchor = '#' . $anchor;
return $this;
}
/**

View File

@ -0,0 +1,18 @@
<?php
// @codingStandardsIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
use Icinga\Module\Doc\Controller as DocController;
class Doc_IndexController extends DocController
{
/**
* Display the application's documentation
*/
public function indexAction()
{
$this->populateView();
}
}
// @codingStandardsIgnoreEnd

View File

@ -0,0 +1,47 @@
<?php
// @codingStandardsIgnoreStart
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
use Icinga\Application\Icinga;
use Icinga\Module\Doc\Controller as DocController;
class Doc_ModuleController extends DocController
{
/**
* Display module documentations index
*/
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);
}
$this->_helper->redirector->gotoSimpleAndExit('view', null, null, array('name' => $moduleName));
}
}
// @codingStandardsIgnoreEnd

View File

@ -0,0 +1,5 @@
<h1>Icinga 2 Documentation</h1>
<?= $this->partial('module/view.phtml', 'doc', array(
'toc' => $toc,
'html' => $html
)); ?>

View File

@ -0,0 +1,6 @@
<h1>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 ?>
</ul>

View File

@ -0,0 +1,21 @@
<?php if ($html === null): ?>
<p>No documentation available.</p>
<?php else: ?>
<div class="row">
<div class="col-sm-12 col-xs-12 col-md-2 col-lg-2">
<?= $this->partial(
'layout/menu.phtml',
'default',
array(
'items' => $toc->getChildren(),
'sub' => false,
'url' => ''
)
);
?>
</div>
<div class="col-sm-12 col-xs-12 col-md-10 col-lg-10">
<?= $html ?>
</div>
</div>
<?php endif ?>

View File

@ -0,0 +1,23 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Doc;
use Icinga\Web\Controller\ActionController;
class Controller extends ActionController
{
/**
* 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,11 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Doc;
use \Exception;
class DocException extends Exception
{
}

View File

@ -0,0 +1,224 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}
namespace Icinga\Module\Doc;
require_once 'vendor/Parsedown/Parsedown.php';
use \RecursiveIteratorIterator;
use \RecursiveDirectoryIterator;
use \Parsedown;
use Icinga\Application\Icinga;
use Icinga\Web\Menu;
use Icinga\Web\Url;
/**
* Parser for documentation written in Markdown
*/
class DocParser
{
protected $dir;
protected $module;
/**
* Create a new documentation parser for the given module or the application
*
* @param string $module
*
* @throws DocException
*/
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;
}
/**
* Retrieve table of contents and HTML converted from markdown files sorted by filename
*
* @return array
* @throws DocException
*/
public function getDocumentation()
{
$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('.', '&#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 = '<span id="' . $id . '"></span>' . PHP_EOL . $line;
}
$cat[] = $line;
}
$fileObject->flock(LOCK_UN);
}
$html = Parsedown::instance()->parse(implode('', $cat));
$html = preg_replace_callback(
'#<pre><code class="language-php">(.*?)\</code></pre>#s',
array($this, 'highlight'),
$html
);
return array($html, $toc[0]->item);
}
/**
* Syntax highlighting for PHP code
*
* @param $match
*
* @return string
*/
protected function highlight($match)
{
return highlight_string(htmlspecialchars_decode($match[1]), true);
}
/**
* Extract atx- or setext-style headers from the given lines
*
* @param string $line
* @param string $lastLine
*
* @return array|null An array containing the header and the header level or null if there's nothing to extract
*/
protected function extractHeader($line, $lastLine)
{
if (!$line) {
return null;
}
$header = null;
if ($line &&
$line[0] === '#' &&
preg_match('/^#+/', $line, $match) === 1
) {
// Atx-style
$level = strlen($match[0]);
$header = trim(substr($line, $level));
if (!$header) {
return null;
}
} elseif (
$line &&
($line[0] === '=' || $line[0] === '-') &&
preg_match('/^[=-]+\s*$/', $line, $match) === 1
) {
// Setext
$header = trim($lastLine);
if (!$header) {
return null;
}
if ($match[0][0] === '=') {
$level = 1;
} else {
$level = 2;
}
}
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)
) {
$header = str_replace($match[0], '', $header);
return $match['id'];
}
return null;
}
/**
* Reduce the toc to the given level
*
* @param array &$toc
* @param int $level
*/
protected function reduceToc(array &$toc, $level) {
while (end($toc)->level >= $level) {
array_pop($toc);
}
}
}

View File

@ -0,0 +1,30 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}
namespace Icinga\Module\Doc;
use \RecursiveFilterIterator;
/**
* Iterator over Markdown files recursively
*/
class MarkdownFileIterator extends RecursiveFilterIterator
{
/**
* Accept files with .md suffix
*
* @return bool Whether the current element of the iterator is acceptable
* through this filter
*/
public function accept()
{
$current = $this->getInnerIterator()->current();
if (!$current->isFile()) {
return false;
}
$filename = $current->getFilename();
$sfx = substr($filename, -3);
return $sfx === false ? false : strtolower($sfx) === '.md';
}
}

View File

@ -1,4 +1,4 @@
# The instance.ini configuration file
# <a id="instances"></a> The instance.ini configuration file
## Abstract

View File

@ -0,0 +1,87 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
/**
* This file is part of Icinga Web 2.
*
* Icinga Web 2 - Head for multiple monitoring backends.
* Copyright (C) 2013 Icinga Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* @copyright 2013 Icinga Development Team <info@icinga.org>
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
* @author Icinga Development Team <info@icinga.org>
*
*/
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Monitoring\Web\Hook;
use Icinga\Web\Hook\Layout\TopBar as IcingaTopBar;
use Icinga\Module\Monitoring\DataView\StatusSummary as StatusSummaryView;
use Icinga\Web\Request;
use Zend_View;
/**
* Render status summary into the topbar of icinga
*/
class TopBar implements IcingaTopBar
{
/**
* Function to generate top bar content
*
* @param Request $request
* @param Zend_View $view
*
* @return string
*/
public function getHtml($request, $view)
{
$hostSummary = StatusSummaryView::fromRequest(
$request,
array(
'hosts_up',
'hosts_unreachable_handled',
'hosts_unreachable_unhandled',
'hosts_down_handled',
'hosts_down_unhandled',
'hosts_pending'
)
)->getQuery()->fetchRow();
$serviceSummary = StatusSummaryView::fromRequest(
$request,
array(
'services_ok',
'services_critical_handled',
'services_critical_unhandled',
'services_warning_handled',
'services_warning_unhandled',
'services_unknown_handled',
'services_unknown_unhandled',
'services_pending'
)
)->getQuery()->fetchRow();
return $view->partial(
'layout/topbar.phtml',
'monitoring',
array(
'hostSummary' => $hostSummary,
'serviceSummary' => $serviceSummary
)
);
}
}

View File

@ -1,48 +1,6 @@
<?php
use Icinga\Authentication\Manager as AuthManager;
use Icinga\Application\Icinga;
use Icinga\Module\Monitoring\DataView\StatusSummary as StatusSummaryView;
use Icinga\Web\Topbar;
if (Icinga::app()->isCli()) {
return;
}
$request = Icinga::app()->getFrontController()->getRequest();
if (AuthManager::getInstance()->isAuthenticated()) {
$hostSummary = StatusSummaryView::fromRequest(
$request,
array(
'hosts_up',
'hosts_unreachable_handled',
'hosts_unreachable_unhandled',
'hosts_down_handled',
'hosts_down_unhandled',
'hosts_pending'
)
)->getQuery()->fetchRow();
$serviceSummary = StatusSummaryView::fromRequest(
$request,
array(
'services_ok',
'services_critical_handled',
'services_critical_unhandled',
'services_warning_handled',
'services_warning_unhandled',
'services_unknown_handled',
'services_unknown_unhandled',
'services_pending'
)
)->getQuery()->fetchRow();
Topbar::addPartial(
'topbar.phtml',
'monitoring',
array('hostSummary' => $hostSummary, 'serviceSummary' => $serviceSummary)
);
}
?>
/*
* register.php
*
* This file runs only on installation
*/

View File

@ -0,0 +1,23 @@
<?php
/*
* run.php
*
* This file runs every request to register runtime functionality
*
*/
use Icinga\Application\Icinga;
use Icinga\Authentication\Manager as AuthManager;
use Icinga\Web\Hook;
if (Icinga::app()->isCli()) {
return;
}
if (AuthManager::getInstance()->isAuthenticated()) {
Hook::register(
Hook::TARGET_LAYOUT_TOPBAR,
'monitoring-topbar',
'Icinga\\Module\\Monitoring\\Web\\Hook\\TopBar'
);
}

View File

@ -44,6 +44,7 @@ require_once BaseTestCase::$appDir . '/forms/Config/GeneralForm.php';
require_once BaseTestCase::$appDir . '/views/helpers/DateFormat.php';
require_once BaseTestCase::$libDir . '/Util/ConfigAwareFactory.php';
require_once BaseTestCase::$libDir . '/Util/DateTimeFactory.php';
require_once BaseTestCase::$libDir . '/Util/Translator.php';
// @codingStandardsIgnoreEnd
use \DateTimeZone;

View File

@ -29,188 +29,168 @@
namespace Tests\Icinga\Application;
require_once 'Zend/Log.php';
require_once 'Zend/Config.php';
require_once 'Zend/Log/Writer/Mock.php';
require_once 'Zend/Log/Writer/Null.php';
require_once 'Zend/Log/Filter/Priority.php';
// @codingStandardsIgnoreStart
require_once realpath(__DIR__ . '/../../../../../library/Icinga/Test/BaseTestCase.php');
// @codingStandardsIgnoreEnd
require_once realpath(__DIR__ . '/../../../../../library/Icinga/Application/Logger.php');
require_once realpath(__DIR__ . '/../../../../../library/Icinga/Exception/ConfigurationError.php');
use \Icinga\Application\Logger;
use \Zend_Config;
use Icinga\Application\Logger;
use Icinga\Test\BaseTestCase;
/**
* Test class for Logger
*
* @backupStaticAttributes enabled
**/
class LoggerTest extends \PHPUnit_Framework_TestCase
class LoggerTest extends BaseTestCase
{
private $timeZone;
private $tempDir;
protected function setUp()
private $logTarget;
private $debugTarget;
public function setUp()
{
date_default_timezone_set('GMT');
$this->tempDir = tempnam(sys_get_temp_dir(), 'icingaweb-log');
unlink($this->tempDir); // tempnam create the file automatically
if (!is_dir($this->tempDir)) {
mkdir($this->tempDir, 0755);
}
$this->debugTarget = $this->tempDir . '/debug.log';
$this->logTarget = $this->tempDir . '/application.log';
$loggingConfigurationArray = array(
'enable' => 1,
'type' => 'stream',
'verbose' => 1,
'target' => $this->logTarget,
'debug' => array(
'enable' => 1,
'type' => 'stream',
'target' => $this->debugTarget
)
);
$loggingConfiguration = new Zend_Config($loggingConfigurationArray);
Logger::reset();
}
public function testOverwrite()
{
$cfg1 = new \Zend_Config(
array(
'debug' => array('enable' => 0),
'type' => 'mock',
'target' => 'target2'
)
);
$cfg2 = new \Zend_Config(
array(
'debug' => array(
'enable' => 1,
'type'=>'mock',
'target'=>'target3'
),
'type' => 'mock',
'target' => 'target4'
)
);
$logger = new Logger($cfg1);
$writers = $logger->getWriters();
$this->assertEquals(1, count($writers));
$logger = new Logger($cfg1);
$writers2 = $logger->getWriters();
$this->assertEquals(1, count($writers));
$this->assertNotEquals($writers[0], $writers2[0]);
$logger = new Logger($cfg2);
$writers2 = $logger->getWriters();
$this->assertEquals(2, count($writers2));
}
public function testFormatMessage()
{
$message = Logger::formatMessage(array('Testmessage'));
$this->assertEquals('Testmessage', $message);
$message = Logger::formatMessage(array('Testmessage %s %s', 'test1', 'test2'));
$this->assertEquals('Testmessage test1 test2', $message);
$message = Logger::formatMessage(array('Testmessage %s', array('test1', 'test2')));
$this->assertEquals('Testmessage '.json_encode(array('test1', 'test2')), $message);
}
public function testLoggingOutput()
{
$cfg1 = new \Zend_Config(
array(
'debug' => array('enable' => 0),
'type' => 'mock',
'target' => 'target2'
)
);
$logger = Logger::create($cfg1);
$writers = $logger->getWriters();
$logger->warn('Warning');
$logger->error('Error');
$logger->info('Info');
$logger->debug('Debug');
$writer = $writers[0];
$this->assertEquals(2, count($writer->events));
$this->assertEquals($writer->events[0]['message'], 'Warning');
$this->assertEquals($writer->events[1]['message'], 'Error');
Logger::create($loggingConfiguration);
}
public function testLogQueuing()
public function tearDown()
{
$cfg1 = new \Zend_Config(
array(
'debug' => array('enable' => 0),
'type' => 'mock',
'target' => 'target2'
)
);
if (file_exists($this->debugTarget)) {
unlink($this->debugTarget);
}
Logger::warn('Warning');
Logger::error('Error');
Logger::info('Info');
Logger::debug('Debug');
if (file_exists($this->logTarget)) {
unlink($this->logTarget);
}
$logger = Logger::create($cfg1);
$writers = $logger->getWriters();
$writer = $writers[0];
$this->assertEquals(2, count($writer->events));
$this->assertEquals($writer->events[0]['message'], 'Warning');
$this->assertEquals($writer->events[1]['message'], 'Error');
rmdir($this->tempDir);
}
public function testDebugLogErrorCatching()
private function getLogData()
{
$cfg1 = new \Zend_Config(
array(
'debug' => array(
'enable' => 1,
'type' => 'Invalid',
'target' => '...'
),
'type' => 'mock',
'target' => 'target2'
)
);
$logger = Logger::create($cfg1);
$writers = $logger->getWriters();
$this->assertEquals(1, count($writers));
$this->assertEquals(1, count($writers[0]->events));
$this->assertEquals(
'Could not add log writer of type "Invalid". Type does not exist.',
$writers[0]->events[0]['message']
return array(
explode(PHP_EOL, file_get_contents($this->logTarget)),
explode(PHP_EOL, file_get_contents($this->debugTarget))
);
}
public function testNotLoggedMessagesQueue()
/**
* Test error messages
*/
public function testLoggingErrorMessages()
{
$cfg1 = new \Zend_Config(
array(
'debug' => array(
'enable' => 0,
'type' => 'Invalid',
'target' => '...'
),
'type' => 'invalid',
'target' => 'target2'
)
);
Logger::error('test-error-1');
Logger::error('test-error-2');
$logger = Logger::create($cfg1);
$this->assertFileExists($this->logTarget);
$this->assertFileExists($this->debugTarget);
list($main, $debug) = $this->getLogData();
$this->assertCount(3, $main);
$this->assertCount(3, $debug);
$this->assertContains(' ERR (3): test-error-1', $main[0]);
$this->assertContains(' ERR (3): test-error-2', $main[1]);
$this->assertContains(' ERR (3): test-error-1', $debug[0]);
$this->assertContains(' ERR (3): test-error-2', $debug[1]);
}
/**
* Test debug log and difference between error and debug messages
*/
public function testLoggingDebugMessages()
{
Logger::debug('test-debug-1');
Logger::error('test-error-1');
Logger::debug('test-debug-2');
$this->assertFileExists($this->logTarget);
$this->assertFileExists($this->debugTarget);
list($main, $debug) = $this->getLogData();
$this->assertCount(2, $main);
$this->assertCount(4, $debug);
$this->assertContains(' ERR (3): test-error-1', $main[0]);
$this->assertContains(' DEBUG (7): test-debug-1', $debug[0]);
$this->assertContains(' ERR (3): test-error-1', $debug[1]);
$this->assertContains(' DEBUG (7): test-debug-2', $debug[2]);
}
public function testLoggingQueueIfNoWriterAvailable()
{
Logger::reset();
Logger::error('test-error-1');
Logger::debug('test-debug-1');
Logger::error('test-error-2');
list($main, $debug) = $this->getLogData();
$this->assertCount(1, $main);
$this->assertCount(1, $debug);
$this->assertTrue(Logger::hasErrorsOccurred());
$queue = Logger::getQueue();
$this->assertCount(2, $queue);
$this->assertCount(3, $queue);
$this->assertSame(
'Could not add log writer of type "Invalid". Type does not exist.',
$queue[0][0],
'Log message of an invalid writer'
$this->assertEquals(
array(
'test-error-1',
3
),
$queue[0]
);
$this->assertSame(0, $queue[0][1], 'Log level "fatal"');
$this->assertSame(
'Could not flush logs to output. An exception was thrown: No writers were added',
$queue[1][0],
'Log message that no writer was added to logger'
$this->assertEquals(
array(
'test-debug-1',
7
),
$queue[1]
);
$this->assertSame(0, $queue[1][1], 'Log level "fatal"');
$this->assertEquals(
array(
'test-error-2',
3
),
$queue[2]
);
}
}