Merge branch 'master' into bugfix/slow-unhandled-service-summary-query-in-host-overview-9864

This commit is contained in:
Johannes Meyer 2015-08-11 14:01:27 +02:00
commit 3f0d073f43
23 changed files with 454 additions and 142 deletions

View File

@ -447,6 +447,7 @@ class Module
*/ */
public function getCssFiles() public function getCssFiles()
{ {
$this->launchConfigScript();
$files = $this->cssFiles; $files = $this->cssFiles;
$files[] = $this->getCssFilename(); $files[] = $this->getCssFilename();
return $files; return $files;
@ -497,6 +498,7 @@ class Module
*/ */
public function getJsFiles() public function getJsFiles()
{ {
$this->launchConfigScript();
$files = $this->jsFiles; $files = $this->jsFiles;
$files[] = $this->getJsFilename(); $files[] = $this->getJsFilename();
return $files; return $files;

View File

@ -173,8 +173,10 @@ class ConfigObject extends ArrayDatasource implements Iterator, ArrayAccess
/** /**
* Add a new property or section * Add a new property or section
* *
* @param string $key The name of the new property or section * @param string $key The name of the new property or section
* @param mixed $value The value to set for the new property or section * @param mixed $value The value to set for the new property or section
*
* @throws ProgrammingError If the key is null
*/ */
public function offsetSet($key, $value) public function offsetSet($key, $value)
{ {
@ -254,7 +256,7 @@ class ConfigObject extends ArrayDatasource implements Iterator, ArrayAccess
/** /**
* Merge the given data with this config * Merge the given data with this config
* *
* @param array|Config $data An array or a config * @param array|ConfigObject $data An array or a config
* *
* @return $this * @return $this
*/ */

View File

@ -3,14 +3,31 @@
namespace Icinga\File\Ini\Dom; namespace Icinga\File\Ini\Dom;
/**
* A single comment-line in an INI file
*/
class Comment class Comment
{ {
/** /**
* The comment text
*
* @var string * @var string
*/ */
public $content; protected $content;
/** /**
* Set the text content of this comment
*
* @param $content
*/
public function setContent($content)
{
$this->content = $content;
}
/**
* Render this comment into INI markup
*
* @return string * @return string
*/ */
public function render() public function render()

View File

@ -3,42 +3,57 @@
namespace Icinga\File\Ini\Dom; namespace Icinga\File\Ini\Dom;
use Icinga\Exception\ConfigurationError;
/**
* A key value pair in a Section
*/
class Directive class Directive
{ {
/** /**
* The value of this configuration directive
*
* @var string * @var string
*/ */
protected $key; protected $key;
/** /**
* The immutable name of this configuration directive
*
* @var string * @var string
*/ */
protected $value; protected $value;
/** /**
* @var array * Comments added one line before this directive
*/
public $commentsPre;
/**
* @var string
*/
public $commentPost;
/**
* @param string $key
* *
* @throws Exception * @var Comment[] The comment lines
*/
protected $commentsPre = null;
/**
* Comment added at the end of the same line
*
* @var Comment
*/
protected $commentPost = null;
/**
* @param string $key The name of this configuration directive
*
* @throws ConfigurationError
*/ */
public function __construct($key) public function __construct($key)
{ {
$this->key = trim($key); $this->key = trim($key);
if (strlen($this->key) < 1) { if (strlen($this->key) < 1) {
throw new Exception(sprintf('Ini parser error: empty key.')); throw new ConfigurationError(sprintf('Ini error: empty directive key.'));
} }
} }
/** /**
* Return the name of this directive
*
* @return string * @return string
*/ */
public function getKey() public function getKey()
@ -47,7 +62,9 @@ class Directive
} }
/** /**
* @return string * Return the value of this configuration directive
*
* @return string
*/ */
public function getValue() public function getValue()
{ {
@ -55,7 +72,9 @@ class Directive
} }
/** /**
* @param string $value * Set the value of this configuration directive
*
* @param string $value
*/ */
public function setValue($value) public function setValue($value)
{ {
@ -63,7 +82,39 @@ class Directive
} }
/** /**
* @return string * Set the comments to be rendered on the line before this directive
*
* @param Comment[] $comments
*/
public function setCommentsPre(array $comments)
{
$this->commentsPre = $comments;
}
/**
* Return the comments to be rendered on the line before this directive
*
* @return Comment[]
*/
public function getCommentsPre()
{
return $this->commentsPre;
}
/**
* Set the comment rendered on the same line of this directive
*
* @param Comment $comment
*/
public function setCommentPost(Comment $comment)
{
$this->commentPost = $comment;
}
/**
* Render this configuration directive into INI markup
*
* @return string
*/ */
public function render() public function render()
{ {
@ -82,16 +133,35 @@ class Directive
return $str; return $str;
} }
/**
* Assure that the given identifier contains no newlines and pending or trailing whitespaces
*
* @param $str The string to sanitize
*
* @return string
*/
protected function sanitizeKey($str) protected function sanitizeKey($str)
{ {
return trim(str_replace(PHP_EOL, ' ', $str)); return trim(str_replace(PHP_EOL, ' ', $str));
} }
/**
* Escape the significant characters in directive values, normalize line breaks and assure that
* the character contains no linebreaks
*
* @param $str The string to sanitize
*
* @return mixed|string
*/
protected function sanitizeValue($str) protected function sanitizeValue($str)
{ {
$str = trim($str); $str = trim($str);
$str = str_replace('\\', '\\\\', $str); $str = str_replace('\\', '\\\\', $str);
$str = str_replace('"', '\\"', $str); $str = str_replace('"', '\\"', $str);
return str_replace(PHP_EOL, ' ', $str);
// line breaks in the value should always match the current system EOL sequence
// to assure editable configuration files
$str = preg_replace("/(\r\n)|(\n)/", PHP_EOL, $str);
return $str;
} }
} }

View File

@ -6,16 +6,22 @@ namespace Icinga\File\Ini\Dom;
class Document class Document
{ {
/** /**
* @var array * The sections of this INI file
*
* @var Section[]
*/ */
protected $sections = array(); protected $sections = array();
/** /**
* @var array * The comemnts at file end that belong to no particular section
*
* @var Comment[]
*/ */
public $commentsDangling; protected $commentsDangling;
/** /**
* Append a section to the end of this INI file
*
* @param Section $section * @param Section $section
*/ */
public function addSection(Section $section) public function addSection(Section $section)
@ -24,6 +30,8 @@ class Document
} }
/** /**
* Return whether this INI file has the section with the given key
*
* @param string $name * @param string $name
* *
* @return bool * @return bool
@ -34,6 +42,8 @@ class Document
} }
/** /**
* Return the section with the given name
*
* @param string $name * @param string $name
* *
* @return Section * @return Section
@ -44,6 +54,8 @@ class Document
} }
/** /**
* Set the section with the given name
*
* @param string $name * @param string $name
* @param Section $section * @param Section $section
* *
@ -55,6 +67,8 @@ class Document
} }
/** /**
* Remove the section with the given name
*
* @param string $name * @param string $name
*/ */
public function removeSection($name) public function removeSection($name)
@ -63,6 +77,28 @@ class Document
} }
/** /**
* Set the dangling comments at file end that belong to no particular directive
*
* @param Comment[] $comments
*/
public function setCommentsDangling(array $comments)
{
$this->commentsDangling = $comments;
}
/**
* Get the dangling comments at file end that belong to no particular directive
*
* @return array
*/
public function getCommentsDangling()
{
return $this->commentsDangling;
}
/**
* Render this document into the corresponding INI markup
*
* @return string * @return string
*/ */
public function render() public function render()

View File

@ -5,30 +5,43 @@ namespace Icinga\File\Ini\Dom;
use Icinga\Exception\ConfigurationError; use Icinga\Exception\ConfigurationError;
/**
* A section in an INI file
*/
class Section class Section
{ {
/** /**
* The immutable name of this section
*
* @var string * @var string
*/ */
protected $name; protected $name;
/** /**
* @var array * All configuration directives of this section
*
* @var Directive[]
*/ */
protected $directives = array(); protected $directives = array();
/** /**
* @var array * Comments added one line before this section
*
* @var Comment[]
*/ */
public $commentsPre; protected $commentsPre;
/** /**
* Comment added at the end of the same line
*
* @var string * @var string
*/ */
public $commentPost; protected $commentPost;
/** /**
* @param string $name * @param string $name The immutable name of this section
*
* @throws ConfigurationError When the section name is empty
*/ */
public function __construct($name) public function __construct($name)
{ {
@ -39,7 +52,9 @@ class Section
} }
/** /**
* @param Directive $directive * Append a directive to the end of this section
*
* @param Directive $directive The directive to append
*/ */
public function addDirective(Directive $directive) public function addDirective(Directive $directive)
{ {
@ -47,7 +62,9 @@ class Section
} }
/** /**
* @param string $key * Remove the directive with the given name
*
* @param string $key They name of the directive to remove
*/ */
public function removeDirective($key) public function removeDirective($key)
{ {
@ -55,7 +72,9 @@ class Section
} }
/** /**
* @param string $key * Return whether this section has a directive with the given key
*
* @param string $key The name of the directive
* *
* @return bool * @return bool
*/ */
@ -65,6 +84,8 @@ class Section
} }
/** /**
* Get the directive with the given key
*
* @param $key string * @param $key string
* *
* @return Directive * @return Directive
@ -75,7 +96,9 @@ class Section
} }
/** /**
* @return string * Return the name of this section
*
* @return string The name
*/ */
public function getName() public function getName()
{ {
@ -83,6 +106,28 @@ class Section
} }
/** /**
* Set the comments to be rendered on the line before this section
*
* @param Comment[] $comments
*/
public function setCommentsPre(array $comments)
{
$this->commentsPre = $comments;
}
/**
* Set the comment rendered on the same line of this section
*
* @param Comment $comment
*/
public function setCommentPost(Comment $comment)
{
$this->commentPost = $comment;
}
/**
* Render this section into INI markup
*
* @return string * @return string
*/ */
public function render() public function render()
@ -90,7 +135,9 @@ class Section
$dirs = ''; $dirs = '';
$i = 0; $i = 0;
foreach ($this->directives as $directive) { foreach ($this->directives as $directive) {
$dirs .= (($i++ > 0 && ! empty($directive->commentsPre)) ? PHP_EOL : '') . $directive->render() . PHP_EOL; $comments = $directive->getCommentsPre();
$dirs .= (($i++ > 0 && ! empty($comments)) ? PHP_EOL : '')
. $directive->render() . PHP_EOL;
} }
$cms = ''; $cms = '';
if (! empty($this->commentsPre)) { if (! empty($this->commentsPre)) {
@ -106,6 +153,13 @@ class Section
return $cms . sprintf('[%s]', $this->sanitize($this->name)) . $post . PHP_EOL . $dirs; return $cms . sprintf('[%s]', $this->sanitize($this->name)) . $post . PHP_EOL . $dirs;
} }
/**
* Escape the significant characters in sections and normalize line breaks
*
* @param $str The string to sanitize
*
* @return mixed
*/
protected function sanitize($str) protected function sanitize($str)
{ {
$str = trim($str); $str = trim($str);

View File

@ -23,6 +23,14 @@ class IniParser
const COMMENT_END = 9; const COMMENT_END = 9;
const LINE_END = 10; const LINE_END = 10;
/**
* Cancel the parsing with an error
*
* @param $message The error description
* @param $line The line in which the error occured
*
* @throws ConfigurationError
*/
private static function throwParseError($message, $line) private static function throwParseError($message, $line)
{ {
throw new ConfigurationError(sprintf('Ini parser error: %s. (l. %d)', $message, $line)); throw new ConfigurationError(sprintf('Ini parser error: %s. (l. %d)', $message, $line));
@ -85,7 +93,7 @@ class IniParser
$token .= $s; $token .= $s;
} else { } else {
$sec = new Section($token); $sec = new Section($token);
$sec->commentsPre = $coms; $sec->setCommentsPre($coms);
$doc->addSection($sec); $doc->addSection($sec);
$dir = null; $dir = null;
$coms = array(); $coms = array();
@ -100,8 +108,7 @@ class IniParser
$token .= $s; $token .= $s;
} else { } else {
$dir = new Directive($token); $dir = new Directive($token);
$dir->commentsPre = $coms; $dir->setCommentsPre($coms);
if (isset($sec)) { if (isset($sec)) {
$sec->addDirective($dir); $sec->addDirective($dir);
} else { } else {
@ -151,9 +158,7 @@ class IniParser
break; break;
case self::DIRECTIVE_VALUE_QUOTED: case self::DIRECTIVE_VALUE_QUOTED:
if ($s === "\n") { if ($s === '\\') {
self::throwParseError('Unterminated DIRECTIVE_VALUE_QUOTED', $line);
} elseif ($s === '\\') {
$state = self::ESCAPE; $state = self::ESCAPE;
$escaping = self::DIRECTIVE_VALUE_QUOTED; $escaping = self::DIRECTIVE_VALUE_QUOTED;
} elseif ($s !== '"') { } elseif ($s !== '"') {
@ -171,16 +176,16 @@ class IniParser
$token .= $s; $token .= $s;
} else { } else {
$com = new Comment(); $com = new Comment();
$com->content = $token; $com->setContent($token);
$token = ''; $token = '';
// Comments at the line end belong to the current line's directive or section. Comments // Comments at the line end belong to the current line's directive or section. Comments
// on empty lines belong to the next directive that shows up. // on empty lines belong to the next directive that shows up.
if ($state === self::COMMENT_END) { if ($state === self::COMMENT_END) {
if (isset($dir)) { if (isset($dir)) {
$dir->commentPost = $com; $dir->setCommentPost($com);
} else { } else {
$sec->commentPost = $com; $sec->setCommentPost($com);
} }
} else { } else {
$coms[] = $com; $coms[] = $com;
@ -206,12 +211,12 @@ class IniParser
case self::COMMENT: case self::COMMENT:
case self::COMMENT_END: case self::COMMENT_END:
$com = new Comment(); $com = new Comment();
$com->content = $token; $com->setContent($token);
if ($state === self::COMMENT_END) { if ($state === self::COMMENT_END) {
if (isset($dir)) { if (isset($dir)) {
$dir->commentPost = $com; $dir->setCommentPost($com);
} else { } else {
$sec->commentPost = $com; $sec->setCommentPost($com);
} }
} else { } else {
$coms[] = $com; $coms[] = $com;
@ -230,7 +235,7 @@ class IniParser
self::throwParseError('File ended in unterminated state ' . $state, $line); self::throwParseError('File ended in unterminated state ' . $state, $line);
} }
if (! empty($coms)) { if (! empty($coms)) {
$doc->commentsDangling = $coms; $doc->setCommentsDangling($coms);
} }
return $doc; return $doc;
} }

View File

@ -117,7 +117,10 @@ class IniWriter
protected function updateSectionOrder(Config $newconfig, Document $oldDoc) protected function updateSectionOrder(Config $newconfig, Document $oldDoc)
{ {
$doc = new Document(); $doc = new Document();
$doc->commentsDangling = $oldDoc->commentsDangling; $dangling = $oldDoc->getCommentsDangling();
if (isset($dangling)) {
$doc->setCommentsDangling($dangling);
}
foreach ($newconfig->toArray() as $section => $directives) { foreach ($newconfig->toArray() as $section => $directives) {
$doc->addSection($oldDoc->getSection($section)); $doc->addSection($oldDoc->getSection($section));
} }

View File

@ -43,7 +43,7 @@ class Menu implements RecursiveIterator
/** /**
* The url of this menu * The url of this menu
* *
* @var string * @var string|null
*/ */
protected $url; protected $url;
@ -404,21 +404,20 @@ class Menu implements RecursiveIterator
*/ */
public function setUrl($url) public function setUrl($url)
{ {
if ($url instanceof Url) { $this->url = $url;
$this->url = $url;
} else {
$this->url = Url::fromPath($url);
}
return $this; return $this;
} }
/** /**
* Return the url of this menu * Return the url of this menu
* *
* @return Url * @return Url|null
*/ */
public function getUrl() public function getUrl()
{ {
if ($this->url !== null && ! $this->url instanceof Url) {
$this->url = Url::fromPath($this->url);
}
return $this->url; return $this->url;
} }

View File

@ -20,7 +20,7 @@ class Dashlet extends UserWidget
/** /**
* The url of this Dashlet * The url of this Dashlet
* *
* @var \Icinga\Web\Url * @var Url|null
*/ */
private $url; private $url;
@ -74,16 +74,13 @@ EOD;
{ {
$this->title = $title; $this->title = $title;
$this->pane = $pane; $this->pane = $pane;
if ($url instanceof Url) { if (! $url) {
$this->url = $url;
} elseif ($url) {
$this->url = Url::fromPath($url);
} else {
throw new IcingaException( throw new IcingaException(
'Cannot create dashboard dashlet "%s" without valid URL', 'Cannot create dashboard dashlet "%s" without valid URL',
$title $title
); );
} }
$this->url = $url;
} }
/** /**
@ -107,10 +104,13 @@ EOD;
/** /**
* Retrieve the dashlets url * Retrieve the dashlets url
* *
* @return Url * @return Url|null
*/ */
public function getUrl() public function getUrl()
{ {
if ($this->url !== null && ! $this->url instanceof Url) {
$this->url = Url::fromPath($this->url);
}
return $this->url; return $this->url;
} }
@ -123,11 +123,7 @@ EOD;
*/ */
public function setUrl($url) public function setUrl($url)
{ {
if ($url instanceof Url) { $this->url = $url;
$this->url = $url;
} else {
$this->url = Url::fromPath($url);
}
return $this; return $this;
} }
@ -159,7 +155,7 @@ EOD;
public function toArray() public function toArray()
{ {
$array = array( $array = array(
'url' => $this->url->getRelativeUrl(), 'url' => $this->getUrl()->getRelativeUrl(),
'title' => $this->getTitle() 'title' => $this->getTitle()
); );
if ($this->getDisabled() === true) { if ($this->getDisabled() === true) {
@ -178,9 +174,9 @@ EOD;
} }
$view = $this->view(); $view = $this->view();
$url = clone($this->url); $url = $this->getUrl();
$url->setParam('view', 'compact'); $url->setParam('view', 'compact');
$iframeUrl = clone($url); $iframeUrl = clone $url;
$iframeUrl->setParam('isIframe'); $iframeUrl->setParam('isIframe');
$searchTokens = array( $searchTokens = array(

View File

@ -3,6 +3,7 @@
namespace Icinga\Module\Doc; namespace Icinga\Module\Doc;
use CachingIterator;
use LogicException; use LogicException;
use SplStack; use SplStack;
use Icinga\Data\Tree\SimpleTree; use Icinga\Data\Tree\SimpleTree;
@ -15,6 +16,20 @@ use Icinga\Module\Doc\Exception\DocException;
*/ */
class DocParser class DocParser
{ {
/**
* Internal identifier for Atx-style headers
*
* @var int
*/
const HEADER_ATX = 1;
/**
* Internal identifier for Setext-style headers
*
* @var int
*/
const HEADER_SETEXT = 2;
/** /**
* Path to the documentation * Path to the documentation
* *
@ -70,11 +85,11 @@ class DocParser
* Extract atx- or setext-style headers from the given lines * Extract atx- or setext-style headers from the given lines
* *
* @param string $line * @param string $line
* @param string $lastLine * @param string $nextLine
* *
* @return array|null An array containing the header and the header level or null if there's nothing to extract * @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) protected function extractHeader($line, $nextLine)
{ {
if (! $line) { if (! $line) {
return null; return null;
@ -90,13 +105,14 @@ class DocParser
if (! $header) { if (! $header) {
return null; return null;
} }
$headerStyle = static::HEADER_ATX;
} elseif ( } elseif (
$line $nextLine
&& ($line[0] === '=' || $line[0] === '-') && ($nextLine[0] === '=' || $nextLine[0] === '-')
&& preg_match('/^[=-]+\s*$/', $line, $match) === 1 && preg_match('/^[=-]+\s*$/', $nextLine, $match) === 1
) { ) {
// Setext // Setext
$header = trim($lastLine); $header = trim($line);
if (! $header) { if (! $header) {
return null; return null;
} }
@ -105,6 +121,7 @@ class DocParser
} else { } else {
$level = 2; $level = 2;
} }
$headerStyle = static::HEADER_SETEXT;
} }
if ($header === null) { if ($header === null) {
return null; return null;
@ -117,7 +134,7 @@ class DocParser
} else { } else {
$id = null; $id = null;
} }
return array($header, $id, $level); return array($header, $id, $level, $headerStyle);
} }
/** /**
@ -128,15 +145,18 @@ class DocParser
public function getDocTree() public function getDocTree()
{ {
$tree = new SimpleTree(); $tree = new SimpleTree();
$stack = new SplStack();
foreach ($this->docIterator as $fileInfo) { foreach ($this->docIterator as $fileInfo) {
/** @var $fileInfo \SplFileInfo */ /** @var $fileInfo \SplFileInfo */
$file = $fileInfo->openFile(); $file = $fileInfo->openFile();
$lastLine = null; $lastLine = null;
foreach ($file as $line) { $stack = new SplStack();
$header = $this->extractHeader($line, $lastLine); $cachingIterator = new CachingIterator($file, CachingIterator::TOSTRING_USE_CURRENT);
for ($cachingIterator->rewind(); $line = $cachingIterator->valid(); $cachingIterator->next()) {
$fileIterator = $cachingIterator->getInnerIterator();
$line = $cachingIterator->current();
$header = $this->extractHeader($line, $fileIterator->valid() ? $fileIterator->current() : null);
if ($header !== null) { if ($header !== null) {
list($title, $id, $level) = $header; list($title, $id, $level, $headerStyle) = $header;
while (! $stack->isEmpty() && $stack->top()->getLevel() >= $level) { while (! $stack->isEmpty() && $stack->top()->getLevel() >= $level) {
$stack->pop(); $stack->pop();
} }
@ -169,14 +189,29 @@ class DocParser
$tree->addChild($section, $stack->top()); $tree->addChild($section, $stack->top());
} }
$stack->push($section); $stack->push($section);
if ($headerStyle === static::HEADER_SETEXT) {
$cachingIterator->next();
continue;
}
} else { } else {
if ($stack->isEmpty()) { if ($stack->isEmpty()) {
throw new LogicException('Heading required'); $title = ucfirst($file->getBasename('.' . pathinfo($file->getFilename(), PATHINFO_EXTENSION)));
$id = $title;
if ($tree->getNode($id) !== null) {
$id = uniqid($id);
}
$section = new DocSection();
$section
->setId($id)
->setTitle($title)
->setLevel(1)
->setNoFollow(true);
$section->setChapter($section);
$tree->addChild($section);
$stack->push($section);
} }
$stack->top()->appendContent($line); $stack->top()->appendContent($line);
} }
// Save last line for setext-style headers
$lastLine = $line;
} }
} }
return $tree; return $tree;

View File

@ -88,6 +88,14 @@ class DocSection extends TreeNode
return $this->content; return $this->content;
} }
/**
* {@inheritdoc}
*/
public function setId($id)
{
return parent::setId(str_replace(' ', '-', (string) $id));
}
/** /**
* Set the header level * Set the header level
* *

View File

@ -35,9 +35,15 @@ if (! $this->compact): ?>
?> ?>
<tr class="state <?= $stateName; ?><?= $downtime->is_in_effect ? ' handled' : ''; ?>"> <tr class="state <?= $stateName; ?><?= $downtime->is_in_effect ? ' handled' : ''; ?>">
<td class="state"> <td class="state">
<?php if ($downtime->start <= time() && ! $downtime->is_in_effect): ?>
<strong><?= $this->translate('Ends'); ?></strong>
<br>
<?= $this->timeUntil($downtime->is_flexible ? $downtime->scheduled_end : $downtime->end, $this->compact) ?>
<?php else: ?>
<strong><?= $downtime->is_in_effect ? $this->translate('Expires') : $this->translate('Starts'); ?></strong> <strong><?= $downtime->is_in_effect ? $this->translate('Expires') : $this->translate('Starts'); ?></strong>
<br> <br>
<?= $this->timeUntil($downtime->is_in_effect ? $downtime->end : $downtime->start, $this->compact) ?> <?= $this->timeUntil($downtime->is_in_effect ? $downtime->end : $downtime->start, $this->compact) ?>
<?php endif; ?>
</td> </td>
<td> <td>
<?php if ($isService): ?> <?php if ($isService): ?>

View File

@ -1,9 +1,15 @@
<table class="action"> <table class="action">
<tr class="state <?= $stateName; ?><?= $downtime->is_in_effect ? ' handled' : ''; ?>"> <tr class="state <?= $stateName; ?><?= $downtime->is_in_effect ? ' handled' : ''; ?>">
<td class="state"> <td class="state">
<strong><?= $downtime->is_in_effect ? $this->translate('Expires') : $this->translate('Starts'); ?></strong> <?php if ($downtime->start <= time() && ! $downtime->is_in_effect): ?>
<br> <strong><?= $this->translate('Ends'); ?></strong>
<?= $this->timeUntil($downtime->is_in_effect ? $downtime->end : $downtime->start, $this->compact) ?> <br>
<?= $this->timeUntil($downtime->is_flexible ? $downtime->scheduled_end : $downtime->end, $this->compact) ?>
<?php else: ?>
<strong><?= $downtime->is_in_effect ? $this->translate('Expires') : $this->translate('Starts'); ?></strong>
<br>
<?= $this->timeUntil($downtime->is_in_effect ? $downtime->end : $downtime->start, $this->compact) ?>
<?php endif; ?>
</td> </td>
<td> <td>
<small> <small>

View File

@ -6,9 +6,15 @@
} ?> } ?>
<tr class="state <?= $downtime->stateText ?>"> <tr class="state <?= $downtime->stateText ?>">
<td class="state"> <td class="state">
<?php if ($downtime->start <= time() && ! $downtime->is_in_effect): ?>
<strong><?= $this->translate('Ends'); ?></strong>
<br>
<?= $this->timeUntil($downtime->is_flexible ? $downtime->scheduled_end : $downtime->end, $this->compact) ?>
<?php else: ?>
<strong><?= $downtime->is_in_effect ? $this->translate('Expires') : $this->translate('Starts'); ?></strong> <strong><?= $downtime->is_in_effect ? $this->translate('Expires') : $this->translate('Starts'); ?></strong>
<br> <br>
<?= $this->timeUntil($downtime->is_in_effect ? $downtime->end : $downtime->start, $this->compact) ?> <?= $this->timeUntil($downtime->is_in_effect ? $downtime->end : $downtime->start, $this->compact) ?>
<?php endif; ?>
</td> </td>
<td class="name oneline"> <td class="name oneline">
<?php if ($downtime->isService): ?> <?php if ($downtime->isService): ?>

View File

@ -53,7 +53,12 @@ if (empty($object->comments) && ! $addLink) {
$this->timeUntil($downtime->end) $this->timeUntil($downtime->end)
); );
} else { } else {
if ((bool) $downtime->is_fixed) { if ($downtime->start <= time()) {
$state = sprintf(
$this->translate('ends %s', 'Last format parameter represents the end time'),
$this->timeUntil($downtime->is_flexible ? $downtime->scheduled_end : $downtime->end)
);
} elseif ((bool) $downtime->is_fixed) {
$state = sprintf( $state = sprintf(
$this->translate('scheduled %s', 'Last format parameter represents the time scheduled'), $this->translate('scheduled %s', 'Last format parameter represents the time scheduled'),
$this->timeUntil($downtime->start) $this->timeUntil($downtime->start)

View File

@ -122,7 +122,6 @@ class LocalCommandFile implements CommandTransportInterface
try { try {
$file = new File($this->path, $this->openMode); $file = new File($this->path, $this->openMode);
$file->fwrite($commandString . "\n"); $file->fwrite($commandString . "\n");
$file->fflush();
} catch (Exception $e) { } catch (Exception $e) {
$message = $e->getMessage(); $message = $e->getMessage();
if ($e instanceof RuntimeException && ($pos = strrpos($message, ':')) !== false) { if ($e instanceof RuntimeException && ($pos = strrpos($message, ':')) !== false) {

View File

@ -279,6 +279,7 @@ abstract class MonitoredObject implements Filterable
'author_name' => 'downtime_author_name', 'author_name' => 'downtime_author_name',
'start' => 'downtime_start', 'start' => 'downtime_start',
'scheduled_start' => 'downtime_scheduled_start', 'scheduled_start' => 'downtime_scheduled_start',
'scheduled_end' => 'downtime_scheduled_end',
'end' => 'downtime_end', 'end' => 'downtime_end',
'duration' => 'downtime_duration', 'duration' => 'downtime_duration',
'is_flexible' => 'downtime_is_flexible', 'is_flexible' => 'downtime_is_flexible',

View File

@ -335,10 +335,6 @@ table.action td.state, table.objectstate td.state {
text-align: center; text-align: center;
} }
table.action td.timesince {
width: 3.5em;
}
/* State row behaviour */ /* State row behaviour */
@ -1117,10 +1113,6 @@ table.groupview {
font-size: 0.8em; font-size: 0.8em;
} }
span.timesince {
font-size: 0.8em;
}
&.ok { &.ok {
border-color: @colorOk; border-color: @colorOk;
} }

View File

@ -112,38 +112,69 @@
/** /**
* Load a given module by name * Load a given module by name
*
* @param {string} name
*
* @return {boolean}
*/ */
loadModule: function (name) { loadModule: function (name) {
if (this.hasModule(name)) { if (this.isLoadedModule(name)) {
this.logger.error('Cannot load module ' + name + ' twice'); this.logger.error('Cannot load module ' + name + ' twice');
return; return false;
} }
this.modules[name] = new Icinga.Module(this, name); if (! this.hasModule(name)) {
this.logger.error('Cannot find module ' + name);
return false;
}
this.modules[name] = new Icinga.Module(
this,
name,
Icinga.availableModules[name]
);
return true;
}, },
/** /**
* Whether a module matching the given name exists * Whether a module matching the given name exists or is loaded
*
* @param {string} name
*
* @return {boolean}
*/ */
hasModule: function (name) { hasModule: function (name) {
return 'undefined' !== typeof this.modules[name] || return this.isLoadedModule(name) ||
'undefined' !== typeof Icinga.availableModules[name]; 'undefined' !== typeof Icinga.availableModules[name];
}, },
/**
* Return whether the given module is loaded
*
* @param {string} name The name of the module
*
* @returns {Boolean}
*/
isLoadedModule: function (name) {
return 'undefined' !== typeof this.modules[name];
},
/** /**
* Get a module by name * Get a module by name
*
* @param {string} name
*
* @return {object}
*/ */
module: function (name) { module: function (name) {
if ('undefined' === typeof this.modules[name]) { if (this.hasModule(name) && !this.isLoadedModule(name)) {
if ('undefined' !== typeof Icinga.availableModules[name]) { this.modules[name] = new Icinga.Module(
this.modules[name] = new Icinga.Module( this,
this, name,
name, Icinga.availableModules[name]
Icinga.availableModules[name] );
);
}
} }
return this.modules[name]; return this.modules[name];

View File

@ -13,6 +13,7 @@
this.icinga = icinga; this.icinga = icinga;
this.searchValue = ''; this.searchValue = '';
this.initializeModules = true;
}; };
Icinga.Events.prototype = { Icinga.Events.prototype = {
@ -30,39 +31,63 @@
}, },
// TODO: What's this? // TODO: What's this?
applyHandlers: function (evt) { applyHandlers: function (event) {
var el = $(evt.target), self = evt.data.self; var $target = $(event.target);
var self = event.data.self;
var icinga = self.icinga; var icinga = self.icinga;
$('.dashboard > div', el).each(function(idx, el) { if (self.initializeModules) {
var url = $(el).data('icingaUrl'); var loaded = false;
if (typeof url === 'undefined') return; var moduleName = $target.data('icingaModule');
icinga.loader.loadUrl(url, $(el)).autorefresh = true; if (moduleName) {
}); if (icinga.hasModule(moduleName) && !icinga.isLoadedModule(moduleName)) {
loaded |= icinga.loadModule(moduleName);
$('td.state span.timesince').attr('title', null); }
var moduleName = el.data('icingaModule');
if (moduleName) {
if (icinga.hasModule(moduleName)) {
var module = icinga.module(moduleName);
// NOT YET, the applyOnloadDings: module.applyEventHandlers(mod);
} }
$('.icinga-module', $target).each(function(idx, mod) {
moduleName = $(mod).data('icingaModule');
if (icinga.hasModule(moduleName) && !icinga.isLoadedModule(moduleName)) {
loaded |= icinga.loadModule(moduleName);
}
});
if (loaded) {
// Modules may register their own handler for the 'renderend' event
// so we need to ensure that it is called the first time they are
// initialized
event.stopImmediatePropagation();
self.initializeModules = false;
var $container = $target.closest('.container');
if (! $container.length) {
// The page obviously got loaded for the first time,
// so we'll trigger the event for all containers
$container = $('.container');
}
$container.trigger('rendered');
// But since we're listening on this event by ourself, we'll have
// to abort our own processing as we'll process it twice otherwise
return false;
}
} else {
self.initializeModules = true;
} }
$('.icinga-module', el).each(function(idx, mod) { $('.dashboard > div', $target).each(function(idx, el) {
var $mod = $(mod); var $element = $(el);
moduleName = $mod.data('icingaModule'); var $url = $element.data('icingaUrl');
if (icinga.hasModule(moduleName)) { if (typeof $url !== 'undefined') {
var module = icinga.module(moduleName); icinga.loader.loadUrl($url, $element).autorefresh = true;
// NOT YET, the applyOnloadDings: module.applyEventHandlers(mod);
} }
}); });
var searchField = $('#menu input.search', el); var $searchField = $('#menu input.search', $target);
// Remember initial search field value if any // Remember initial search field value if any
if (searchField.length && searchField.val().length) { if ($searchField.length && $searchField.val().length) {
self.searchValue = searchField.val(); self.searchValue = $searchField.val();
} }
if (icinga.ui.isOneColLayout()) { if (icinga.ui.isOneColLayout()) {
@ -76,13 +101,14 @@
* Global default event handlers * Global default event handlers
*/ */
applyGlobalDefaults: function () { applyGlobalDefaults: function () {
// Apply element-specific behavior whenever the layout is rendered
// Note: It is important that this is the first handler for this event!
$(document).on('rendered', { self: this }, this.applyHandlers);
$.each(self.icinga.behaviors, function (name, behavior) { $.each(self.icinga.behaviors, function (name, behavior) {
behavior.bind($(document)); behavior.bind($(document));
}); });
// Apply element-specific behavior whenever the layout is rendered
$(document).on('rendered', { self: this }, this.applyHandlers);
// We catch resize events // We catch resize events
$(window).on('resize', { self: this.icinga.ui }, this.icinga.ui.onWindowResize); $(window).on('resize', { self: this.icinga.ui }, this.icinga.ui.onWindowResize);

View File

@ -60,4 +60,19 @@ EOD;
'IniParser does not recognize escaped bracket in section' 'IniParser does not recognize escaped bracket in section'
); );
} }
public function testMultilineValues()
{
$config = <<<'EOD'
[section]
key1 = "with some
newline in the value"
EOD;
$doc = IniParser::parseIni($config);
$this->assertEquals(
2,
count(explode("\n", $doc->getSection('section')->getDirective('key1')->getValue())),
'IniParser does not recognize multi-line values'
);
}
} }

View File

@ -257,7 +257,7 @@ EOD;
); );
} }
public function testWhetherLinebreaksAreRemoved() public function testWhetherLinebreaksAreProcessed()
{ {
$target = $this->writeConfigToTemporaryFile(''); $target = $this->writeConfigToTemporaryFile('');
$writer = new IniWriter( $writer = new IniWriter(
@ -277,7 +277,7 @@ inkey' => 'blarg'
$rendered = $writer->render(); $rendered = $writer->render();
$this->assertEquals( $this->assertEquals(
count(explode("\n", $rendered)), count(explode("\n", $rendered)),
4, 5,
'generated config should not contain more than three line breaks' 'generated config should not contain more than three line breaks'
); );
} }
@ -327,7 +327,6 @@ EOD;
[section] [section]
key1 = "value with \"quotes\"" key1 = "value with \"quotes\""
key2 = "value with \\" key2 = "value with \\"
key3 = "value with newline"
EOD; EOD;
$target = $this->writeConfigToTemporaryFile($config); $target = $this->writeConfigToTemporaryFile($config);
@ -336,8 +335,7 @@ EOD;
array( array(
'section' => array( 'section' => array(
'key1' => 'value with "quotes"', 'key1' => 'value with "quotes"',
'key2' => 'value with \\', 'key2' => 'value with \\'
'key3' => 'value with' . PHP_EOL . 'newline'
) )
) )
), ),