diff --git a/application/controllers/ErrorController.php b/application/controllers/ErrorController.php index 7d30d62e5..074e2fdc2 100644 --- a/application/controllers/ErrorController.php +++ b/application/controllers/ErrorController.php @@ -4,6 +4,7 @@ // namespace Icinga\Application\Controllers; +use Icinga\Logger\Logger; use Icinga\Web\Controller\ActionController; use Icinga\Application\Icinga; @@ -21,6 +22,10 @@ class ErrorController extends ActionController { $error = $this->_getParam('error_handler'); $exception = $error->exception; + + Logger::error($exception); + Logger::error('Stacktrace: %s', $exception->getTraceAsString()); + switch ($error->type) { case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE: case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER: diff --git a/application/controllers/StaticController.php b/application/controllers/StaticController.php index 51b5fdb62..c90272ffc 100644 --- a/application/controllers/StaticController.php +++ b/application/controllers/StaticController.php @@ -2,10 +2,11 @@ // {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}} -use Zend_Controller_Action_Exception as ActionException; use Icinga\Web\Controller\ActionController; use Icinga\Application\Icinga; use Icinga\Logger\Logger; +use Icinga\Web\FileCache; +use Zend_Controller_Action_Exception as ActionException; /** * Delivery static content to clients @@ -30,8 +31,25 @@ class StaticController extends ActionController public function gravatarAction() { + $cache = FileCache::instance(); + $filename = md5(strtolower(trim($this->_request->getParam('email')))); + $cacheFile = 'gravatar-' . $filename; + header('Cache-Control: public'); + header('Pragma: cache'); + if ($etag = $cache->etagMatchesCachedFile($cacheFile)) { + header("HTTP/1.1 304 Not Modified"); + return; + } + header('Content-Type: image/jpg'); - $img = file_get_contents('http://www.gravatar.com/avatar/' . md5(strtolower(trim($this->_request->getParam('email')))) . '?s=200&d=mm'); + if ($cache->has($cacheFile)) { + header('ETag: "' . $cache->etagForCachedFile($cacheFile) . '"'); + $cache->send($cacheFile); + return; + } + $img = file_get_contents('http://www.gravatar.com/avatar/' . $filename . '?s=200&d=mm'); + $cache->store($cacheFile, $img); + header('ETag: "' . $cache->etagForCachedFile($cacheFile) . '"'); echo $img; } diff --git a/library/Icinga/Application/ApplicationBootstrap.php b/library/Icinga/Application/ApplicationBootstrap.php index 3f68ce43b..7736f8cf9 100644 --- a/library/Icinga/Application/ApplicationBootstrap.php +++ b/library/Icinga/Application/ApplicationBootstrap.php @@ -452,9 +452,8 @@ abstract class ApplicationBootstrap */ protected function setupInternationalization() { - $localeDir = $this->getApplicationDir('locale'); - if (file_exists($localeDir) && is_dir($localeDir)) { - Translator::registerDomain(Translator::DEFAULT_DOMAIN, $localeDir); + if ($this->hasLocales()) { + Translator::registerDomain(Translator::DEFAULT_DOMAIN, $this->getLocaleDir()); } try { @@ -469,4 +468,48 @@ abstract class ApplicationBootstrap return $this; } + + /** + * @return string Our locale directory + */ + public function getLocaleDir() + { + return $this->getApplicationDir('locale'); + } + + /** + * return bool Whether Icinga Web has translations + */ + public function hasLocales() + { + $localedir = $this->getLocaleDir(); + return file_exists($localedir) && is_dir($localedir); + } + + /** + * List all available locales + * + * NOTE: Might be a candidate for a static function in Translator + * + * return array Locale list + */ + public function listLocales() + { + $locales = array(); + if (! $this->hasLocales()) { + return $locales; + } + $localedir = $this->getLocaleDir(); + + $dh = opendir($localedir); + while (false !== ($file = readdir($dh))) { + $filename = $localedir . DIRECTORY_SEPARATOR . $file; + if (preg_match('/^[a-z]{2}_[A-Z]{2}$/', $file) && is_dir($filename)) { + $locales[] = $file; + } + } + closedir($dh); + sort($locales); + return $locales; + } } diff --git a/library/Icinga/Application/Config.php b/library/Icinga/Application/Config.php index f794d46a0..f30684883 100644 --- a/library/Icinga/Application/Config.php +++ b/library/Icinga/Application/Config.php @@ -85,6 +85,18 @@ class Config extends Zend_Config return self::$app[$configname]; } + /** + * Set module config + * + * @param string $moduleName + * @param string $configName + * @param Zend_Config $config + */ + public static function setModuleConfig($moduleName, $configName, Zend_Config $config) + { + self::$modules[$moduleName][$configName] = $config; + } + /** * Retrieve a module config instance * diff --git a/library/Icinga/Application/Modules/Module.php b/library/Icinga/Application/Modules/Module.php index fc7c25b17..d0bf451ee 100644 --- a/library/Icinga/Application/Modules/Module.php +++ b/library/Icinga/Application/Modules/Module.php @@ -177,7 +177,6 @@ class Module /** * Add a pane to dashboard * - * @param $id * @param $name * @return Pane */ @@ -201,21 +200,19 @@ class Module /** * Add a menu Section to the Sidebar menu * - * @param string $id - * @param string $name + * @param $name * @param array $properties * @return mixed */ - protected function menuSection($id, $name, array $properties = array()) + protected function menuSection($name, array $properties = array()) { - if (array_key_exists($id, $this->menuItems)) { - $this->menuItems[$id]->setProperties($properties); + if (array_key_exists($name, $this->menuItems)) { + $this->menuItems[$name]->setProperties($properties); } else { - $this->menuItems[$id] = new Menu($id, new Zend_Config($properties)); - $this->menuItems[$id]->setTitle($name); + $this->menuItems[$name] = new Menu($name, new Zend_Config($properties)); } - return $this->menuItems[$id]; + return $this->menuItems[$name]; } /** @@ -711,12 +708,44 @@ class Module */ protected function registerLocales() { - if (file_exists($this->localedir) && is_dir($this->localedir)) { + if ($this->hasLocales()) { Translator::registerDomain($this->name, $this->localedir); } return $this; } + /** + * return bool Whether this module has translations + */ + public function hasLocales() + { + return file_exists($this->localedir) && is_dir($this->localedir); + } + + /** + * List all available locales + * + * return array Locale list + */ + public function listLocales() + { + $locales = array(); + if (! $this->hasLocales()) { + return $locales; + } + + $dh = opendir($this->localedir); + while (false !== ($file = readdir($dh))) { + $filename = $this->localedir . DIRECTORY_SEPARATOR . $file; + if (preg_match('/^[a-z]{2}_[A-Z]{2}$/', $file) && is_dir($filename)) { + $locales[] = $file; + } + } + closedir($dh); + sort($locales); + return $locales; + } + /** * Register web integration * diff --git a/library/Icinga/Application/Web.php b/library/Icinga/Application/Web.php index 2b45ad683..ecf863e9e 100644 --- a/library/Icinga/Application/Web.php +++ b/library/Icinga/Application/Web.php @@ -13,8 +13,6 @@ use Icinga\Logger\Logger; use Icinga\Web\Request; use Icinga\Web\Response; use Icinga\Web\View; -use Icinga\Web\Session\Session as BaseSession; -use Icinga\Web\Session; use Icinga\User; use Icinga\Util\Translator; use Icinga\Util\DateTimeFactory; @@ -59,13 +57,6 @@ class Web extends ApplicationBootstrap */ private $request; - /** - * Session object - * - * @var BaseSession - */ - private $session; - /** * User object * @@ -92,7 +83,6 @@ class Web extends ApplicationBootstrap ->setupErrorHandling() ->loadConfig() ->setupResourceFactory() - ->setupSession() ->setupUser() ->setupTimezone() ->setupLogger() @@ -172,7 +162,6 @@ class Web extends ApplicationBootstrap $this->setupFrontController(); $this->setupViewRenderer(); - return $this; } @@ -192,17 +181,6 @@ class Web extends ApplicationBootstrap return $this; } - /** - * Initialize a session provider - * - * @return self - */ - private function setupSession() - { - $this->session = Session::create(); - return $this; - } - /** * Inject dependencies into request * diff --git a/library/Icinga/Data/Db/DbQuery.php b/library/Icinga/Data/Db/DbQuery.php index 605e36af9..a89544be8 100644 --- a/library/Icinga/Data/Db/DbQuery.php +++ b/library/Icinga/Data/Db/DbQuery.php @@ -89,6 +89,17 @@ class DbQuery extends SimpleQuery public function getSelectQuery() { $select = $this->dbSelect(); + + // Add order fields to select for postgres distinct queries (#6351) + if ($this->hasOrder() + && $this->getDatasource()->getDbType() === 'pgsql' + && $select->getPart(Zend_Db_Select::DISTINCT) === true) { + foreach ($this->getOrder() as $fieldAndDirection) { + list($alias, $field) = explode('.', $fieldAndDirection[0]); + $this->columns[$field] = $fieldAndDirection[0]; + } + } + $select->columns($this->columns); $this->applyFilterSql($select); @@ -102,6 +113,7 @@ class DbQuery extends SimpleQuery ); } } + return $select; } diff --git a/library/Icinga/Data/Filter/Filter.php b/library/Icinga/Data/Filter/Filter.php index 7e995bb59..64607a4a0 100644 --- a/library/Icinga/Data/Filter/Filter.php +++ b/library/Icinga/Data/Filter/Filter.php @@ -132,12 +132,12 @@ abstract class Filter public static function expression($col, $op, $expression) { switch ($op) { - case '=': return new FilterEqual($col, $op, $expression); + case '=': return new FilterMatch($col, $op, $expression); case '<': return new FilterLessThan($col, $op, $expression); case '>': return new FilterGreaterThan($col, $op, $expression); case '>=': return new FilterEqualOrGreaterThan($col, $op, $expression); case '<=': return new FilterEqualOrLessThan($col, $op, $expression); - case '!=': return new FilterNotEqual($col, $op, $expression); + case '!=': return new FilterMatchNot($col, $op, $expression); default: throw new ProgrammingError( 'There is no such filter sign: %s', $op diff --git a/library/Icinga/Data/Filter/FilterMatch.php b/library/Icinga/Data/Filter/FilterMatch.php new file mode 100644 index 000000000..ef3cad801 --- /dev/null +++ b/library/Icinga/Data/Filter/FilterMatch.php @@ -0,0 +1,22 @@ +expression; + if (strpos($expression, '*') === false) { + return (string) $row->{$this->column} === $expression; + } else { + $parts = array(); + foreach (preg_split('/\*/', $expression) as $part) { + $parts[] = preg_quote($part); + } + return preg_match('/^' . implode('.*', $parts) . '$/', $row->{$this->column}); + } + } +} diff --git a/library/Icinga/Data/Filter/FilterMatchNot.php b/library/Icinga/Data/Filter/FilterMatchNot.php new file mode 100644 index 000000000..2ce5c2336 --- /dev/null +++ b/library/Icinga/Data/Filter/FilterMatchNot.php @@ -0,0 +1,22 @@ +expression; + if (strpos($expression, '*') === false) { + return (string) $row->{$this->column} !== $expression; + } else { + $parts = array(); + foreach (preg_split('/\*/', $expression) as $part) { + $parts[] = preg_quote($part); + } + return ! preg_match('/^' . implode('.*', $parts) . '$/', $row->{$this->column}); + } + } +} diff --git a/library/Icinga/Data/Filter/FilterNotEqual.php b/library/Icinga/Data/Filter/FilterNotEqual.php new file mode 100644 index 000000000..d1a5e9b64 --- /dev/null +++ b/library/Icinga/Data/Filter/FilterNotEqual.php @@ -0,0 +1,13 @@ +{$this->column} !== (string) $this->expression; + } +} diff --git a/library/Icinga/Protocol/File/Exception/FileReaderException.php b/library/Icinga/Protocol/File/Exception/FileReaderException.php index 3e0890a57..a7db5d701 100644 --- a/library/Icinga/Protocol/File/Exception/FileReaderException.php +++ b/library/Icinga/Protocol/File/Exception/FileReaderException.php @@ -2,9 +2,9 @@ namespace Icinga\Protocol\File; -use RuntimeException; +use Icinga\Exception\IcingaException; /** * Exception thrown if a file reader specific error occurs */ -class FileReaderException extends RuntimeException {} +class FileReaderException extends IcingaException {} diff --git a/library/Icinga/Protocol/File/Reader.php b/library/Icinga/Protocol/File/Reader.php index 50842bc4a..651846b58 100644 --- a/library/Icinga/Protocol/File/Reader.php +++ b/library/Icinga/Protocol/File/Reader.php @@ -40,7 +40,7 @@ class Reader extends FilterIterator { foreach (array('filename', 'fields') as $key) { if (! isset($config->{$key})) { - throw new FileReaderException('The directive `' . $key . '\' is required'); + throw new FileReaderException('The directive `%s\' is required', $key); } } $this->fields = $config->fields; diff --git a/library/Icinga/Protocol/Ldap/Connection.php b/library/Icinga/Protocol/Ldap/Connection.php index 531a399c8..6fa41e2aa 100644 --- a/library/Icinga/Protocol/Ldap/Connection.php +++ b/library/Icinga/Protocol/Ldap/Connection.php @@ -307,7 +307,7 @@ class Connection $results = @ldap_search( $this->ds, $base, - (string) $query, + $query->create(), $fields, 0, // Attributes and values 0 // No limit - at least where possible @@ -619,7 +619,7 @@ class Connection $result = @ldap_read( $ds, '', - (string) $query, + $query->create(), $query->listFields() ); diff --git a/library/Icinga/Protocol/Ldap/Query.php b/library/Icinga/Protocol/Ldap/Query.php index ca8f5519f..462c83d61 100644 --- a/library/Icinga/Protocol/Ldap/Query.php +++ b/library/Icinga/Protocol/Ldap/Query.php @@ -4,8 +4,6 @@ namespace Icinga\Protocol\Ldap; -use Icinga\Exception\IcingaException; - /** * Search class * @@ -84,7 +82,7 @@ class Query public function limit($count = null, $offset = null) { if (! preg_match('~^\d+~', $count . $offset)) { - throw new IcingaException( + throw new Exception( 'Got invalid limit: %s, %s', $count, $offset @@ -302,21 +300,11 @@ class Query * * @string */ - public function __toString() - { - return $this->render(); - } - - /** - * Returns the LDAP filter that will be applied - * - * @string - */ - protected function render() + public function create() { $parts = array(); if (! isset($this->filters['objectClass']) || $this->filters['objectClass'] === null) { - // throw new IcingaException('Object class is mandatory'); + throw new Exception('Object class is mandatory'); } foreach ($this->filters as $key => $value) { $parts[] = sprintf( diff --git a/library/Icinga/Web/Controller/ActionController.php b/library/Icinga/Web/Controller/ActionController.php index bfbb35b77..aead4043a 100644 --- a/library/Icinga/Web/Controller/ActionController.php +++ b/library/Icinga/Web/Controller/ActionController.php @@ -348,7 +348,9 @@ class ActionController extends Zend_Controller_Action // Cast preference app.show_benchmark to bool because preferences loaded from a preferences storage are // always strings if ((bool) $user->getPreferences()->get('app.show_benchmark', false) === true) { - $layout->benchmark = $this->renderBenchmark(); + if (!$this->_helper->viewRenderer->getNoRender()) { + $layout->benchmark = $this->renderBenchmark(); + } } } diff --git a/library/Icinga/Web/FileCache.php b/library/Icinga/Web/FileCache.php new file mode 100644 index 000000000..16c89a2a4 --- /dev/null +++ b/library/Icinga/Web/FileCache.php @@ -0,0 +1,275 @@ +name = $name; + $tmpdir = sys_get_temp_dir(); + $basedir = $tmpdir . '/FileCache_' . $name; + + if (file_exists($basedir) && is_writeable($basedir)) { + + $this->basedir = $basedir; + $this->enabled = true; + + } elseif (file_exists($tmpdir) && is_writeable($tmpdir)) { + + if (mkdir($basedir, '0750', true)) { + $this->enabled = true; + $this->basedir = $basedir; + } + + } + } + + /** + * Store the given content to the desired file name + * + * @param string $file new (relative) filename + * @param string $content the content to be stored + * + * @return bool whether the file has been stored + */ + public function store($file, $content) + { + if (! $this->enabled) { + + return false; + } + + return file_put_contents($this->filename($file), $content); + } + + /** + * Find out whether a given file exists + * + * @param string $file the (relative) filename + * @param int $newerThan optional timestamp to compare against + * + * @return bool whether such file exists + */ + public function has($file, $newerThan = null) + { + if (! $this->enabled) { + + return false; + } + + $filename = $this->filename($file); + + if (! file_exists($filename) || ! is_readable($filename)) { + + return false; + } + + if ($newerThan === null) { + + return true; + } + + $info = stat($file); + + if ($info === false) { + + return false; + } + + return (int) $newerThan < $info['mtime']; + } + + /** + * Get a specific file or false if no such file available + * + * @param string $file the disired file name + * + * @return string|bool Filename content or false + */ + public function get($file) + { + if ($this->has($file)) { + return file_get_contents($this->filename($file)); + } + + return false; + } + + /** + * Send a specific file to the browser (output) + * + * @param string $file the disired file name + * + * @return bool Whether the file has been sent + */ + public function send($file) + { + if ($this->has($file)) { + readfile($this->filename($file)); + + return true; + } + + return false; + } + + /** + * Get absolute filename for a given file + * + * @param string $file the disired file name + * + * @return string absolute filename + */ + protected function filename($file) + { + return $this->basedir . '/' . $file; + } + + /** + * Whether the given ETag matches a cached file + * + * If no ETag is given we'll try to fetch the one from the current + * HTTP request. + * + * @param string $file The cached file you want to check + * @param string $match The ETag to match against + * + * @return string|bool ETag on match, otherwise false + */ + public function etagMatchesCachedFile($file, $match = null) + { + return self::etagMatchesFiles($this->filename($file), $match); + } + + /** + * Create an ETag for the given file + * + * @param string $file The desired cache file + * + * @return string your ETag + */ + public function etagForCachedFile($file) + { + return self::etagForFiles($this->filename($file)); + } + + /** + * Whether the given ETag matchesspecific file(s) on disk + * + * If no ETag is given we'll try to fetch the one from the current + * HTTP request. + * + * @param string|array $files file(s) to check + * @param string $match ETag to match against + * + * @return string|bool ETag on match, otherwise false + */ + public static function etagMatchesFiles($files, $match = null) + { + if ($match === null) { + $match = isset($_SERVER['HTTP_IF_NONE_MATCH']) + ? trim($_SERVER['HTTP_IF_NONE_MATCH'], '"') + : false; + } + if (! $match) { + return false; + } + + $etag = self::etagForFiles($files); + return $match === $etag ? $etag : false; + } + + /** + * Create ETag for the given files + * + * Custom algorithm creating an ETag based on filenames, mtimes + * and file sizes. Supports single files or a list of files. This + * way we are able to create ETags for virtual files depending on + * multiple source files (e.g. compressed JS, CSS). + * + * @param string|array $files Single file or a list of such + * + * @return string The generated ETag + */ + public static function etagForFiles($files) + { + if (is_string($files)) { + $files = array($files); + } + + $sizes = array(); + $mtimes = array(); + + foreach ($files as $file) { + $file = realpath($file); + if ($file !== false && $info = stat($file)) { + $mtimes[] = $info['mtime']; + $sizes[] = $info['size']; + } else { + $mtimes[] = time(); + $sizes[] = 0; + } + } + + return sprintf( + '%s-%s-%s', + hash('crc32', implode('|', $files)), + hash('crc32', implode('|', $sizes)), + hash('crc32', implode('|', $mtimes)) + ); + } + + /** + * Factory creating your cache instance + * + * @param string $name Instance name + * + * @return FileCache + */ + public static function instance($name = 'icingaweb') + { + if ($name !== 'icingaweb') { + $name = 'icingaweb/modules/' . $name; + } + + if (!array_key_exists($name, self::$instances)) { + self::$instances[$name] = new static($name); + } + + return self::$instances[$name]; + } +} diff --git a/library/Icinga/Web/JavaScript.php b/library/Icinga/Web/JavaScript.php index 9eff9f5f0..096053512 100644 --- a/library/Icinga/Web/JavaScript.php +++ b/library/Icinga/Web/JavaScript.php @@ -5,6 +5,7 @@ namespace Icinga\Web; use Icinga\Application\Icinga; +use Icinga\Web\FileCache; use JShrink\Minifier; class JavaScript @@ -64,36 +65,58 @@ class JavaScript $js = $out = ''; $min = $minified ? '.min' : ''; - // TODO: Cache header - header('Content-Type: application/javascript'); - $cacheFile = '/tmp/cache_icinga' . $min . '.js'; - if (file_exists($cacheFile)) { - readfile($cacheFile); - exit; - } - - // We do not minify vendor files + // Prepare vendor file list + $vendorFiles = array(); foreach (self::$vendorFiles as $file) { - $out .= file_get_contents($basedir . '/' . $file . $min . '.js'); + $vendorFiles[] = $basedir . '/' . $file . $min . '.js'; } + // Prepare Icinga JS file list + $jsFiles = array(); foreach (self::$jsFiles as $file) { - $js .= file_get_contents($basedir . '/' . $file); + $jsFiles[] = $basedir . '/' . $file; } foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $name => $module) { if ($module->hasJs()) { - $js .= file_get_contents($module->getJsFilename()); + $jsFiles[] = $module->getJsFilename(); } } + $files = array_merge($vendorFiles, $jsFiles); + + if ($etag = FileCache::etagMatchesFiles($files)) { + header("HTTP/1.1 304 Not Modified"); + return; + } else { + $etag = FileCache::etagForFiles($files); + } + header('Cache-Control: public'); + header('ETag: "' . $etag . '"'); + header('Content-Type: application/javascript'); + + $cacheFile = 'icinga-' . $etag . $min . '.js'; + $cache = FileCache::instance(); + if ($cache->has($cacheFile)) { + $cache->send($cacheFile); + return; + } + + // We do not minify vendor files + foreach ($vendorFiles as $file) { + $out .= file_get_contents($file); + } + + foreach ($jsFiles as $file) { + $js .= file_get_contents($file); + } + if ($minified) { require_once 'IcingaVendor/JShrink/Minifier.php'; $out .= Minifier::minify($js, array('flaggedComments' => false)); } else { $out .= $js; } - // Not yet, this is for tests only. Waiting for Icinga\Web\Cache - // file_put_contents($cacheFile, $out); + $cache->store($cacheFile, $out); echo $out; } } diff --git a/library/Icinga/Web/Menu.php b/library/Icinga/Web/Menu.php index c5640ac09..2fa49b99c 100644 --- a/library/Icinga/Web/Menu.php +++ b/library/Icinga/Web/Menu.php @@ -5,7 +5,6 @@ namespace Icinga\Web; use Icinga\Exception\ConfigurationError; -use Icinga\Logger\Logger; use Zend_Config; use RecursiveIterator; use Icinga\Application\Config; @@ -173,34 +172,34 @@ class Menu implements RecursiveIterator */ protected function addMainMenuItems() { - $this->add('dashboard', t('Dashboard'), array( + $this->add(t('Dashboard'), array( 'url' => 'dashboard', 'icon' => 'img/icons/dashboard.png', 'priority' => 10 )); - $section = $this->add('system', t('System'), array( + $section = $this->add(t('System'), array( 'icon' => 'img/icons/configuration.png', 'priority' => 200 )); - $section->add('preferences', t('Preferences'), array( + $section->add(t('Preferences'), array( 'url' => 'preference', 'priority' => 200 )); - $section->add('configuration', t('Configuration'), array( + $section->add(t('Configuration'), array( 'url' => 'config', 'priority' => 300 )); - $section->add('modules', t('Modules'), array( + $section->add(t('Modules'), array( 'url' => 'config/modules', 'priority' => 400 )); - $section->add('applicationlog', t('ApplicationLog'), array( + $section->add(t('ApplicationLog'), array( 'url' => 'list/applicationlog', 'priority' => 500 )); - $this->add('logout', t('Logout'), array( + $this->add(t('Logout'), array( 'url' => 'authentication/logout', 'icon' => 'img/icons/logout.png', 'priority' => 300 @@ -428,10 +427,9 @@ class Menu implements RecursiveIterator * @param array $config * @return Menu */ - public function add($id, $name, $config = array()) + public function add($name, $config = array()) { - $config['title'] = $name; - return $this->addSubMenu($id, new Zend_Config($config)); + return $this->addSubMenu($name, new Zend_Config($config)); } /** diff --git a/library/Icinga/Web/Session.php b/library/Icinga/Web/Session.php index 58bfc70bd..4723903e9 100644 --- a/library/Icinga/Web/Session.php +++ b/library/Icinga/Web/Session.php @@ -47,7 +47,7 @@ class Session public static function getSession() { if (self::$session === null) { - throw new ProgrammingError('No session created yet'); + self::create(); } return self::$session; diff --git a/library/Icinga/Web/StyleSheet.php b/library/Icinga/Web/StyleSheet.php index 0a2a33bc9..4553b32ef 100644 --- a/library/Icinga/Web/StyleSheet.php +++ b/library/Icinga/Web/StyleSheet.php @@ -5,6 +5,7 @@ namespace Icinga\Web; use Icinga\Application\Icinga; +use Icinga\Web\FileCache; use Icinga\Web\LessCompiler; class StyleSheet @@ -46,28 +47,45 @@ class StyleSheet public static function send($minified = false) { + $app = Icinga::app(); + $basedir = $app->getBootstrapDirecory(); + foreach (self::$lessFiles as $file) { + $lessFiles[] = $basedir . '/' . $file; + } + $files = $lessFiles; + foreach ($app->getModuleManager()->getLoadedModules() as $name => $module) { + if ($module->hasCss()) { + $files[] = $module->getCssFilename(); + } + } + + if ($etag = FileCache::etagMatchesFiles($files)) { + header("HTTP/1.1 304 Not Modified"); + return; + } else { + $etag = FileCache::etagForFiles($files); + } + header('Cache-Control: public'); + header('ETag: "' . $etag . '"'); header('Content-Type: text/css'); $min = $minified ? '.min' : ''; - $cacheFile = '/tmp/cache_icinga' . $min . '.css'; - if (file_exists($cacheFile)) { - readfile($cacheFile); - exit; + $cacheFile = 'icinga-' . $etag . $min . '.css'; + $cache = FileCache::instance(); + if ($cache->has($cacheFile)) { + $cache->send($cacheFile); + return; } - $less = new LessCompiler(); - $basedir = Icinga::app()->getBootstrapDirecory(); - foreach (self::$lessFiles as $file) { - $less->addFile($basedir . '/' . $file); + foreach ($lessFiles as $file) { + $less->addFile($file); } $less->addLoadedModules(); if ($minified) { $less->compress(); } $out = $less->compile(); - // Not yet, this is for tests only. Waiting for Icinga\Web\Cache - // file_put_contents($cacheFile, $out); + $cache->store($cacheFile, $out); echo $out; - } } diff --git a/library/Icinga/Web/Widget/Dashboard.php b/library/Icinga/Web/Widget/Dashboard.php index d8b09b15a..a86204d87 100644 --- a/library/Icinga/Web/Widget/Dashboard.php +++ b/library/Icinga/Web/Widget/Dashboard.php @@ -8,7 +8,6 @@ use Icinga\Application\Icinga; use Icinga\Application\Config as IcingaConfig; use Icinga\Exception\ConfigurationError; use Icinga\Exception\ProgrammingError; -use Icinga\Web\Widget\AbstractWidget; use Icinga\Web\Widget\Dashboard\Pane; use Icinga\Web\Widget\Dashboard\Component as DashboardComponent; use Icinga\Web\Url; @@ -96,7 +95,7 @@ class Dashboard extends AbstractWidget $current = $this->panes[$pane->getName()]; $current->addComponents($pane->getComponents()); } else { - $this->panes = array_filter(array_merge($this->panes, $panes)); + $this->panes[$pane->getName()] = $pane; } } @@ -128,6 +127,16 @@ class Dashboard extends AbstractWidget return $this->tabs; } + /** + * Return all panes of this dashboard + * + * @return array + */ + public function getPanes() + { + return $this->panes; + } + /** * Populate this dashboard via the given configuration file * @@ -164,9 +173,9 @@ class Dashboard extends AbstractWidget * * @TODO: Should only allow component objects to be added directly as soon as we store more information * - * @param string $pane The pane to add the component to - * @param Component|string $component The component to add or the title of the newly created component - * @param $url The url to use for the component + * @param string $pane The pane to add the component to + * @param Component|string $component The component to add or the title of the newly created component + * @param string|null $url The url to use for the component * * @return self */ @@ -198,20 +207,14 @@ class Dashboard extends AbstractWidget } /** - * Return true if a pane doesn't exist or doesn't have any components in it - * - * @param string $pane The name of the pane to check for emptyness + * Check if this dashboard has a specific pane * + * @param $pane string The name of the pane * @return bool */ - public function isEmptyPane($pane) + public function hasPane($pane) { - $paneObj = $this->getPane($pane); - if ($paneObj === null) { - return true; - } - $cmps = $paneObj->getComponents(); - return !empty($cmps); + return array_key_exists($pane, $this->panes); } /** @@ -305,11 +308,11 @@ class Dashboard extends AbstractWidget return $active; } + /** + * @see determineActivePane() + */ public function getActivePane() { - if ($active = $this->getTabs()->getActiveName()) { - return $this->getPane($active); - } return $this->determineActivePane(); } @@ -323,10 +326,12 @@ class Dashboard extends AbstractWidget $active = $this->getTabs()->getActiveName(); if (! $active) { if ($active = Url::fromRequest()->getParam($this->tabParam)) { - if ($this->isEmptyPane($active)) { - $active = $this->setDefaultPane(); - } else { + if ($this->hasPane($active)) { $this->activate($active); + } else { + throw new ProgrammingError( + 'Try to get an inexistent pane.' + ); } } else { $active = $this->setDefaultPane(); diff --git a/library/Icinga/Web/Widget/Dashboard/Component.php b/library/Icinga/Web/Widget/Dashboard/Component.php index 71615c572..7410c6210 100644 --- a/library/Icinga/Web/Widget/Dashboard/Component.php +++ b/library/Icinga/Web/Widget/Dashboard/Component.php @@ -30,13 +30,6 @@ class Component extends AbstractWidget */ private $url; - /** - * The id of this Component - * - * @var string - */ - private $id; - /** * The title being displayed on top of the component * @var @@ -49,6 +42,13 @@ class Component extends AbstractWidget */ private $pane; + /** + * The disabled option is used to "delete" default dashlets provided by modules + * + * @var bool + */ + private $disabled = false; + /** * The template string used for rendering this widget * @@ -67,14 +67,12 @@ EOD; /** * Create a new component displaying the given url in the provided pane * - * @param string $id The id to use for this component * @param string $title The title to use for this component * @param Url|string $url The url this component uses for displaying information * @param Pane $pane The pane this Component will be added to */ - public function __construct($id, $title, $url, Pane $pane) + public function __construct($title, $url, Pane $pane) { - $this->id = $id; $this->title = $title; $this->pane = $pane; if ($url instanceof Url) { @@ -126,6 +124,26 @@ EOD; return $this; } + /** + * Set the disabled property + * + * @param boolean $disabled + */ + public function setDisabled($disabled) + { + $this->disabled = $disabled; + } + + /** + * Get the disabled property + * + * @return boolean + */ + public function getDisabled() + { + return $this->disabled; + } + /** * Return this component's structure as array * @@ -145,6 +163,10 @@ EOD; */ public function render() { + if ($this->disabled === true) { + return ''; + } + $view = $this->view(); $url = clone($this->url); $url->setParam('view', 'compact'); @@ -195,14 +217,14 @@ EOD; /** * Create a @see Component instance from the given Zend config, using the provided title - * @param $id The id for this component + * * @param $title The title for this component * @param Zend_Config $config The configuration defining url, parameters, height, width, etc. * @param Pane $pane The pane this component belongs to * * @return Component A newly created Component for use in the Dashboard */ - public static function fromIni($id, $title, Zend_Config $config, Pane $pane) + public static function fromIni($title, Zend_Config $config, Pane $pane) { $height = null; $width = null; @@ -210,27 +232,7 @@ EOD; $parameters = $config->toArray(); unset($parameters['url']); // otherwise there's an url = parameter in the Url - $cmp = new Component($id, $title, Url::fromPath($url, $parameters), $pane); + $cmp = new Component($title, Url::fromPath($url, $parameters), $pane); return $cmp; } - - /** - * Set the components id - * - * @param $id string - */ - public function setId($id) - { - $this->id = $id; - } - - /** - * Retrieve the components id - * - * @return string - */ - public function getId() - { - return $this->id; - } } diff --git a/library/Icinga/Web/Widget/Dashboard/Pane.php b/library/Icinga/Web/Widget/Dashboard/Pane.php index edb3b6b77..45bd9c558 100644 --- a/library/Icinga/Web/Widget/Dashboard/Pane.php +++ b/library/Icinga/Web/Widget/Dashboard/Pane.php @@ -39,7 +39,7 @@ class Pane extends AbstractWidget /** * Create a new pane * - * @param $name The pane to create + * @param string $name The pane to create */ public function __construct($name) { @@ -83,44 +83,54 @@ class Pane extends AbstractWidget /** * Return true if a component with the given title exists in this pane * - * @param string $id The id of the component to check for existence + * @param string $title The title of the component to check for existence * * @return bool */ - public function hasComponent($id) + public function hasComponent($title) { - return array_key_exists($id, $this->components); + return array_key_exists($title, $this->components); + } + + /** + * Checks if the current pane has any components + * + * @return bool + */ + public function hasComponents() + { + return ! empty($this->components); } /** * Return a component with the given name if existing * - * @param string $id The id of the component to return + * @param string $title The title of the component to return * * @return Component The component with the given title * @throws ProgrammingError If the component doesn't exist */ - public function getComponent($id) + public function getComponent($title) { - if ($this->hasComponent($id)) { - return $this->components[$id]; + if ($this->hasComponent($title)) { + return $this->components[$title]; } throw new ProgrammingError( 'Trying to access invalid component: %s', - $id + $title ); } /** - * Removes the component with the given id if it exists in this pane + * Removes the component with the given title if it exists in this pane * - * @param string $id The pane + * @param string $title The pane * @return Pane $this */ - public function removeComponent($id) + public function removeComponent($title) { - if ($this->hasComponent($id)) { - unset($this->components[$id]); + if ($this->hasComponent($title)) { + unset($this->components[$title]); } return $this; } @@ -146,7 +156,6 @@ class Pane extends AbstractWidget /** * Add a component to this pane, optionally creating it if $component is a string * - * @param string $id An unique Identifier * @param string|Component $component The component object or title * (if a new component will be created) * @param string|null $url An Url to be used when component is a string @@ -154,12 +163,12 @@ class Pane extends AbstractWidget * @return self * @throws \Icinga\Exception\ConfigurationError */ - public function addComponent($id, $component, $url = null) + public function addComponent($component, $url = null) { if ($component instanceof Component) { - $this->components[$component->getId()] = $component; - } elseif (is_string($id) && is_string($component) && $url !== null) { - $this->components[$id] = new Component($id, $component, $url, $this); + $this->components[$component->getTitle()] = $component; + } elseif (is_string($component) && $url !== null) { + $this->components[$component] = new Component($component, $url, $this); } else { throw new ConfigurationError('Invalid component added: %s', $component); } @@ -176,15 +185,15 @@ class Pane extends AbstractWidget { /* @var $component Component */ foreach ($components as $component) { - if (array_key_exists($component->getId(), $this->components)) { - if (preg_match('/-(\d+)$/', $component->getId(), $m)) { - $name = preg_replace('/-\d+$/', $m[1]++, $component->getId()); + if (array_key_exists($component->getTitle(), $this->components)) { + if (preg_match('/_(\d+)$/', $component->getTitle(), $m)) { + $name = preg_replace('/_\d+$/', $m[1]++, $component->getTitle()); } else { - $name = $component->getId() . '-2'; + $name = $component->getTitle() . '_2'; } $this->components[$name] = $component; } else { - $this->components[$component->getId()] = $component; + $this->components[$component->getTitle()] = $component; } } @@ -194,18 +203,17 @@ class Pane extends AbstractWidget /** * Add a component to the current pane * - * @param $id * @param $title - * @param null $url - * @return mixed + * @param $url + * @return Component * * @see addComponent() */ - public function add($id, $title, $url = null) + public function add($title, $url = null) { - $this->addComponent($id, $title, $url); + $this->addComponent($title, $url); - return $this->components[$id]; + return $this->components[$title]; } /** diff --git a/library/Icinga/Web/Widget/FilterEditor.php b/library/Icinga/Web/Widget/FilterEditor.php index 4ce6ac6e6..af46a40f2 100644 --- a/library/Icinga/Web/Widget/FilterEditor.php +++ b/library/Icinga/Web/Widget/FilterEditor.php @@ -183,9 +183,10 @@ class FilterEditor extends AbstractWidget { $name = 'sign_' . $filter->getId(); $signs = array( - '=' => '=', - '>' => '>', - '<' => '<', + '=' => '=', + '!=' => '!=', + '>' => '>', + '<' => '<', '>=' => '>=', '<=' => '<=', ); diff --git a/modules/doc/configuration.php b/modules/doc/configuration.php index 06e900300..ce3f99113 100644 --- a/modules/doc/configuration.php +++ b/modules/doc/configuration.php @@ -4,7 +4,8 @@ /* @var $this \Icinga\Application\Modules\Module */ -$section = $this->menuSection('documentation', $this->translate('Documentation'), array( +$section = $this->menuSection($this->translate('Documentation'), array( + 'title' => 'Documentation', 'icon' => 'img/icons/comment.png', 'url' => 'doc', 'priority' => 80 diff --git a/modules/monitoring/application/controllers/ListController.php b/modules/monitoring/application/controllers/ListController.php index e123cdb0f..b71d78acf 100644 --- a/modules/monitoring/application/controllers/ListController.php +++ b/modules/monitoring/application/controllers/ListController.php @@ -34,8 +34,21 @@ class Monitoring_ListController extends Controller protected function hasBetterUrl() { + $request = $this->getRequest(); $url = clone($this->url); + if ($this->getRequest()->isPost()) { + + if ($request->getPost('sort')) { + $url->setParam('sort', $request->getPost('sort')); + if ($request->getPost('dir')) { + $url->setParam('dir', $request->getPost('dir')); + } else { + $url->removeParam('dir'); + } + return $url; + } + $q = $this->getRequest()->getPost('q'); } else { $q = $url->shift('q'); @@ -488,15 +501,8 @@ class Monitoring_ListController extends Controller $request = $this->getRequest(); $limit = $params->shift('limit'); - - $sort = null; - $dir = null; - if ($request->isPost()) { - $sort = $request->getPost('sort', null); - $dir = $request->getPost('dir', null); - } - $sort = $params->shift('sort', $sort); - $dir = $params->shift('dir', $dir); + $sort = $params->shift('sort'); + $dir = $params->shift('dir'); $page = $params->shift('page'); $format = $params->shift('format'); $view = $params->shift('view'); @@ -533,7 +539,9 @@ class Monitoring_ListController extends Controller $query->applyFilter($filter); } $this->view->filter = $filter; - $query->order($sort, $dir); + if ($sort) { + $query->order($sort, $dir); + } $this->applyRestrictions($query); $this->handleFormatRequest($query); return $query; diff --git a/modules/monitoring/application/controllers/ShowController.php b/modules/monitoring/application/controllers/ShowController.php index 1ad0f29dd..0200284ce 100644 --- a/modules/monitoring/application/controllers/ShowController.php +++ b/modules/monitoring/application/controllers/ShowController.php @@ -89,9 +89,8 @@ class Monitoring_ShowController extends Controller $this->getTabs()->activate('history'); //$this->view->object->populate(); $this->view->object->fetchEventHistory(); + $this->view->history = $this->view->object->eventhistory->paginate($this->params->get('limit', 50)); $this->handleFormatRequest($this->view->object->eventhistory); - $this->view->history = $this->view->object->eventhistory - ->paginate($this->params->get('limit', 50)); } public function servicesAction() diff --git a/modules/monitoring/application/controllers/TimelineController.php b/modules/monitoring/application/controllers/TimelineController.php index 2f9b1339d..3fa621f3e 100644 --- a/modules/monitoring/application/controllers/TimelineController.php +++ b/modules/monitoring/application/controllers/TimelineController.php @@ -2,9 +2,9 @@ // {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}} -use DateTime; -use DateInterval; -use Zend_Config; +use \DateTime; +use \DateInterval; +use \Zend_Config; use Icinga\Web\Url; use Icinga\Util\Format; use Icinga\Application\Config; diff --git a/modules/monitoring/application/views/helpers/MonitoringProperties.php b/modules/monitoring/application/views/helpers/MonitoringProperties.php deleted file mode 100644 index ed52c5a04..000000000 --- a/modules/monitoring/application/views/helpers/MonitoringProperties.php +++ /dev/null @@ -1,284 +0,0 @@ - 'Current Attempt', - 'buildCheckType' => 'Check Type', - 'buildLatency' => 'Check Latency / Duration', - 'buildLastStateChange' => 'Last State Change', - 'buildLastNotification' => 'Last Notification', - 'buildFlapping' => 'Is This %s Flapping?', - 'buildScheduledDowntime' => 'In Scheduled Downtime?', - 'status_update_time' => 'Last Update' - ); - - private static $notificationReasons = array( - 0 => 'NORMAL', - 1 => 'ACKNOWLEDGEMENT', - 2 => 'FLAPPING START', - 3 => 'FLAPPING STOP', - 4 => 'FLAPPING DISABLED', - 5 => 'DOWNTIME START', - 6 => 'DOWNTIME END', - 7 => 'DOWNTIME CANCELLED', - 8 => 'CUSTOM', - 9 => 'STALKING' - ); - - /** - * Return the object type - * @param stdClass $object - * @return mixed - */ - private function getObjectType($object) - { - $keys = array_keys(get_object_vars($object)); - $keyParts = explode('_', array_shift($keys), 2); - return array_shift($keyParts); - } - - /** - * Drop all object specific attribute prefixes - * @param stdClass $object - * @param $type - * @return object - */ - private function dropObjectType($object, $type) - { - $vars = get_object_vars($object); - $out = array(); - foreach ($vars as $name => $value) { - $name = str_replace($type. '_', '', $name); - $out[$name] = $value; - } - return (object)$out; - } - - /** - * Get string for attempt - * @param stdClass $object - * @return string - */ - private function buildAttempt($object) - { - return sprintf( - '%s/%s (%s state)', - $object->current_check_attempt, - $object->max_check_attempts, - ($object->state_type === '1') ? 'HARD' : 'SOFT' - ); - } - - /** - * Generic fomatter for float values - * @param $value - * @return string - */ - private function floatFormatter($value) - { - return sprintf('%.4f', $value); - } - - /** - * Get the string for check type - * @param stdClass $object - * @return string - */ - private function buildCheckType($object) - { - if ($object->passive_checks_enabled === '1' && $object->active_checks_enabled === '0') { - return self::CHECK_PASSIVE; - } elseif ($object->passive_checks_enabled === '0' && $object->active_checks_enabled === '0') { - return self::CHECK_DISABLED; - } - - return self::CHECK_ACTIVE; - } - - /** - * Get string for latency - * @param stdClass $object - * @return string - */ - private function buildLatency($object) - { - $val = ''; - if ($this->buildCheckType($object) === self::CHECK_PASSIVE) { - $val .= self::VALUE_NA; - } else { - $val .= $this->floatFormatter( - (isset($object->check_latency)) ? $object->check_latency : 0 - ); - } - - $val .= ' / '. $this->floatFormatter( - isset($object->check_execution_time) ? $object->check_execution_time : 0 - ). ' seconds'; - - return $val; - } - - /** - * Get string for next check - * @param stdClass $object - * @return string - */ - private function buildNextCheck($object) - { - if ($this->buildCheckType($object) === self::CHECK_PASSIVE) { - return self::VALUE_NA; - } else { - return $object->next_check; - } - } - - /** - * Get date for last state change - * @param stdClass $object - * @return string - */ - private function buildLastStateChange($object) - { - return strftime('%Y-%m-%d %H:%M:%S', $object->last_state_change); - } - - /** - * Get string for "last notification" - * @param stdClass $object - * @return string - */ - private function buildLastNotification($object) - { - $val = ''; - - if ($object->last_notification === '0000-00-00 00:00:00') { - $val .= self::VALUE_NA; - } else { - $val .= $object->last_notification; - } - - $val .= sprintf(' (notification %d)', $object->current_notification_number); - - return $val; - } - - /** - * Get string for "is flapping" - * @param stdClass $object - * @return string - */ - private function buildFlapping($object) - { - $val = ''; - - if ($object->is_flapping === '0') { - $val .= self::VALUE_NO; - } else { - $val .= self::VALUE_YES; - } - - $val .= sprintf(' (%.2f%% state change)', $object->percent_state_change); - - return $val; - } - - /** - * Get string for scheduled downtime - * @param stdClass $object - * @return string - */ - private function buildScheduledDowntime($object) - { - if ($object->in_downtime === '1') { - return self::VALUE_YES; - } - - return self::VALUE_NO; - } - - /** - * Get an array which represent monitoring properties - * - * @param stdClass $object - * @return array - */ - public function monitoringProperties($object) - { - $type = $this->getObjectType($object); - //$object = $this->dropObjectType($object, $type); - - $out = array(); - foreach (self::$keys as $property => $label) { - $label = sprintf($label, ucfirst($type)); - if (is_callable(array(&$this, $property))) { - $out[$label] = $this->$property($object); - } elseif (isset($object->{$property})) { - $out[$label] = $object->{$property}; - } - } - - return $out; - } - - public function getNotificationType($notification) - { - $reason = intval($notification->notification_reason); - if (!isset(self::$notificationReasons[$reason])) { - return 'N/A'; - } - $type = self::$notificationReasons[$reason]; - if ($reason === 8) { - if (intval($notification->notification_type) === 0) { - $type .= '(UP)'; - } else { - $type .= '(OK)'; - } - } - return $type; - } -} diff --git a/modules/monitoring/configuration.php b/modules/monitoring/configuration.php index ed5251ec1..412581d24 100644 --- a/modules/monitoring/configuration.php +++ b/modules/monitoring/configuration.php @@ -22,73 +22,72 @@ $this->provideConfigTab('security', array( /* * Problems Section */ -$section = $this->menuSection('problems', $this->translate('Problems'), array( +$section = $this->menuSection($this->translate('Problems'), array( 'icon' => 'img/icons/error.png', 'priority' => 20 )); -$section->add('unhandled hosts', $this->translate('Unhandled Hosts'), array( +$section->add($this->translate('Unhandled Hosts'), array( 'url' => 'monitoring/list/hosts?host_problem=1&host_handled=0', 'priority' => 40 )); -$section->add('unhandled services', $this->translate('Unhandled Services'), array( +$section->add($this->translate('Unhandled Services'), array( 'url' => 'monitoring/list/services?service_problem=1&service_handled=0&sort=service_severity', 'priority' => 40 )); -$section->add('host problems', $this->translate('Host Problems'), array( +$section->add($this->translate('Host Problems'), array( 'url' => 'monitoring/list/hosts?host_problem=1&sort=host_severity', 'priority' => 50 )); -$section->add('service prolems', $this->translate('Service Problems'), array( +$section->add($this->translate('Service Problems'), array( 'url' => 'monitoring/list/services?service_problem=1&sort=service_severity&dir=desc', 'priority' => 50 )); -$section->add('current downtimes', $this->translate('Current Downtimes')) - ->setUrl('monitoring/list/downtimes?downtime_is_in_effect=1'); +$section->add($this->translate('Current Downtimes'))->setUrl('monitoring/list/downtimes?downtime_is_in_effect=1'); /* * Overview Section */ -$section = $this->menuSection('overview', $this->translate('Overview'), array( +$section = $this->menuSection($this->translate('Overview'), array( 'icon' => 'img/icons/hostgroup.png', 'priority' => 30 )); -$section->add('tactical overview', $this->translate('Tactical Overview'), array( +$section->add($this->translate('Tactical Overview'), array( 'url' => 'monitoring/tactical', 'priority' => 40 )); -$section->add('hosts', $this->translate('Hosts'), array( +$section->add($this->translate('Hosts'), array( 'url' => 'monitoring/list/hosts', 'priority' => 50 )); -$section->add('services', $this->translate('Services'), array( +$section->add($this->translate('Services'), array( 'url' => 'monitoring/list/services', 'priority' => 50 )); -$section->add('servicematrix', $this->translate('Servicematrix'), array( +$section->add($this->translate('Servicematrix'), array( 'url' => 'monitoring/list/servicematrix?service_problem=1', 'priority' => 51 )); -$section->add('servicegroups', $this->translate('Servicegroups'), array( +$section->add($this->translate('Servicegroups'), array( 'url' => 'monitoring/list/servicegroups', 'priority' => 60 )); -$section->add('hostgroups', $this->translate('Hostgroups'), array( +$section->add($this->translate('Hostgroups'), array( 'url' => 'monitoring/list/hostgroups', 'priority' => 60 )); -$section->add('contactgroups', $this->translate('Contactgroups'), array( +$section->add($this->translate('Contactgroups'), array( 'url' => 'monitoring/list/contactgroups', 'priority' => 61 )); -$section->add('downtimes', $this->translate('Downtimes'), array( +$section->add($this->translate('Downtimes'), array( 'url' => 'monitoring/list/downtimes', 'priority' => 71 )); -$section->add('comments', $this->translate('Comments'), array( +$section->add($this->translate('Comments'), array( 'url' => 'monitoring/list/comments?comment_type=(comment|ack)', 'priority' => 70 )); -$section->add('contacts', $this->translate('Contacts'), array( +$section->add($this->translate('Contacts'), array( 'url' => 'monitoring/list/contacts', 'priority' => 70 )); @@ -96,33 +95,31 @@ $section->add('contacts', $this->translate('Contacts'), array( /* * History Section */ -$section = $this->menuSection('history', $this->translate('History'), array( - 'title' => $this->translate('History'), +$section = $this->menuSection($this->translate('History'), array( 'icon' => 'img/icons/history.png' )); -$section->add('critical events', $this->translate('Critical Events'), array( - 'title' => $this->translate('Critical Events'), +$section->add($this->translate('Critical Events'), array( 'url' => 'monitoring/list/statehistorysummary', 'priority' => 50 )); -$section->add('notifications', $this->translate('Notifications'), array( +$section->add($this->translate('Notifications'), array( 'url' => 'monitoring/list/notifications' )); -$section->add('events', $this->translate('Events'), array( +$section->add($this->translate('Events'), array( 'title' => $this->translate('All Events'), 'url' => 'monitoring/list/eventhistory?timestamp>=-7%20days' )); -$section->add('timeline', $this->translate('Timeline'))->setUrl('monitoring/timeline'); +$section->add($this->translate('Timeline'))->setUrl('monitoring/timeline'); /* * System Section */ -$section = $this->menuSection('system', $this->translate('System')); -$section->add('process info', $this->translate('Process Info'), array( +$section = $this->menuSection($this->translate('System')); +$section->add($this->translate('Process Info'), array( 'url' => 'monitoring/process/info', 'priority' => 120 )); -$section->add('performance info', $this->translate('Performance Info'), array( +$section->add($this->translate('Performance Info'), array( 'url' => 'monitoring/process/performance', 'priority' => 130 )); @@ -130,19 +127,16 @@ $section->add('performance info', $this->translate('Performance Info'), array( /* * Dashboard */ -$dashboard = $this->dashboard('current incidents')->setTitle($this->translate('Current Incidents')); +$dashboard = $this->dashboard($this->translate('Current Incidents')); $dashboard->add( - 'service problems', $this->translate('Service Problems'), 'monitoring/list/services?service_problem=1&limit=10&sort=service_severity' ); $dashboard->add( - 'recently recovered services', $this->translate('Recently Recovered Services'), 'monitoring/list/services?service_state=0&limit=10&sort=service_last_state_change&dir=desc' ); $dashboard->add( - 'host problems', $this->translate('Host Problems'), 'monitoring/list/hosts?host_problem=1&sort=host_severity' ); diff --git a/modules/monitoring/library/Monitoring/Backend.php b/modules/monitoring/library/Monitoring/Backend.php index 34be4aff5..454a33cef 100644 --- a/modules/monitoring/library/Monitoring/Backend.php +++ b/modules/monitoring/library/Monitoring/Backend.php @@ -69,33 +69,31 @@ class Backend implements Selectable, Queryable, ConnectionInterface */ public static function createBackend($backendName = null) { - $allBackends = array(); - $defaultBackend = null; - foreach (IcingaConfig::module('monitoring', 'backends') as $name => $config) { - if (!(bool) $config->get('disabled', false) && $defaultBackend === null) { - $defaultBackend = $config; - } - $allBackends[$name] = $config; + $config = IcingaConfig::module('monitoring', 'backends'); + if ($config->count() === 0) { + throw new ConfigurationError(t('No backend has been configured')); } - if (empty($allBackends)) { - throw new ConfigurationError('No backend has been configured'); - } - if ($defaultBackend === null) { - throw new ConfigurationError('All backends are disabled'); - } - if ($backendName === null) { - $backendConfig = $defaultBackend; - } else { - if (!array_key_exists($backendName, $allBackends)) { + if ($backendName !== null) { + $backendConfig = $config->get($backendName); + if ($backendConfig === null) { throw new ConfigurationError('No configuration for backend %s', $backendName); } - $backendConfig = $allBackends[$backendName]; - if ((bool) $backendConfig->get('disabled', false)) { + if ((bool) $backendConfig->get('disabled', false) === true) { throw new ConfigurationError( - 'Configuration for backend %s available but backend is disabled', + t('Configuration for backend %s available but backend is disabled'), $backendName ); } + } else { + foreach ($config as $name => $backendConfig) { + if ((bool) $backendConfig->get('disabled', false) === false) { + $backendName = $name; + break; + } + } + if ($backendName === null) { + throw new ConfigurationError(t('All backends are disabled')); + } } $resource = ResourceFactory::create($backendConfig->resource); if ($backendConfig->type === 'ido' && $resource->getDbType() !== 'oracle') { diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommandQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommandQuery.php index 664cb3576..c730c078e 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommandQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommandQuery.php @@ -51,10 +51,12 @@ class CommandQuery extends IdoQuery { $this->select->join( array('cnc' => $this->prefix . 'contact_notificationcommands'), - 'cnc.command_object_id = co.object_id' + 'cnc.command_object_id = co.object_id', + array() )->join( array('con' => $this->prefix . 'contacts'), - 'con.contact_id = cnc.contact_id' + 'con.contact_id = cnc.contact_id', + array() ); } } \ No newline at end of file diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentdeletionhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentdeletionhistoryQuery.php index 8149c1c67..4ee17de6a 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentdeletionhistoryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/CommentdeletionhistoryQuery.php @@ -45,7 +45,7 @@ class CommentdeletionhistoryQuery extends IdoQuery array() )->join( array('h' => $this->prefix . 'commenthistory'), - 'o.' . $this->object_id . ' = h.' . $this->object_id . " AND o.is_active = 1 AND h.deletion_time > '1970-01-01 00:00:00' AND h.entry_type <> 2", + 'o.' . $this->object_id . ' = h.' . $this->object_id . " AND o.is_active = 1 AND h.deletion_time > '1970-01-02 00:00:00' AND h.entry_type <> 2", array() ); $this->joinedVirtualTables = array('commenthistory' => true); diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeQuery.php index 1f5e17c6c..fe1152076 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeQuery.php @@ -23,8 +23,8 @@ class DowntimeQuery extends IdoQuery 'downtime_triggered_by_id' => 'sd.triggered_by_id', 'downtime_scheduled_start' => 'UNIX_TIMESTAMP(sd.scheduled_start_time)', 'downtime_scheduled_end' => 'UNIX_TIMESTAMP(sd.scheduled_end_time)', - 'downtime_start' => "UNIX_TIMESTAMP(CASE WHEN sd.trigger_time != '0000-00-00 00:00:00' then sd.trigger_time ELSE sd.scheduled_start_time END)", - 'downtime_end' => 'CASE WHEN sd.is_fixed THEN UNIX_TIMESTAMP(sd.scheduled_end_time) ELSE UNIX_TIMESTAMP(sd.trigger_time) + sd.duration END', + 'downtime_start' => "UNIX_TIMESTAMP(CASE WHEN UNIX_TIMESTAMP(sd.trigger_time) > 0 then sd.trigger_time ELSE sd.scheduled_start_time END)", + 'downtime_end' => 'CASE WHEN sd.is_fixed > 0 THEN UNIX_TIMESTAMP(sd.scheduled_end_time) ELSE UNIX_TIMESTAMP(sd.trigger_time) + sd.duration END', 'downtime_duration' => 'sd.duration', 'downtime_is_in_effect' => 'sd.is_in_effect', 'downtime_internal_id' => 'sd.internal_downtime_id', diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeendhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeendhistoryQuery.php index 5d600be03..f9ec831a7 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeendhistoryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimeendhistoryQuery.php @@ -47,7 +47,7 @@ class DowntimeendhistoryQuery extends IdoQuery array('h' => $this->prefix . 'downtimehistory'), 'o.' . $this->object_id . ' = h.' . $this->object_id . ' AND o.is_active = 1', array() - )->where('h.actual_end_time > ?', '1970-01-01 00:00:00'); + )->where('h.actual_end_time > ?', '1970-01-02 00:00:00'); $this->joinedVirtualTables = array('downtimehistory' => true); } } diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimestarthistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimestarthistoryQuery.php index e5a1bb8dc..01c56cd4d 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimestarthistoryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/DowntimestarthistoryQuery.php @@ -47,7 +47,7 @@ class DowntimestarthistoryQuery extends IdoQuery array('h' => $this->prefix . 'downtimehistory'), 'o.' . $this->object_id . ' = h.' . $this->object_id . ' AND o.is_active = 1', array() - )->where('h.actual_start_time > ?', '1970-01-01 00:00:00'); + )->where('h.actual_start_time > ?', '1970-01-02 00:00:00'); $this->joinedVirtualTables = array('downtimehistory' => true); } } diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php index 914441d03..3b7fdaa06 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/GroupsummaryQuery.php @@ -4,6 +4,7 @@ namespace Icinga\Module\Monitoring\Backend\Ido\Query; +use Icinga\Logger\Logger; use Zend_Db_Select; class GroupSummaryQuery extends IdoQuery @@ -69,8 +70,15 @@ class GroupSummaryQuery extends IdoQuery ) ); + $groupColumn = 'hostgroup'; + + if (in_array('servicegroup', $this->desiredColumns)) { + $groupColumn = 'servicegroup'; + } + $union = $this->db->select()->union(array($hosts, $services), Zend_Db_Select::SQL_UNION_ALL); - $this->select->from(array('statussummary' => $union), '*')->group($columns[0]); + $this->select->from(array('statussummary' => $union), array($groupColumn))->group(array($groupColumn)); + $this->joinedVirtualTables = array( 'servicestatussummary' => true, 'hoststatussummary' => true diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php index e557f4c7b..64c7e69b8 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/NotificationhistoryQuery.php @@ -85,7 +85,8 @@ class NotificationhistoryQuery extends IdoQuery $this->select->group('n.object_id') ->group('n.start_time') ->group('n.output') - ->group('n.state'); + ->group('n.state') + ->group('o.objecttype_id'); } $this->joinedVirtualTables = array('history' => true); diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php index 2006228b1..d3aca9934 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php @@ -41,7 +41,7 @@ class StatusQuery extends IdoQuery 'host_next_check' => 'CASE hs.should_be_scheduled WHEN 1 THEN UNIX_TIMESTAMP(hs.next_check) ELSE NULL END', 'host_check_execution_time' => 'hs.execution_time', 'host_check_latency' => 'hs.latency', - 'host_problem' => 'CASE WHEN hs.current_state = 0 THEN 0 ELSE 1 END', + 'host_problem' => 'CASE WHEN COALESCE(hs.current_state, 0) = 0 THEN 0 ELSE 1 END', 'host_notifications_enabled' => 'hs.notifications_enabled', @@ -278,20 +278,41 @@ class StatusQuery extends IdoQuery ELSE 0 END' ), + 'serviceproblemsummary' => array( 'host_unhandled_services' => 'sps.unhandled_services_count' ), - 'lasthostcomment' => array( - 'host_last_comment' => 'hlc.last_comment_data', - 'host_last_downtime' => 'hlc.last_downtime_data', - 'host_last_flapping' => 'hlc.last_flapping_data', - 'host_last_ack' => 'hlc.last_ack_data', + + 'lasthostcommentgeneric' => array( + 'host_last_comment' => 'hlcg.last_comment_data' ), - 'lastservicecomment' => array( - 'service_last_comment' => 'slc.last_comment_data', - 'service_last_downtime' => 'slc.last_downtime_data', - 'service_last_flapping' => 'slc.last_flapping_data', - 'service_last_ack' => 'slc.last_ack_data', + + 'lasthostcommentdowntime' => array( + 'host_last_downtime' => 'hlcd.last_downtime_data' + ), + + 'lasthostcommentflapping' => array( + 'host_last_flapping' => 'hlcf.last_flapping_data' + ), + + 'lasthostcommentack' => array( + 'host_last_ack' => 'hlca.last_ack_data' + ), + + 'lastservicecommentgeneric' => array( + 'service_last_comment' => 'slcg.last_comment_data' + ), + + 'lastservicecommentdowntime' => array( + 'service_last_downtime' => 'slcd.last_downtime_data' + ), + + 'lastservicecommentflapping' => array( + 'service_last_flapping' => 'slcf.last_flapping_data' + ), + + 'lastservicecommentack' => array( + 'service_last_ack' => 'slca.last_ack_data' ) ); @@ -483,41 +504,117 @@ class StatusQuery extends IdoQuery ); } - protected function getLastCommentSubQuery() + /** + * Create a subquery to join comments into status query + * @param int $entryType + * @param string $fieldName + * @return Zend_Db_Expr + */ + protected function getLastCommentSubQuery($entryType, $fieldName) { $sub = '(SELECT' - . ' lc.object_id,' - . " CASE WHEN lc.entry_type = 1 THEN CONCAT('[' || c.author_name || '] ' || c.comment_data) ELSE NULL END AS last_comment_data," - . " CASE WHEN lc.entry_type = 2 THEN CONCAT('[' || c.author_name || '] ' || c.comment_data) ELSE NULL END AS last_downtime_data," - . " CASE WHEN lc.entry_type = 3 THEN CONCAT('[' || c.author_name || '] ' || c.comment_data) ELSE NULL END AS last_flapping_data," - . " CASE WHEN lc.entry_type = 4 THEN CONCAT('[' || c.author_name || '] ' || c.comment_data) ELSE NULL END AS last_ack_data" - . ' FROM icinga_comments c' - . ' JOIN (SELECT' - . ' MAX(comment_id) as comment_id,' - . ' object_id,' - . ' entry_type' - . ' FROM icinga_comments' - . ' WHERE entry_type = 1 OR entry_type = 4' - . ' GROUP BY object_id, entry_type' - . ') lc ON lc.comment_id = c.comment_id GROUP BY lc.object_id)'; + . ' c.object_id,' + . " '[' || c.author_name || '] ' || c.comment_data AS $fieldName" + . ' FROM icinga_comments c JOIN (' + . ' SELECT MAX(comment_id) AS comment_id, object_id FROM icinga_comments' + . ' WHERE entry_type = ' . $entryType . ' GROUP BY object_id' + . ' ) lc ON c.comment_id = lc.comment_id)'; + return new Zend_Db_Expr($sub); } - protected function joinLasthostcomment() + /** + * Join last host comment + */ + protected function joinLasthostcommentgeneric() { $this->select->joinLeft( - array('hlc' => $this->getLastCommentSubQuery()), - 'hlc.object_id = hs.host_object_id', + array('hlcg' => $this->getLastCommentSubQuery(1, 'last_comment_data')), + 'hlcg.object_id = hs.host_object_id', array() ); } - // TODO: Terribly slow. As I have no idea of how to fix this we should remove it. - protected function joinLastservicecomment() + /** + * Join last host downtime comment + */ + protected function joinLasthostcommentdowntime() { $this->select->joinLeft( - array('slc' => $this->getLastCommentSubQuery()), - 'slc.object_id = ss.service_object_id', + array('hlcd' => $this->getLastCommentSubQuery(2, 'last_downtime_data')), + 'hlcg.object_id = hs.host_object_id', + array() + ); + } + + /** + * Join last host flapping comment + */ + protected function joinLastHostcommentflapping() + { + $this->select->joinLeft( + array('hlcf' => $this->getLastCommentSubQuery(3, 'last_flapping_data')), + 'hlcg.object_id = hs.host_object_id', + array() + ); + } + + /** + * Join last host acknowledgement comment + */ + protected function joinLasthostcommentack() + { + $this->select->joinLeft( + array('hlca' => $this->getLastCommentSubQuery(4, 'last_ack_data')), + 'hlca.object_id = hs.host_object_id', + array() + ); + } + + /** + * Join last service comment + */ + protected function joinLastservicecommentgeneric() + { + $this->select->joinLeft( + array('slcg' => $this->getLastCommentSubQuery(1, 'last_comment_data')), + 'slcg.object_id = ss.service_object_id', + array() + ); + } + + /** + * Join last service downtime comment + */ + protected function joinLastservicecommentdowntime() + { + $this->select->joinLeft( + array('slcd' => $this->getLastCommentSubQuery(2, 'last_downtime_data')), + 'slcd.object_id = ss.service_object_id', + array() + ); + } + + /** + * Join last service flapping comment + */ + protected function joinLastservicecommentflapping() + { + $this->select->joinLeft( + array('slcf' => $this->getLastCommentSubQuery(3, 'last_flapping_data')), + 'slcf.object_id = ss.service_object_id', + array() + ); + } + + /** + * Join last service acknowledgement comment + */ + protected function joinLastservicecommentack() + { + $this->select->joinLeft( + array('slca' => $this->getLastCommentSubQuery(4, 'last_ack_data')), + 'slca.object_id = ss.service_object_id', array() ); } diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusSummaryQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusSummaryQuery.php index 75db67b55..6555010c2 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusSummaryQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusSummaryQuery.php @@ -33,23 +33,27 @@ class StatusSummaryQuery extends IdoQuery 'hosts_flapping' => 'SUM(CASE WHEN object_type = \'host\' AND is_flapping = 1 THEN 1 ELSE 0 END)' ), 'servicestatussummary' => array( + 'services_total' => 'SUM(CASE WHEN object_type = \'service\' THEN 1 ELSE 0 END)', + 'services_problem' => 'SUM(CASE WHEN object_type = \'service\' AND state > 0 THEN 1 ELSE 0 END)', + 'services_problem_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state > 0 AND (acknowledged + in_downtime + host_problem) > 0 THEN 1 ELSE 0 END)', + 'services_problem_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state > 0 AND (acknowledged + in_downtime + host_problem) = 0 THEN 1 ELSE 0 END)', 'services_ok' => 'SUM(CASE WHEN object_type = \'service\' AND state = 0 THEN 1 ELSE 0 END)', 'services_ok_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 0 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', 'services_pending' => 'SUM(CASE WHEN object_type = \'service\' AND state = 99 THEN 1 ELSE 0 END)', 'services_pending_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 99 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', 'services_warning' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 THEN 1 ELSE 0 END)', - 'services_warning_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) > 0 THEN 1 ELSE 0 END)', - 'services_warning_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) = 0 THEN 1 ELSE 0 END)', + 'services_warning_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND (acknowledged + in_downtime + host_problem) > 0 THEN 1 ELSE 0 END)', + 'services_warning_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND (acknowledged + in_downtime + host_problem) = 0 THEN 1 ELSE 0 END)', 'services_warning_passive' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', 'services_warning_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 1 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', 'services_critical' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 THEN 1 ELSE 0 END)', - 'services_critical_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) > 0 THEN 1 ELSE 0 END)', - 'services_critical_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) = 0 THEN 1 ELSE 0 END)', + 'services_critical_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND (acknowledged + in_downtime + host_problem) > 0 THEN 1 ELSE 0 END)', + 'services_critical_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND (acknowledged + in_downtime + host_problem) = 0 THEN 1 ELSE 0 END)', 'services_critical_passive' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', 'services_critical_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 2 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', 'services_unknown' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 THEN 1 ELSE 0 END)', - 'services_unknown_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) > 0 THEN 1 ELSE 0 END)', - 'services_unknown_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND (acknowledged + in_downtime + COALESCE(host_state, 0)) = 0 THEN 1 ELSE 0 END)', + 'services_unknown_handled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND (acknowledged + in_downtime + host_problem) > 0 THEN 1 ELSE 0 END)', + 'services_unknown_unhandled' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND (acknowledged + in_downtime + host_problem) = 0 THEN 1 ELSE 0 END)', 'services_unknown_passive' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND is_passive_checked = 1 THEN 1 ELSE 0 END)', 'services_unknown_not_checked' => 'SUM(CASE WHEN object_type = \'service\' AND state = 3 AND is_active_checked = 0 AND is_passive_checked = 0 THEN 1 ELSE 0 END)', 'services_active' => 'SUM(CASE WHEN object_type = \'service\' AND is_active_checked = 1 THEN 1 ELSE 0 END)', @@ -131,6 +135,7 @@ class StatusSummaryQuery extends IdoQuery 'acknowledged' => 'hs.problem_has_been_acknowledged', 'in_downtime' => 'CASE WHEN (hs.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END', 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END', + 'host_problem' => 'CASE WHEN COALESCE(hs.current_state, 0) = 0 THEN 0 ELSE 1 END', 'is_passive_checked' => 'CASE WHEN hs.active_checks_enabled = 0 AND hs.passive_checks_enabled = 1 THEN 1 ELSE 0 END', 'is_active_checked' => 'hs.active_checks_enabled', 'is_processing_events' => 'hs.event_handler_enabled', @@ -144,6 +149,7 @@ class StatusSummaryQuery extends IdoQuery 'acknowledged' => 'ss.problem_has_been_acknowledged', 'in_downtime' => 'CASE WHEN (ss.scheduled_downtime_depth = 0) THEN 0 ELSE 1 END', 'host_state' => 'CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL THEN 99 ELSE hs.current_state END', + 'host_problem' => 'CASE WHEN COALESCE(hs.current_state, 0) = 0 THEN 0 ELSE 1 END', 'is_passive_checked' => 'CASE WHEN ss.active_checks_enabled = 0 AND ss.passive_checks_enabled = 1 THEN 1 ELSE 0 END', 'is_active_checked' => 'ss.active_checks_enabled', 'is_processing_events' => 'ss.event_handler_enabled', diff --git a/modules/monitoring/library/Monitoring/DataView/DataView.php b/modules/monitoring/library/Monitoring/DataView/DataView.php index b8ccbb852..b7035a850 100644 --- a/modules/monitoring/library/Monitoring/DataView/DataView.php +++ b/modules/monitoring/library/Monitoring/DataView/DataView.php @@ -31,6 +31,8 @@ abstract class DataView implements Browsable, Filterable, Sortable protected $connection; + protected $isSorted = false; + /** * Create a new view * @@ -99,6 +101,7 @@ public function dump() protected function applyUrlFilter($request = null) { $url = Url::fromRequest(); + $limit = $url->shift('limit'); $sort = $url->shift('sort'); $dir = $url->shift('dir'); @@ -132,20 +135,19 @@ public function dump() } } - $order = isset($params['order']) ? $params['order'] : null; - if ($order !== null) { - if (strtolower($order) === 'desc') { - $order = self::SORT_DESC; - } else { - $order = self::SORT_ASC; + if (isset($params['sort'])) { + + $order = isset($params['order']) ? $params['order'] : null; + if ($order !== null) { + if (strtolower($order) === 'desc') { + $order = self::SORT_DESC; + } else { + $order = self::SORT_ASC; + } } + + $view->sort($params['sort'], $order); } - - $view->sort( - isset($params['sort']) ? $params['sort'] : null, - $order - ); - return $view; } @@ -226,6 +228,7 @@ public function dump() foreach ($sortColumns['columns'] as $column) { $this->query->order($column, $order); } + $this->isSorted = true; } return $this; } @@ -285,6 +288,7 @@ public function dump() */ public function getQuery() { + if (! $this->isSorted) { $this->sort(); } return $this->query; } diff --git a/modules/monitoring/test/php/application/views/helpers/MonitoringPropertiesTest.php b/modules/monitoring/test/php/application/views/helpers/MonitoringPropertiesTest.php deleted file mode 100644 index 5a476aaef..000000000 --- a/modules/monitoring/test/php/application/views/helpers/MonitoringPropertiesTest.php +++ /dev/null @@ -1,104 +0,0 @@ -current_check_attempt = '5'; - - $propertyHelper = new Zend_View_Helper_MonitoringProperties(); - $items = $propertyHelper->monitoringProperties($host); - - $this->assertEquals('5/10 (HARD state)', $items['Current Attempt']); - } - - public function testOutput2() - { - $host = new HostStruct4Properties(); - $host->current_check_attempt = '5'; - $host->active_checks_enabled = '1'; - $host->passive_checks_enabled = '0'; - $host->is_flapping = '1'; - - $propertyHelper = new Zend_View_Helper_MonitoringProperties(); - $items = $propertyHelper->monitoringProperties($host); - - $test = array( - 'Current Attempt' => "5/10 (HARD state)", - 'Check Type' => "ACTIVE", - 'Check Latency / Duration' => "0.1204 / 0.0000 seconds", - 'Last State Change' => "2013-07-04 11:24:43", - 'Last Notification' => "N/A (notification 0)", - 'Is This Host Flapping?' => "YES (12.37% state change)", - 'In Scheduled Downtime?' => "YES" - ); - - $this->assertEquals($test, $items); - } -} diff --git a/modules/monitoring/test/php/regression/Bug7043Test.php b/modules/monitoring/test/php/regression/Bug7043Test.php new file mode 100644 index 000000000..37618a348 --- /dev/null +++ b/modules/monitoring/test/php/regression/Bug7043Test.php @@ -0,0 +1,50 @@ +shouldReceive('create') + ->andReturn( + Mockery::mock('Icinga\Data\Db\DbConnection') + ->shouldReceive('getDbType') + ->andReturn('mysql') + ->shouldReceive('setTablePrefix') + ->getMock() + ); + + Config::setModuleConfig('monitoring', 'backends', new Zend_Config(array( + 'backendName' => array( + 'type' => 'ido', + 'resource' => 'ido' + ) + ))); + + $defaultBackend = Backend::createBackend(); + + $this->assertEquals('backendName', $defaultBackend->getName(), 'Default backend has name set'); + } +} \ No newline at end of file diff --git a/public/js/icinga/loader.js b/public/js/icinga/loader.js index f91aa20b9..f70bc0522 100644 --- a/public/js/icinga/loader.js +++ b/public/js/icinga/loader.js @@ -329,8 +329,7 @@ this.icinga.ui.reloadCss(); } - var redirect = req.getResponseHeader('X-Icinga-Redirect'); - if (this.processRedirectHeader(req)) return; + if (req.getResponseHeader('X-Icinga-Redirect')) return; // div helps getting an XML tree var $resp = $('
' + req.responseText + '
'); @@ -567,6 +566,7 @@ delete this.requests[req.$target.attr('id')]; this.icinga.ui.fadeNotificationsAway(); + this.processRedirectHeader(req); if (typeof req.loadNext !== 'undefined') { if ($('#col2').length) { @@ -665,7 +665,11 @@ var self = this; var containerId = $container.attr('id'); if (typeof containerId !== 'undefined') { - scrollPos = $container.scrollTop(); + if (autorefresh) { + scrollPos = $container.scrollTop(); + } else { + scrollPos = 0; + } } var origFocus = document.activeElement; diff --git a/test/php/application/forms/Config/Authentication/DbBackendFormTest.php b/test/php/application/forms/Config/Authentication/DbBackendFormTest.php index c504955ad..51bec896f 100644 --- a/test/php/application/forms/Config/Authentication/DbBackendFormTest.php +++ b/test/php/application/forms/Config/Authentication/DbBackendFormTest.php @@ -26,10 +26,10 @@ class DbBackendFormTest extends BaseTestCase */ public function testValidBackendIsValid() { - $this->setUpUserBackendMock() + $this->setUpResourceFactoryMock(); + Mockery::mock('overload:Icinga\Authentication\Backend\DbUserBackend') ->shouldReceive('count') ->andReturn(2); - $this->setUpResourceFactoryMock(); $form = new DbBackendForm(); $form->setBackendName('test'); @@ -49,10 +49,10 @@ class DbBackendFormTest extends BaseTestCase */ public function testInvalidBackendIsNotValid() { - $this->setUpUserBackendMock() + $this->setUpResourceFactoryMock(); + Mockery::mock('overload:Icinga\Authentication\Backend\DbUserBackend') ->shouldReceive('count') ->andReturn(0); - $this->setUpResourceFactoryMock(); $form = new DbBackendForm(); $form->setBackendName('test'); @@ -66,18 +66,10 @@ class DbBackendFormTest extends BaseTestCase ); } - protected function setUpUserBackendMock() - { - return Mockery::mock('overload:Icinga\Authentication\Backend\DbUserBackend'); - } - protected function setUpResourceFactoryMock() { Mockery::mock('alias:Icinga\Data\ResourceFactory') - ->shouldReceive('getResourceConfig') - ->andReturn(new \Zend_Config(array())) - ->shouldReceive('createResource') - ->with(Mockery::type('\Zend_Config')) + ->shouldReceive('create') ->andReturn(Mockery::mock('Icinga\Data\Db\DbConnection')); } } diff --git a/test/php/library/Icinga/Data/Filter/FilterTest.php b/test/php/library/Icinga/Data/Filter/FilterTest.php index cc91522dd..dbd640b91 100644 --- a/test/php/library/Icinga/Data/Filter/FilterTest.php +++ b/test/php/library/Icinga/Data/Filter/FilterTest.php @@ -182,7 +182,7 @@ class FilterTest extends BaseTestCase public function testComplexFilterFromQueryString() { - $q = 'host=localhost|nohost*&problem&service=*www*|ups*&state!=1&!handled'; + $q = '(host=localhost|host=nohost*)&problem&(service=*www*|service=ups*)&state!=1&!handled'; $filter = Filter::fromQueryString($q); $this->assertFalse($filter->matches($this->row(0))); $this->assertTrue($filter->matches($this->row(1))); diff --git a/test/php/library/Icinga/Protocol/Ldap/QueryTest.php b/test/php/library/Icinga/Protocol/Ldap/QueryTest.php index b710a0934..f3fa2c727 100644 --- a/test/php/library/Icinga/Protocol/Ldap/QueryTest.php +++ b/test/php/library/Icinga/Protocol/Ldap/QueryTest.php @@ -109,10 +109,10 @@ class QueryTest extends BaseTestCase $this->assertEquals('testIntColumn', $cols[0][0]); } - public function test__toString() + public function testCreateQuery() { $select = $this->prepareSelect(); $res = '(&(objectClass=dummyClass)(testIntColumn=1)(testStringColumn=test)(testWildcard=abc*))'; - $this->assertEquals($res, (string) $select); + $this->assertEquals($res, $select->create()); } } diff --git a/test/php/library/Icinga/Util/TranslatorTest.php b/test/php/library/Icinga/Util/TranslatorTest.php index 1c63e6c68..23bc154dd 100644 --- a/test/php/library/Icinga/Util/TranslatorTest.php +++ b/test/php/library/Icinga/Util/TranslatorTest.php @@ -27,7 +27,7 @@ class TranslatorTest extends BaseTestCase public function testWhetherGetAvailableLocaleCodesReturnsAllAvailableLocaleCodes() { $this->assertEquals( - array('de_DE', 'fr_FR'), + array(Translator::DEFAULT_LOCALE, 'de_DE', 'fr_FR'), Translator::getAvailableLocaleCodes(), 'Translator::getAvailableLocaleCodes does not return all available locale codes' ); diff --git a/test/php/library/Icinga/Web/Widget/DashboardTest.php b/test/php/library/Icinga/Web/Widget/DashboardTest.php new file mode 100644 index 000000000..9114a8f90 --- /dev/null +++ b/test/php/library/Icinga/Web/Widget/DashboardTest.php @@ -0,0 +1,509 @@ +shouldReceive('escape'); + + return $mock; + } +} + +class DashboardWithPredefinableActiveName extends Dashboard +{ + public $activeName = ''; + + public function getTabs() + { + return Mockery::mock('Icinga\Web\Widget\Tabs') + ->shouldReceive('getActiveName')->andReturn($this->activeName) + ->shouldReceive('activate') + ->getMock(); + } +} + +class DashboardTest extends BaseTestCase +{ + public function tearDown() + { + parent::tearDown(); + Mockery::close(); // Necessary because some tests run in a separate process + } + + protected function setupIcingaMock(\Zend_Controller_Request_Abstract $request) + { + $moduleMock = Mockery::mock('Icinga\Application\Modules\Module'); + $moduleMock->shouldReceive('getPaneItems')->andReturn(array( + 'test-pane' => new Pane('Test Pane') + )); + + $moduleManagerMock = Mockery::mock('Icinga\Application\Modules\Manager'); + $moduleManagerMock->shouldReceive('getLoadedModules')->andReturn(array( + 'test-module' => $moduleMock + )); + + $bootstrapMock = Mockery::mock('Icinga\Application\ApplicationBootstrap')->shouldDeferMissing(); + $bootstrapMock->shouldReceive('getFrontController->getRequest')->andReturnUsing( + function () use ($request) { return $request; } + )->shouldReceive('getApplicationDir')->andReturn(self::$appDir); + + $bootstrapMock->shouldReceive('getModuleManager')->andReturn($moduleManagerMock); + + Icinga::setApp($bootstrapMock, true); + } + + public function testWhetherCreatePaneCreatesAPane() + { + $dashboard = new Dashboard(); + $pane = $dashboard->createPane('test')->getPane('test'); + + $this->assertEquals('test', $pane->getTitle(), 'Dashboard::createPane() could not create a pane'); + } + + /** + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testMergePanesWithDifferentPaneName() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $dashboard->createPane('test2'); + + $panes = array( + new Pane('test1a'), + new Pane('test2a') + ); + + $dashboard->mergePanes($panes); + + $this->assertCount(4, $dashboard->getPanes(), 'Dashboard::mergePanes() could not merge different panes'); + } + + /** + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testMergePanesWithSamePaneName() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $dashboard->createPane('test2'); + + $panes = array( + new Pane('test1'), + new Pane('test3') + ); + + $dashboard->mergePanes($panes); + + $this->assertCount(3, $dashboard->getPanes(), 'Dashboard::mergePanes() could not merge same panes'); + } + + /** + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherGetPaneReturnsAPaneByName() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + + $pane = $dashboard->getPane('test1'); + + $this->assertEquals( + 'test1', + $pane->getName(), + 'Dashboard:getPane() could not return pane by name' + ); + } + + /** + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testLoadPaneItemsProvidedByEnabledModules() + { + $dashboard = Dashboard::load(); + + $this->assertCount( + 1, + $dashboard->getPanes(), + 'Dashboard::load() could not load panes from enabled modules' + ); + } + + /** + * @expectedException \Icinga\Exception\ProgrammingError + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherGetPaneThrowsAnExceptionOnNotExistentPaneName() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + + $dashboard->getPane('test2'); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherRenderNotRendersPanesDisabledComponent() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + $component = new ComponentWithMockedView('test', 'test', $pane); + $component->setDisabled(true); + $pane->addComponent($component); + + $rendered = $dashboard->render(); + + $greaterThanOne = strlen($rendered) > 1; + + $this->assertFalse( + $greaterThanOne, + 'Dashboard::render() disabled component is rendered, but should not' + ); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherRenderRendersPanesEnabledComponent() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + $component = new ComponentWithMockedView('test', 'test', $pane); + $pane->addComponent($component); + + $rendered = $dashboard->render(); + + $greaterThanOne = strlen($rendered) > 1; + + $this->assertTrue( + $greaterThanOne, + 'Dashboard::render() could not render enabled component' + ); + } + + public function testWhetherRenderNotRendersNotExistentPane() + { + $dashboard = new Dashboard(); + + $rendered = $dashboard->render(); + + $greaterThanOne = strlen($rendered) > 1; + + $this->assertFalse( + $greaterThanOne, + 'Dashboard::render() not existent pane ist rendered, but should not' + ); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherGetPaneKeyTitleArrayReturnFormedArray() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1')->getPane('test1')->setTitle('Test1'); + $dashboard->createPane('test2')->getPane('test2')->setTitle('Test2'); + + $result = $dashboard->getPaneKeyTitleArray(); + + $expected = array( + 'test1' => 'Test1', + 'test2' => 'Test2' + ); + + $this->assertEquals( + $expected, + $result, + 'Dashboard::getPaneKeyTitleArray() could not return valid expectation' + ); + } + + /** + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherHasPanesHasPanes() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $dashboard->createPane('test2'); + + $hasPanes = $dashboard->hasPanes(); + + $this->assertTrue($hasPanes, 'Dashboard::hasPanes() could not return valid expectation'); + } + + public function testWhetherHasPanesHasNoPanes() + { + $dashboard = new Dashboard(); + + $hasPanes = $dashboard->hasPanes(); + + $this->assertFalse($hasPanes, 'Dashboard::hasPanes() has panes but should not'); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherRemoveComponentRemovesComponent() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + + $component = new Component('test', 'test', $pane); + $pane->addComponent($component); + + $component2 = new Component('test2', 'test2', $pane); + $pane->addComponent($component2); + + $dashboard->removeComponent('test1', 'test'); + + $result = $dashboard->getPane('test1')->hasComponent('test'); + + $this->assertFalse( + $result, + 'Dashboard::removeComponent() could not remove component from the pane' + ); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherRemoveComponentRemovesComponentByConcatenation() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + + $component = new Component('test', 'test', $pane); + $pane->addComponent($component); + + $component2 = new Component('test2', 'test2', $pane); + $pane->addComponent($component2); + + $dashboard->removeComponent('test1.test', null); + + $result = $dashboard->getPane('test1')->hasComponent('test'); + + $this->assertFalse( + $result, + 'Dashboard::removeComponent() could not remove component from the pane' + ); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherToArrayReturnsDashboardStructureAsArray() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + + $component = new Component('test', 'test', $pane); + $pane->addComponent($component); + + $result = $dashboard->toArray(); + + $expected = array( + 'test1' => array( + 'title' => 'test1' + ), + 'test1.test' => array( + 'url' => 'test' + ) + ); + + $this->assertEquals( + $expected, + $result, + 'Dashboard::toArray() could not return valid expectation' + ); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherSetComponentUrlUpdatesTheComponentUrl() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + $component = new Component('test', 'test', $pane); + $pane->addComponent($component); + + $dashboard->setComponentUrl('test1', 'test', 'new'); + + $this->assertEquals( + 'new', + $component->getUrl()->getPath(), + 'Dashboard::setComponentUrl() could not return valid expectation' + ); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherSetComponentUrlUpdatesTheComponentUrlConcatenation() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + $component = new Component('test', 'test', $pane); + $pane->addComponent($component); + + $dashboard->setComponentUrl('test1.test', null, 'new'); + + $this->assertEquals( + 'new', + $component->getUrl()->getPath(), + 'Dashboard::setComponentUrl() could not return valid expectation' + ); + } + + /** + * @depends testWhetherGetPaneReturnsAPaneByName + */ + public function testWhetherSetComponentUrlUpdatesTheComponentUrlNotExistentPane() + { + $dashboard = new Dashboard(); + $dashboard->createPane('test1'); + $pane = $dashboard->getPane('test1'); + $component = new Component('test', 'test', $pane); + $pane->addComponent($component); + + $dashboard->setComponentUrl('test3.test', null, 'new'); + + $result = $dashboard->getPane('test3')->getComponent('test'); + + $this->assertEquals( + 'new', + $result->getUrl()->getPath(), + 'Dashboard::setComponentUrl() could not return valid expectation' + ); + } + + /** + * @expectedException \Icinga\Exception\ConfigurationError + */ + public function testWhetherDetermineActivePaneThrowsAnExceptionIfCouldNotDetermine() + { + $dashboard = new Dashboard(); + $dashboard->determineActivePane(); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + * @expectedException \Icinga\Exception\ProgrammingError + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherDetermineActivePaneThrowsAnExceptionIfCouldNotDetermineInvalidPane() + { + $dashboard = new DashboardWithPredefinableActiveName(); + $dashboard->createPane('test1'); + + Mockery::mock('alias:Icinga\Web\Url') + ->shouldReceive('fromRequest->getParam')->andReturn('test2'); + + $dashboard->determineActivePane(); + } + + /** + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherDetermineActivePaneDeterminesActivePane() + { + $dashboard = new DashboardWithPredefinableActiveName(); + $dashboard->activeName = 'test2'; + $dashboard->createPane('test1'); + $dashboard->createPane('test2'); + + $activePane = $dashboard->determineActivePane(); + + $this->assertEquals( + 'test2', + $activePane->getTitle(), + 'Dashboard::determineActivePane() could not determine active pane' + ); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherDetermineActivePaneDeterminesActiveValidPane() + { + $dashboard = new DashboardWithPredefinableActiveName(); + $dashboard->createPane('test1'); + $dashboard->createPane('test2'); + + Mockery::mock('alias:Icinga\Web\Url') + ->shouldReceive('fromRequest->getParam')->andReturn('test2'); + + $activePane = $dashboard->determineActivePane(); + + $this->assertEquals( + 'test2', + $activePane->getTitle(), + 'Dashboard::determineActivePane() could not determine active pane' + ); + } + + /** + * @depends testWhetherCreatePaneCreatesAPane + */ + public function testWhetherGetActivePaneReturnsActivePane() + { + $dashboard = new DashboardWithPredefinableActiveName(); + $dashboard->activeName = 'test2'; + $dashboard->createPane('test1'); + $dashboard->createPane('test2'); + + $activePane = $dashboard->getActivePane(); + + $this->assertEquals( + 'test2', + $activePane->getTitle(), + 'Dashboard::determineActivePane() could not get expected active pane' + ); + } + + public function testWhetherLoadConfigPanes() + { + $this->markTestIncomplete( + 'Dashboard::loadConfigPanes() is not fully implemented yet or rather not used' + ); + } + + /** + * @depends testWhetherLoadConfigPanes + */ + public function testWhetherReadConfigPopulatesDashboard() + { + $this->markTestIncomplete( + 'Dashboard::readConfig() is not fully implemented yet or rather not used' + ); + } +}