diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:43:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:43:29 +0000 |
commit | a9b77c01caef9ae7a2c84e2333d28ceb028cf4d3 (patch) | |
tree | 4a77cd3e323c37b0e5b3d7578b9718cdf1a89262 /library | |
parent | Initial commit. (diff) | |
download | icingaweb2-module-eventdb-a9b77c01caef9ae7a2c84e2333d28ceb028cf4d3.tar.xz icingaweb2-module-eventdb-a9b77c01caef9ae7a2c84e2333d28ceb028cf4d3.zip |
Adding upstream version 1.3.0.upstream/1.3.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'library')
-rw-r--r-- | library/Eventdb/Data/LegacyFilterParser.php | 153 | ||||
-rw-r--r-- | library/Eventdb/Event.php | 120 | ||||
-rw-r--r-- | library/Eventdb/Eventdb.php | 184 | ||||
-rw-r--r-- | library/Eventdb/EventdbController.php | 181 | ||||
-rw-r--r-- | library/Eventdb/Hook/DetailviewExtensionHook.php | 124 | ||||
-rw-r--r-- | library/Eventdb/ProvidedHook/Monitoring/DetailviewExtension.php | 81 | ||||
-rw-r--r-- | library/Eventdb/ProvidedHook/Monitoring/EventdbActionHook.php | 182 | ||||
-rw-r--r-- | library/Eventdb/ProvidedHook/Monitoring/HostActions.php | 15 | ||||
-rw-r--r-- | library/Eventdb/ProvidedHook/Monitoring/ServiceActions.php | 15 | ||||
-rw-r--r-- | library/Eventdb/Test/BaseTestCase.php | 27 | ||||
-rw-r--r-- | library/Eventdb/Test/Bootstrap.php | 35 | ||||
-rw-r--r-- | library/Eventdb/Test/PseudoHost.php | 15 | ||||
-rw-r--r-- | library/Eventdb/Test/PseudoMonitoringBackend.php | 14 | ||||
-rw-r--r-- | library/Eventdb/Test/PseudoService.php | 16 | ||||
-rw-r--r-- | library/Eventdb/Web/EventdbOutputFormat.php | 66 |
15 files changed, 1228 insertions, 0 deletions
diff --git a/library/Eventdb/Data/LegacyFilterParser.php b/library/Eventdb/Data/LegacyFilterParser.php new file mode 100644 index 0000000..2907202 --- /dev/null +++ b/library/Eventdb/Data/LegacyFilterParser.php @@ -0,0 +1,153 @@ +<?php +/* Icinga Web 2 - EventDB | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Eventdb\Data; + +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterAnd; +use Icinga\Data\Filter\FilterOr; +use Icinga\Exception\InvalidPropertyException; + +/** + * Class LegacyFilterParser + * + * Utility class to parse Icinga-web 1.x JSON filters of the EventDB module + */ +class LegacyFilterParser +{ + /** + * @param $json string JSON data + * @param $host string Icinga host name (for default host filter and logging) + * @param $service string Icinga service name (for logging) + * + * @return Filter + * @throws InvalidPropertyException When filter could not be parsed + */ + static public function parse($json, $host, $service = null) + { + $json = static::fixJSONQuotes($json); + + $data = json_decode($json, false, 5); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new InvalidPropertyException( + 'Could not decode legacy filter (host=%s)%s (%s): %s', + $host, + ($service ? ' (service=' . $service . ')' : ''), + json_last_error_msg(), + $json + ); + } + + $filter = new FilterAnd(); + + if (property_exists($data, 'host')) { + // Note: we can' support regexp right now, but we replace '.*' to a normal wildcard + $data->host = str_replace('.*', '*', $data->host); + if ($data->host === '*') { + $data->host = null; + } + } else { + $data->host = $host; + } + if ($data->host !== null) { + $filter->andFilter(Filter::expression('host_name', '=', $data->host)); + } + + static::handleArray($filter, $data, 'programInclusion', 'program'); + static::handleArray($filter, $data, 'programExclusion', 'program', '!='); + + static::handleArray($filter, $data, 'priorityExclusion', 'priority', '!='); + static::handleArray($filter, $data, 'sourceExclusion', 'source', '!='); + static::handleArray($filter, $data, 'facilityExclusion', 'facility', '!='); + + // TODO: msg - when really needed + // TODO: startTime - when really needed + + // Note: any other field or data part gets ignored... + + return $filter; + } + + protected static function handleArray(Filter $filter, $data, $property, $filterAttr, $op = '=') + { + if (property_exists($data, $property) && ! empty($data->$property)) { + if ($op === '!=') { + $subFilter = $filter; + } else { + $subFilter = new FilterOr; + } + + /* + if (is_array($data->$property) && count($data->$property) === 1) { + $data->$property = current($data->$property); + } + */ + if (! is_array($data->$property)) { + $data->$property = array($data->$property); + } + foreach ($data->$property as $val) { + $subFilter->addFilter(Filter::expression($filterAttr, $op, $val)); + } + + if ($subFilter !== $filter) { + $filters = $subFilter->filters(); + if ($filter->isChain() && count($filters) > 1) { + $filter->andFilter($subFilter); + } else { + $filter->andFilter(current($filters)); + } + } + } + } + + /** + * @author partially by NikiC https://stackoverflow.com/users/385378/nikic + * @source partially from https://stackoverflow.com/a/20440596/449813 + * + * @param $json string + * + * @return string + */ + public static function fixJSONQuotes($json) + { + // fix unquoted identifiers + $json = preg_replace('/([{,]+)(\s*)([^"]+?)\s*:/', '$1"$3":', $json); + + $regex = <<<'REGEX' +~ + "[^"\\]*(?:\\.|[^"\\]*)*" + (*SKIP)(*F) + | '([^'\\]*(?:\\.|[^'\\]*)*)' +~x +REGEX; + + return preg_replace_callback($regex, function ($matches) { + return '"' . preg_replace('~\\\\.(*SKIP)(*F)|"~', '\\"', $matches[1]) . '"'; + }, $json); + } + + /** + * Basic check if it looks like a JSON filter + * + * @param $string + * + * @return bool + */ + static public function isJsonFilter($string) + { + if (! $string || ! is_string($string)) { + return false; + } + + $string = trim($string); + if (empty($string)) { + return false; + } + + if (preg_match('/^\{.*\}$/s', $string)) { + // looks like JSON data + return true; + } + return false; + } +} diff --git a/library/Eventdb/Event.php b/library/Eventdb/Event.php new file mode 100644 index 0000000..a3bba1a --- /dev/null +++ b/library/Eventdb/Event.php @@ -0,0 +1,120 @@ +<?php +/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Eventdb; + +use ArrayObject; + +class Event extends ArrayObject +{ + public static $facilities = array( + 0 => 'kernel messages', + 1 => 'user-level messages', + 2 => 'mail system', + 3 => 'system daemons', + 4 => 'security/authorization messages', + 5 => 'messages generated internally by syslogd', + 6 => 'line printer subsystem', + 7 => 'network news subsystem', + 8 => 'UUCP subsystem', + 9 => 'clock daemon', + 10 => 'security/authorization messages', + 11 => 'FTP daemon', + 12 => 'NTP subsystem', + 13 => 'log audit', + 14 => 'log alert', + 15 => 'clock daemon', + 16 => 'local use 0', + 17 => 'local use 1', + 18 => 'local use 2', + 19 => 'local use 3', + 20 => 'local use 4', + 21 => 'local use 5', + 22 => 'local use 6', + 23 => 'local use 7' + ); + + public static $priorities = array( + 0 => 'emergency', + 1 => 'alert', + 2 => 'critical', + 3 => 'error', + 4 => 'warning', + 5 => 'notice', + 6 => 'info', + 7 => 'debug' + ); + + public static $types = array( + 0 => 'syslog', + 1 => 'snmp', + 2 => 'mail' + ); + + public static $typeIcons = array( + '_default' => 'help', + 'syslog' => 'doc-text', + 'snmp' => 'plug', + 'mail' => 'bell', + ); + + public function __construct($data) + { + parent::__construct($data, ArrayObject::ARRAY_AS_PROPS); + } + + public function offsetGet($index) + { + if (! $this->offsetExists($index)) { + return null; + } + $getter = 'get' . ucfirst($index); + if (method_exists($this, $getter)) { + return $this->$getter(); + } + return parent::offsetGet($index); + } + + public function getAck() + { + return (bool) parent::offsetGet('ack'); + } + + public function getFacility() + { + $facility = (int) parent::offsetGet('facility'); + return array_key_exists($facility, static::$facilities) ? static::$facilities[$facility] : $facility; + } + + public function getPriority() + { + $priority = (int) parent::offsetGet('priority'); + return array_key_exists($priority, static::$priorities) ? static::$priorities[$priority] : $priority; + } + + public function getType() + { + $type = (int) parent::offsetGet('type'); + return array_key_exists($type, static::$types) ? static::$types[$type] : $type; + } + + public function getTypeIcon() + { + if (array_key_exists($type = $this->getType(), static::$typeIcons)) { + return static::$typeIcons[$type]; + } else { + return static::$typeIcons['_default']; + } + } + + public static function fromData($data) + { + return new static($data); + } + + public static function getPriorityId($priorityName) + { + $priorities = array_flip(static::$priorities); + return $priorities[$priorityName]; + } +} diff --git a/library/Eventdb/Eventdb.php b/library/Eventdb/Eventdb.php new file mode 100644 index 0000000..b885258 --- /dev/null +++ b/library/Eventdb/Eventdb.php @@ -0,0 +1,184 @@ +<?php +/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Eventdb; + +use Icinga\Application\Config; +use Icinga\Data\ConfigObject; +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterExpression; +use Icinga\Data\ResourceFactory; +use Icinga\Exception\ConfigurationError; +use Icinga\Repository\DbRepository; +use Icinga\Repository\RepositoryQuery; + +class Eventdb extends DbRepository +{ + /** + * {@inheritdoc} + */ + const DATETIME_FORMAT = 'Y-m-d G:i:s'; + + /** + * {@inheritdoc} + */ + protected $tableAliases = array( + 'comment' => 'c', + 'event' => 'e', + ); + + /** + * Default query columns + * + * @var array + */ + protected static $defaultQueryColumns = array( + 'event' => array( + 'id', + 'host_name', + 'host_address', + 'type', + 'facility', + 'priority', + 'program', + 'message', + 'alternative_message', + 'ack', + 'created', + 'modified', + ), + 'comment' => array( + 'id', + 'event_id', + 'type', + 'message', + 'created', + 'modified', + 'user' + ) + ); + + protected static $edbcQueryColumns = array( + 'event' => array( + 'group_active', + 'group_id', + 'group_count', + 'group_leader', + 'group_autoclear', + 'flags', + 'alternative_message' + ) + ); + + /** @var bool */ + protected $hasCorrelatorExtensions = null; + + /** + * Checks if Event repository has EDBC columns + * + * @return bool + */ + public function hasCorrelatorExtensions() + { + if ($this->hasCorrelatorExtensions === null) { + $dba = $this->getDataSource()->getDbAdapter(); + $result = $dba->fetchRow("SHOW COLUMNS FROM `event` LIKE 'group_leader'"); + + $this->hasCorrelatorExtensions = ! ! $result; + } + return $this->hasCorrelatorExtensions; + } + + public function filterGroups(RepositoryQuery $query) + { + if ($this->hasCorrelatorExtensions()) { + $query->addFilter(Filter::matchAny( + Filter::expression('group_leader', '=', -1), + new FilterExpression('group_leader', 'IS', new \Zend_Db_Expr('NULL')) + )); + } + return $this; + } + + /** + * {@inheritdoc} + */ + protected function initializeQueryColumns() + { + $additionalColumns = Config::module('eventdb', 'columns')->keys(); + $queryColumns = static::$defaultQueryColumns; + if ($this->hasCorrelatorExtensions()) { + foreach (static::$edbcQueryColumns as $table => $fields) { + if (array_key_exists($table, $queryColumns)) { + $queryColumns[$table] = array_merge($queryColumns[$table], $fields); + } else { + $queryColumns[$table] = $fields; + } + } + } + if ($additionalColumns !== null) { + $eventColumns = $queryColumns['event']; + $queryColumns['event'] = array_merge($eventColumns, array_diff($additionalColumns, $eventColumns)); + } + return $queryColumns; + } + + /** + * Create and return a new instance of the Eventdb + * + * @param ConfigObject $config The configuration to use, otherwise the module's configuration + * + * @return static + * + * @throws ConfigurationError In case no resource has been configured in the module's configuration + */ + public static function fromConfig(ConfigObject $config = null) + { + if ($config === null) { + $moduleConfig = Config::module('eventdb'); + if (($resourceName = $moduleConfig->get('backend', 'resource')) === null) { + throw new ConfigurationError( + mt('eventdb', 'You need to configure a resource to access the EventDB database first') + ); + } + + $resource = ResourceFactory::create($resourceName); + } else { + $resource = ResourceFactory::createResource($config); + } + + return new static($resource); + } + + /** + * {@inheritdoc} + */ + protected function initializeConversionRules() + { + return array('event' => array('host_address' => 'ip_address')); + } + + /** + * Convert an IP address into its human-readable form + * + * @param string $rawAddress + * + * @return string + */ + protected function retrieveIpAddress($rawAddress) + { + return $rawAddress === null ? null : inet_ntop($rawAddress); + } + + /** + * Convert an IP address into its binary form + * + * @param string $address + * + * @return string + */ + protected function persistIpAddress($address) + { + return $address === null ? null : inet_pton($address); + } +} diff --git a/library/Eventdb/EventdbController.php b/library/Eventdb/EventdbController.php new file mode 100644 index 0000000..538553e --- /dev/null +++ b/library/Eventdb/EventdbController.php @@ -0,0 +1,181 @@ +<?php +/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Eventdb; + +use Icinga\Application\Icinga; +use Icinga\Data\QueryInterface; +use Icinga\Exception\IcingaException; +use Icinga\Exception\Json\JsonEncodeException; +use Icinga\Module\Monitoring\Backend\MonitoringBackend; +use Icinga\Web\Controller; +use Icinga\Web\View; + +class EventdbController extends Controller +{ + /** @var View */ + public $view; + + /** @var MonitoringBackend */ + protected $monitoringBackend; + + /** + * Get the EventDB repository + * + * @return Eventdb + */ + protected function getDb() + { + return Eventdb::fromConfig(); + } + + /** + * {@inheritdoc} + */ + public function getRestrictions($name, $permission = null) + { + $restrictions = array(); + if ($this->Auth()->isAuthenticated()) { + foreach ($this->Auth()->getUser()->getRoles() as $role) { + if ($permission !== null && ! in_array($permission, $role->getPermissions())) { + continue; + } + $restrictionsFromRole = $role->getRestrictions($name); + if (empty($restrictionsFromRole)) { + $restrictions = array(); + break; + } else { + if (! is_array($restrictionsFromRole)) { + $restrictionsFromRole = array($restrictionsFromRole); + } + $restrictions = array_merge($restrictions, array_values($restrictionsFromRole)); + } + } + } + return $restrictions; + } + + /** + * Retrieves the Icinga MonitoringBackend + * + * @param string|null $name + * + * @return MonitoringBackend + * @throws IcingaException When monitoring is not enabled + */ + protected function monitoringBackend($name = null) + { + if ($this->monitoringBackend === null) { + if (! Icinga::app()->getModuleManager()->hasEnabled('monitoring')) { + throw new IcingaException('The module "monitoring" must be enabled and configured!'); + } + $this->monitoringBackend = MonitoringBackend::instance($name); + } + return $this->monitoringBackend; + } + + protected function setViewScript($name) + { + $this->_helper->viewRenderer->setNoController(true); + $this->_helper->viewRenderer->setScriptAction($name); + } + + protected function isFormatRequest() + { + return $this->hasParam('format'); + } + + protected function isApiRequest() + { + $format = $this->getParam('format'); + $header = $this->getRequest()->getHeader('Accept'); + + if ($format === 'json' || preg_match('#application/json(;.+)?#', $header)) { + return true; + } else { + return false; + } + } + + protected function isTextRequest() + { + $format = $this->getParam('format'); + if ($format === 'text' || $this->isPlainTextRequest()) { + return true; + } else { + return false; + } + } + + protected function isPlainTextRequest() + { + $header = $this->getRequest()->getHeader('Accept'); + if ($header !== null && preg_match('#text/plain#', $header)) { + return true; + } else { + return false; + } + } + + /** + * Send the user a summary of SQL queries + * + * @param array|QueryInterface $queries + */ + protected function sendSqlSummary($queries) + { + if (! is_array($queries)) { + $queries = array($queries); + } + + $str = ''; + foreach ($queries as $query) { + if ($query !== null) { + $str .= wordwrap($query) . "\n\n"; + } + } + + $this->sendText($str); + } + + /** + * Output JSON data to the requester + * + * @param mixed $data + * @param int $options + * @param int $depth + * + * @throws JsonEncodeException + */ + protected function sendJson($data, $options = 0, $depth = 100) + { + header('Content-Type: application/json'); + + if (defined('JSON_PARTIAL_OUTPUT_ON_ERROR')) { + $options |= JSON_PARTIAL_OUTPUT_ON_ERROR; + } + + $output = json_encode($data, $options, $depth); + if (! $output && json_last_error() !== null) { + throw new JsonEncodeException('JSON error: ' . json_last_error_msg()); + } + echo $output; + exit; + } + + protected function sendText($str, $script = null) + { + if ($this->isPlainTextRequest()) { + if ($script !== null) { + echo $this->view->render($this->getViewScript($script, true)); + } else { + echo $str; + } + exit; + } else { + $this->view->text = $str; + $this->view->partial = $script; + $this->setViewScript('format/text'); + } + } +} diff --git a/library/Eventdb/Hook/DetailviewExtensionHook.php b/library/Eventdb/Hook/DetailviewExtensionHook.php new file mode 100644 index 0000000..9fee702 --- /dev/null +++ b/library/Eventdb/Hook/DetailviewExtensionHook.php @@ -0,0 +1,124 @@ +<?php +/* Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Eventdb\Hook; + +use Icinga\Application\ClassLoader; +use Icinga\Application\Icinga; +use Icinga\Application\Modules\Module; +use Icinga\Module\Eventdb\Event; +use Icinga\Web\View; + +/** + * Base class for hooks extending the detail view of events + * + * Extend this class if you want to extend the detail view of events with custom HTML. + */ +abstract class DetailviewExtensionHook +{ + /** + * The view the generated HTML will be included in + * + * @var View + */ + private $view; + + /** + * The module of the derived class + * + * @var Module + */ + private $module; + + /** + * Create a new hook + * + * @see init() For hook initialization. + */ + final public function __construct() + { + $this->init(); + } + + /** + * Overwrite this function for hook initialization, e.g. loading the hook's config + */ + protected function init() + { + } + + /** + * Shall return valid HTML to include in the detail view + * + * @param Event $event The event to generate HTML for + * + * @return string + */ + abstract public function getHtmlForEvent(Event $event); + + /** + * Shall return valid HTML to include in the multi-select view for events + * + * @param Event[] $events The events to generate HTML for + * + * @return string + */ + public function getHtmlForEvents($events) + { + return ''; + } + + /** + * Get {@link view} + * + * @return View + */ + public function getView() + { + return $this->view; + } + + /** + * Set {@link view} + * + * @param View $view + * + * @return $this + */ + public function setView($view) + { + $this->view = $view; + return $this; + } + + /** + * Get the module of the derived class + * + * @return Module + */ + public function getModule() + { + if ($this->module === null) { + $class = get_class($this); + if (ClassLoader::classBelongsToModule($class)) { + $this->module = Icinga::app()->getModuleManager()->getModule(ClassLoader::extractModuleName($class)); + } + } + + return $this->module; + } + + /** + * Set the module of the derived class + * + * @param Module $module + * + * @return $this + */ + public function setModule(Module $module) + { + $this->module = $module; + + return $this; + } +} diff --git a/library/Eventdb/ProvidedHook/Monitoring/DetailviewExtension.php b/library/Eventdb/ProvidedHook/Monitoring/DetailviewExtension.php new file mode 100644 index 0000000..e03f401 --- /dev/null +++ b/library/Eventdb/ProvidedHook/Monitoring/DetailviewExtension.php @@ -0,0 +1,81 @@ +<?php +/* Icinga Web 2 - EventDB | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Eventdb\ProvidedHook\Monitoring; + +use Icinga\Application\Config; +use Icinga\Authentication\Auth; +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterAnd; +use Icinga\Module\Eventdb\Eventdb; +use Icinga\Module\Monitoring\Hook\DetailviewExtensionHook; +use Icinga\Module\Monitoring\Object\MonitoredObject; +use Icinga\Web\Navigation\Navigation; +use Icinga\Web\Url; + +/** + * Available in icingaweb2 after 2.5.0 + */ +class DetailviewExtension extends DetailviewExtensionHook +{ + public function getHtmlForObject(MonitoredObject $object) + { + if (! Auth::getInstance()->hasPermission('eventdb/events')) { + return ''; + } + + $config = static::config(); + + if ($config->get('detailview_disable') === '1') { + return ''; + } + + $actions = clone EventdbActionHook::getActions($object); + if (! $actions->hasRenderableItems()) { + // no actions -> no EventDB + return ''; + } + + $htm = '<h2>EventDB</h2>'; + + $htm .= '<div class="quick-actions">'; + $actions->setLayout(Navigation::LAYOUT_TABS); + $htm .= $actions->render(); + $htm .= '</div>'; + + $url = Url::fromPath('eventdb/events', array('host_name' => $object->host_name)); + + $customFilter = EventdbActionHook::getCustomFilter($object); + if ($customFilter === null) { + $customFilter = new FilterAnd; + } + $detailview_filter = $config->get('detailview_filter', 'ack=0'); + if ($detailview_filter !== null) { + $customFilter = $customFilter->andFilter(Filter::fromQueryString($detailview_filter)); + } + + $htm .= sprintf( + '<div class="container" data-last-update="-1" data-icinga-url="%s" data-icinga-refresh="60">', + $url->with(array( + 'sort' => 'priority', + 'dir' => 'asc', + 'view' => 'compact', + 'limit' => 5, + ))->addFilter($customFilter) + ); + $htm .= '<p class="progress-label">' . mt('eventdb', 'Loading') . '<span>.</span><span>.</span><span>.</span></p>'; + $htm .= '</div>'; + + return $htm; + } + + protected function eventDb() + { + return Eventdb::fromConfig(); + } + + protected static function config() + { + return Config::module('eventdb')->getSection('monitoring'); + } +} diff --git a/library/Eventdb/ProvidedHook/Monitoring/EventdbActionHook.php b/library/Eventdb/ProvidedHook/Monitoring/EventdbActionHook.php new file mode 100644 index 0000000..1eb968d --- /dev/null +++ b/library/Eventdb/ProvidedHook/Monitoring/EventdbActionHook.php @@ -0,0 +1,182 @@ +<?php +/* Icinga Web 2 - EventDB | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Eventdb\ProvidedHook\Monitoring; + +use Icinga\Application\Config; +use Icinga\Application\Logger; +use Icinga\Authentication\Auth; +use Icinga\Data\Filter\Filter; +use Icinga\Data\Filter\FilterParseException; +use Icinga\Exception\InvalidPropertyException; +use Icinga\Module\Eventdb\Data\LegacyFilterParser; +use Icinga\Module\Monitoring\Object\Host; +use Icinga\Module\Monitoring\Object\MonitoredObject; +use Icinga\Module\Monitoring\Object\Service; +use Icinga\Web\Navigation\Navigation; +use Icinga\Web\Url; +use Icinga\Web\UrlParams; + +class EventdbActionHook +{ + protected static $wantCache = true; + + protected static $cachedNav = array(); + + protected static $customFilters = array(); + + public static function wantCache($bool=true) + { + static::$wantCache = $bool; + } + + /** + * @param MonitoredObject $object + * @param bool $no_cache + * + * @return null|Filter + */ + public static function getCustomFilter(MonitoredObject $object) + { + if (! Auth::getInstance()->hasPermission('eventdb/events')) { + return null; + } + + $objectKey = static::getObjectKey($object); + + // check cache if the filter already have been rendered + if (static::$wantCache && array_key_exists($objectKey, self::$customFilters)) { + return self::$customFilters[$objectKey]; + } + + $config = static::config(); + $custom_var = $config->get('custom_var', null); + + $service = null; + $edb_filter = null; + if ($custom_var !== null && $object instanceof Service) { + $edb_filter = $object->{'_service_' . $custom_var . '_filter'}; + $service = $object->service_description; + } elseif ($custom_var !== null && $object instanceof Host) { + $edb_filter = $object->{'_host_' . $custom_var . '_filter'}; + } + + $customFilter = null; + if ($edb_filter !== null) { + if (LegacyFilterParser::isJsonFilter($edb_filter)) { + try { + $customFilter = LegacyFilterParser::parse( + $edb_filter, + $object->host_name, + $service + ); + } catch (InvalidPropertyException $e) { + Logger::warning($e->getMessage()); + } + } else { + try { + $customFilter = Filter::fromQueryString($edb_filter); + + if (! in_array('host_name', $customFilter->listFilteredColumns())) { + $customFilter = $customFilter->andFilter(Filter::expression('host_name', '=', $object->host_name)); + } + } catch (FilterParseException $e) { + Logger::warning('Could not parse custom EventDB filter: %s (%s)', $edb_filter, $e->getMessage()); + } + } + } + + return self::$customFilters[$objectKey] = $customFilter; + } + + /** + * @param MonitoredObject $object Host or Service to render for + * @param bool $no_cache Only for testing - to avoid caching + * + * @return array|Navigation + */ + public static function getActions(MonitoredObject $object) + { + if (! Auth::getInstance()->hasPermission('eventdb/events')) { + return array(); + } + + $objectKey = static::getObjectKey($object); + + // check cache if the buttons already have been rendered + if (static::$wantCache && array_key_exists($objectKey, self::$cachedNav)) { + return self::$cachedNav[$objectKey]; + } + + $nav = new Navigation(); + + $config = static::config(); + + $custom_var = $config->get('custom_var', null); + + $edb_cv = null; + $always_on = null; + + if ($custom_var !== null && $object instanceof Service) { + $edb_cv = $object->{'_service_' . $custom_var}; + $always_on = $config->get('always_on_service', 0); + } elseif ($custom_var !== null && $object instanceof Host) { + $edb_cv = $object->{'_host_' . $custom_var}; + $always_on = $config->get('always_on_host', 0); + } + + $customFilter = static::getCustomFilter($object); + if ($customFilter !== null) { + $params = UrlParams::fromQueryString($customFilter->toQueryString()); + $nav->addItem( + 'events_filtered', + array( + 'label' => mt('eventdb', 'Filtered events'), + 'url' => Url::fromPath('eventdb/events')->setParams($params), + 'icon' => 'tasks', + 'class' => 'action-link', + 'priority' => 1 + ) + ); + } + + // show access to all events, if (or) + // - custom_var is not configured + // - always_on is configured + // - custom_var is configured and set on object (to any value) + if ($custom_var === null || ! empty($edb_cv) || ! empty($always_on)) { + $nav->addItem( + 'events', + array( + 'label' => mt('eventdb', 'All events for host'), + 'url' => Url::fromPath( + 'eventdb/events', + array( + 'host_name' => $object->host_name, + ) + ), + 'icon' => 'tasks', + 'class' => 'action-link', + 'priority' => 99 + ) + ); + } + + return self::$cachedNav[$objectKey] = $nav; + } + + protected static function getObjectKey(MonitoredObject $object) + { + $type = $object->getType(); + $objectKey = sprintf('%!%', $type, $object->host_name); + if ($type === 'service') { + $objectKey .= '!' . $object->service_description; + } + return $objectKey; + } + + protected static function config() + { + return Config::module('eventdb')->getSection('monitoring'); + } +} diff --git a/library/Eventdb/ProvidedHook/Monitoring/HostActions.php b/library/Eventdb/ProvidedHook/Monitoring/HostActions.php new file mode 100644 index 0000000..caf33e3 --- /dev/null +++ b/library/Eventdb/ProvidedHook/Monitoring/HostActions.php @@ -0,0 +1,15 @@ +<?php +/* Icinga Web 2 - EventDB | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Eventdb\ProvidedHook\Monitoring; + +use Icinga\Module\Monitoring\Hook\HostActionsHook; +use Icinga\Module\Monitoring\Object\Host; + +class HostActions extends HostActionsHook +{ + public function getActionsForHost(Host $host) + { + return EventdbActionHook::getActions($host); + } +} diff --git a/library/Eventdb/ProvidedHook/Monitoring/ServiceActions.php b/library/Eventdb/ProvidedHook/Monitoring/ServiceActions.php new file mode 100644 index 0000000..61a7a87 --- /dev/null +++ b/library/Eventdb/ProvidedHook/Monitoring/ServiceActions.php @@ -0,0 +1,15 @@ +<?php +/* Icinga Web 2 - EventDB | (c) 2017 Icinga Development Team | GPLv2+ */ + +namespace Icinga\Module\Eventdb\ProvidedHook\Monitoring; + +use Icinga\Module\Monitoring\Hook\ServiceActionsHook; +use Icinga\Module\Monitoring\Object\Service; + +class ServiceActions extends ServiceActionsHook +{ + public function getActionsForService(Service $service) + { + return EventdbActionHook::getActions($service); + } +} diff --git a/library/Eventdb/Test/BaseTestCase.php b/library/Eventdb/Test/BaseTestCase.php new file mode 100644 index 0000000..d24ea49 --- /dev/null +++ b/library/Eventdb/Test/BaseTestCase.php @@ -0,0 +1,27 @@ +<?php + +namespace Icinga\Module\Eventdb\Test; + +use Icinga\Application\Icinga; +use PHPUnit_Framework_TestCase; + +abstract class BaseTestCase extends PHPUnit_Framework_TestCase +{ + private static $app; + + private $db; + + public function setUp() + { + $this->app(); + } + + protected function app() + { + if (self::$app === null) { + self::$app = Icinga::app(); + } + + return self::$app; + } +} diff --git a/library/Eventdb/Test/Bootstrap.php b/library/Eventdb/Test/Bootstrap.php new file mode 100644 index 0000000..848b360 --- /dev/null +++ b/library/Eventdb/Test/Bootstrap.php @@ -0,0 +1,35 @@ +<?php + +namespace Icinga\Module\Eventdb\Test; + +use Icinga\Application\EmbeddedWeb; +use Icinga\Authentication\Auth; +use Icinga\User; + +class Bootstrap +{ + public static function web($basedir = null) + { + error_reporting(E_ALL | E_STRICT); + if ($basedir === null) { + $basedir = dirname(dirname(dirname(__DIR__))); + } + $testsDir = $basedir . '/test'; + require_once 'Icinga/Application/EmbeddedWeb.php'; + + if (array_key_exists('ICINGAWEB_CONFIGDIR', $_SERVER)) { + $configDir = $_SERVER['ICINGAWEB_CONFIGDIR']; + } else { + $configDir = $testsDir . '/config'; + } + + EmbeddedWeb::start($testsDir, $configDir) + ->getModuleManager() + ->loadModule('eventdb', $basedir) + ->loadModule('monitoring', $basedir . '/vendor/icingaweb2/modules/monitoring'); + + $user = new User('icingaadmin'); + $user->setPermissions(array('*')); + Auth::getInstance()->setAuthenticated($user); + } +} diff --git a/library/Eventdb/Test/PseudoHost.php b/library/Eventdb/Test/PseudoHost.php new file mode 100644 index 0000000..7ebcd8c --- /dev/null +++ b/library/Eventdb/Test/PseudoHost.php @@ -0,0 +1,15 @@ +<?php + +namespace Icinga\Module\Eventdb\Test; + +use Icinga\Module\Monitoring\Object\Host; + +class PseudoHost extends Host +{ + public function provideCustomVars($vars) + { + $this->properties = new \stdClass; + $this->hostVariables = $this->customvars = $vars; + return $this; + } +} diff --git a/library/Eventdb/Test/PseudoMonitoringBackend.php b/library/Eventdb/Test/PseudoMonitoringBackend.php new file mode 100644 index 0000000..3ad6de5 --- /dev/null +++ b/library/Eventdb/Test/PseudoMonitoringBackend.php @@ -0,0 +1,14 @@ +<?php + +namespace Icinga\Module\Eventdb\Test; + +use Icinga\Data\ConfigObject; +use Icinga\Module\Monitoring\Backend\MonitoringBackend; + +class PseudoMonitoringBackend extends MonitoringBackend +{ + public static function dummy() + { + return new static('dummy', new ConfigObject()); + } +} diff --git a/library/Eventdb/Test/PseudoService.php b/library/Eventdb/Test/PseudoService.php new file mode 100644 index 0000000..737a8ac --- /dev/null +++ b/library/Eventdb/Test/PseudoService.php @@ -0,0 +1,16 @@ +<?php + +namespace Icinga\Module\Eventdb\Test; + +use Icinga\Module\Monitoring\Object\Service; + +class PseudoService extends Service +{ + public function provideCustomVars($vars) + { + $this->properties = new \stdClass; + $this->serviceVariables = $this->customvars = $vars; + $this->hostVariables = array(); + return $this; + } +} diff --git a/library/Eventdb/Web/EventdbOutputFormat.php b/library/Eventdb/Web/EventdbOutputFormat.php new file mode 100644 index 0000000..ceec1ce --- /dev/null +++ b/library/Eventdb/Web/EventdbOutputFormat.php @@ -0,0 +1,66 @@ +<?php + +namespace Icinga\Module\Eventdb\Web; + +use Icinga\Web\Widget\Tabextension\OutputFormat; +use Icinga\Web\Widget\Tabs; + +class EventdbOutputFormat extends OutputFormat +{ + /** + * TEXT output type + */ + const TYPE_TEXT = 'text'; + + /** + * Types that are disabled by default + * + * @var array + */ + protected static $disabledTypes = array(self::TYPE_PDF, self::TYPE_CSV); + + /** + * Types that are enabled in addition to default + * + * @var array + */ + protected $enable = array(); + + /** + * {@inheritdoc} + */ + public function __construct($disabled = array(), $enable = array()) + { + $this->enable = $enable; + $disabled = array_merge(static::$disabledTypes, $disabled); + parent::__construct($disabled); + } + + /** + * {@inheritdoc} + */ + public function getSupportedTypes() + { + $supported = parent::getSupportedTypes(); + + if (in_array(self::TYPE_TEXT, $this->enable)) { + $supported[self::TYPE_TEXT] = array( + 'name' => 'text', + 'label' => mt('eventdb', 'Text'), + 'icon' => 'doc-text', + 'urlParams' => array('format' => 'text'), + ); + } + + return $supported; + } + + public function apply(Tabs $tabs) + { + parent::apply($tabs); + + if ($textTab = $tabs->get(self::TYPE_TEXT)) { + $textTab->setTargetBlank(false); + } + } +} |