From e1895db33b0798a1d557ad441ba9a266baaa6d83 Mon Sep 17 00:00:00 2001 From: Markus Opolka Date: Fri, 10 May 2024 10:25:06 +0200 Subject: [PATCH 1/9] Remove legacy DB support - This removes all support for converting legacy Icinga Web 1 views. Rationale being that this is likely not a use case any more and it does not need to be supported in the future (i.e. when migrating this to Icinga DB) --- application/clicommands/ConvertCommand.php | 104 ---- application/clicommands/LegacyCommand.php | 92 --- application/views/helpers/Badges.php | 1 - library/Toplevelview/Command.php | 44 -- library/Toplevelview/Config/ConfigEmitter.php | 72 --- .../Toplevelview/Legacy/LegacyDbHelper.php | 525 ------------------ 6 files changed, 838 deletions(-) delete mode 100644 application/clicommands/ConvertCommand.php delete mode 100644 application/clicommands/LegacyCommand.php delete mode 100644 library/Toplevelview/Command.php delete mode 100644 library/Toplevelview/Config/ConfigEmitter.php delete mode 100644 library/Toplevelview/Legacy/LegacyDbHelper.php diff --git a/application/clicommands/ConvertCommand.php b/application/clicommands/ConvertCommand.php deleted file mode 100644 index a05538b..0000000 --- a/application/clicommands/ConvertCommand.php +++ /dev/null @@ -1,104 +0,0 @@ - */ - -namespace Icinga\Module\Toplevelview\Clicommands; - -use Icinga\Exception\ConfigurationError; -use Icinga\Module\Toplevelview\Command; -use Icinga\Module\Toplevelview\Config\ConfigEmitter; -use Icinga\Module\Toplevelview\Legacy\LegacyDbHelper; -use Icinga\Module\Toplevelview\ViewConfig; -use Zend_Db_Adapter_Pdo_Sqlite; - -/** - * Converts a TopLevelView database into the new YAML configuration format - */ -class ConvertCommand extends Command -{ - protected $dbConnections = array(); - - public function init() - { - parent::init(); - - if (! extension_loaded('pdo_sqlite')) { - throw new ConfigurationError('You need the PHP extension "pdo_sqlite" in order to convert TopLevelView'); - } - } - - /** - * List all hierarchies in the database - * - * Arguments: - * --db SQLite3 data from from old TopLevelView module - */ - public function listAction() - { - $dbFile = $this->params->getRequired('db'); - $db = $this->sqlite($dbFile); - - $helper = new LegacyDbHelper($db); - foreach ($helper->fetchHierarchies() as $root) { - printf("[%d] %s\n", $root['id'], $root['name']); - } - } - - /** - * Generate a YAML config file for Icinga Web 2 module - * - * Arguments: - * --db SQLite3 data from from old TopLevelView module - * --id Database id to export (see list) - * --output Write to file (default '-' for stdout) - * --name If name is specified instead of file, - * config is saved under that name - */ - public function convertAction() - { - $dbFile = $this->params->getRequired('db'); - $db = $this->sqlite($dbFile); - - $id = $this->params->getRequired('id'); - - $output = $this->params->get('output', null); - $name = $this->params->get('name', null); - $format = $this->params->get('format', 'yaml'); - - $helper = new LegacyDbHelper($db, $this->monitoringBackend()); - $tree = $helper->fetchTree($id); - - $emitter = ConfigEmitter::fromLegacyTree($tree); - - if ($name !== null and $output === null) { - $viewConfig = new ViewConfig(); - $viewConfig->setConfigDir(); - $viewConfig->setFormat(ViewConfig::FORMAT_YAML); - $viewConfig->setName($name); - $viewConfig->setText($emitter->emitYAML($format)); - $viewConfig->store(); - printf("Saved as config %s\n", $name); - exit(0); - } - - $text = $emitter->emit($format); - if ($output === null || $output === '-') { - echo $text; - } else { - file_put_contents($output, $text); - } - } - - /** - * Sets up the Zend PDO resource for SQLite - * - * @param string $file - * - * @return Zend_Db_Adapter_Pdo_Sqlite - */ - protected function sqlite($file) - { - return new Zend_Db_Adapter_Pdo_Sqlite(array( - 'dbname' => $file, - )); - } -} diff --git a/application/clicommands/LegacyCommand.php b/application/clicommands/LegacyCommand.php deleted file mode 100644 index f9784be..0000000 --- a/application/clicommands/LegacyCommand.php +++ /dev/null @@ -1,92 +0,0 @@ - */ - -namespace Icinga\Module\Toplevelview\Clicommands; - -use Icinga\Exception\ConfigurationError; -use Icinga\Module\Monitoring\Backend\MonitoringBackend; -use Icinga\Module\Toplevelview\Command; -use Icinga\Module\Toplevelview\Legacy\LegacyDbHelper; -use Zend_Db_Adapter_Pdo_Sqlite; - -/** - * Tools for the legacy DB - */ -class LegacyCommand extends Command -{ - public function init() - { - parent::init(); - - if (! extension_loaded('pdo_sqlite')) { - throw new ConfigurationError('You need the PHP extension "pdo_sqlite" in order to convert TopLevelView'); - } - } - - /** - * Delete unreferenced objects from the database - * - * Arguments: - * --db SQLite3 data from from old TopLevelView module - * --noop Only show counts, don't delete - */ - public function cleanupAction() - { - $dbFile = $this->params->getRequired('db'); - $noop = $this->params->shift('noop'); - $db = $this->sqlite($dbFile); - - $helper = new LegacyDbHelper($db); - - $result = $helper->cleanupUnreferencedObjects($noop); - foreach ($result as $type => $c) { - printf("%s: %d\n", $type, $c); - } - } - - /** - * Migrate database ids from an IDO to another IDO - * - * Arguments: - * --db SQLite3 data from from old TopLevelView module - * --target Target database path (will be overwritten) - * --old OLD IDO backend (configured in monitoring module) - * --new New IDO backend (configured in monitoring module) (optional) - * --purge Remove unresolvable data during update (see log) - */ - public function idomigrateAction() - { - $dbFile = $this->params->getRequired('db'); - $old = $this->params->getRequired('old'); - $target = $this->params->getRequired('target'); - $new = $this->params->get('new'); - $purge = $this->params->shift('purge'); - - $db = $this->sqlite($dbFile); - - $helper = new LegacyDbHelper($db, MonitoringBackend::instance($new)); - $helper->setOldBackend(MonitoringBackend::instance($old)); - - // Use the copy as db - $helper->setDb($helper->copySqliteDb($db, $target)); - - $result = $helper->migrateObjectIds(false, $purge); - foreach ($result as $type => $c) { - printf("%s: %d\n", $type, $c); - } - } - - /** - * Sets up the Zend PDO resource for SQLite - * - * @param string $file - * - * @return Zend_Db_Adapter_Pdo_Sqlite - */ - protected function sqlite($file) - { - return new Zend_Db_Adapter_Pdo_Sqlite(array( - 'dbname' => $file, - )); - } -} diff --git a/application/views/helpers/Badges.php b/application/views/helpers/Badges.php index 6066a72..b70d6ed 100644 --- a/application/views/helpers/Badges.php +++ b/application/views/helpers/Badges.php @@ -2,7 +2,6 @@ /* Icinga Web 2 Top Level View | (c) 2017 Icinga Development Team | GPLv2+ */ use Icinga\Module\Toplevelview\Tree\TLVStatus; -use Icinga\Web\Url; class Zend_View_Helper_Badges extends Zend_View_Helper_Abstract { diff --git a/library/Toplevelview/Command.php b/library/Toplevelview/Command.php deleted file mode 100644 index 95fb47b..0000000 --- a/library/Toplevelview/Command.php +++ /dev/null @@ -1,44 +0,0 @@ - */ - -namespace Icinga\Module\Toplevelview; - -use Icinga\Application\Icinga; -use Icinga\Exception\ConfigurationError; -use Icinga\Exception\IcingaException; -use Icinga\Module\Monitoring\Backend\MonitoringBackend; -use Icinga\Cli\Command as IcingaCommand; - -class Command extends IcingaCommand -{ - /** @var MonitoringBackend */ - protected $monitoringBackend; - - public function init() - { - parent::init(); - - if (! extension_loaded('yaml')) { - throw new ConfigurationError('You need the PHP extension "yaml" in order to use TopLevelView'); - } - } - - /** - * 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; - } -} diff --git a/library/Toplevelview/Config/ConfigEmitter.php b/library/Toplevelview/Config/ConfigEmitter.php deleted file mode 100644 index b8caff9..0000000 --- a/library/Toplevelview/Config/ConfigEmitter.php +++ /dev/null @@ -1,72 +0,0 @@ - */ - -namespace Icinga\Module\Toplevelview\Config; - -use Icinga\Exception\NotImplementedError; -use stdClass; - -class ConfigEmitter -{ - /** @var array */ - protected $config; - - public function __construct($config) - { - $this->config = $config; - } - - public static function classToArray($obj) - { - $arr = array(); - foreach (get_object_vars($obj) as $k => $v) { - if ($k !== 'children') { - $arr[$k] = $v; - } - } - - // handle children last for visibility - if (property_exists($obj, 'children')) { - $arr['children'] = array(); - foreach ($obj->children as $child) { - // convert each child to an array - $arr['children'][] = static::classToArray($child); - } - } - - return $arr; - } - - public static function fromLegacyTree(stdClass $tree) - { - return new static(static::classToArray($tree)); - } - - public function emitJSON(&$contentType = null) - { - $contentType = 'application/json'; - return json_encode($this->config); - } - - public function emitYAML(&$contentType = null) - { - $contentType = 'application/yaml'; - return yaml_emit($this->config, YAML_UTF8_ENCODING, YAML_LN_BREAK); - } - - public function emitEXPORT(&$contentType = null) - { - $contentType = 'text/plain'; - return var_export($this->config, true); - } - - public function emit($format, &$contentType = null) - { - $funcName = 'emit' . strtoupper($format); - if (method_exists($this, $funcName)) { - return $this->$funcName($contentType); - } else { - throw new NotImplementedError('format "%s" is not implemented to emit!', $format); - } - } -} diff --git a/library/Toplevelview/Legacy/LegacyDbHelper.php b/library/Toplevelview/Legacy/LegacyDbHelper.php deleted file mode 100644 index ae50256..0000000 --- a/library/Toplevelview/Legacy/LegacyDbHelper.php +++ /dev/null @@ -1,525 +0,0 @@ - */ - -namespace Icinga\Module\Toplevelview\Legacy; - -use Icinga\Application\Benchmark; -use Icinga\Application\Logger; -use Icinga\Exception\IcingaException; -use Icinga\Exception\NotFoundError; -use Icinga\Exception\ProgrammingError; -use Icinga\Module\Monitoring\Backend\MonitoringBackend; -use stdClass; -use Zend_Db_Adapter_Pdo_Abstract; -use Zend_Db_Adapter_Pdo_Sqlite; - -class LegacyDbHelper -{ - /** @var Zend_Db_Adapter_Pdo_Abstract */ - protected $db; - - /** @var MonitoringBackend */ - protected $backend; - - /** @var MonitoringBackend */ - protected $oldBackend; - - protected static $idoObjectIds = [ - 'host' => 1, - 'service' => 2, - 'hostgroup' => 3, - ]; - - public function __construct(Zend_Db_Adapter_Pdo_Abstract $db, MonitoringBackend $backend = null) - { - $this->db = $db; - $this->backend = $backend; - } - - public function fetchHierarchies() - { - $query = $this->db->select() - ->from('toplevelview_view_hierarchy AS h', array( - 'id', - )) - ->joinLeft('toplevelview_view AS v', 'v.id = h.view_id', array( - 'name', - 'display_name', - )) - ->where('h.level = ?', 0) - ->where('h.root_id = h.id'); - - return $this->db->fetchAll($query); - } - - /** - * Purges stale object references from the database - * - * Apparently the original editor replaces the tree data, - * but leaves unreferenced objects where the view_id has - * no referenced row in toplevelview_view. - * - * @param bool $noop Only check but don't delete - * - * @return array object types with counts cleaned up - */ - public function cleanupUnreferencedObjects($noop = false) - { - $results = [ - 'host' => 0, - 'hostgroup' => 0, - 'service' => 0 - ]; - - foreach (array_keys($results) as $type) { - $query = $this->db->select() - ->from("toplevelview_${type} AS o", ['id']) - ->joinLeft('toplevelview_view AS v', 'v.id = o.view_id', []) - ->where('v.id IS NULL'); - - Logger::debug("searching for unreferenced %s objects: %s", $type, (string) $query); - - $ids = $this->db->fetchCol($query); - $results[$type] = count($ids); - - if (! $noop) { - Logger::debug("deleting unreferenced %s objects: %s", $type, json_encode($ids)); - $this->db->delete("toplevelview_${type}", sprintf('id IN (%s)', join(', ', $ids))); - } - } - - return $results; - } - - /** - * Migrate object ids from an old MonitoringBackend to a new one - * - * Since data is not stored as names, we need to lookup a name for each id, - * and get the new id from the other backend. - * - * @param bool $noop Do not update the database - * @param bool $removeUnknown Remove objects that are unknown in (new) IDO DB - * - * @return int[] - * @throws IcingaException - * @throws \Zend_Db_Adapter_Exception - */ - public function migrateObjectIds($noop = false, $removeUnknown = false) - { - $result = [ - 'host' => 0, - 'service' => 0, - 'hostgroup' => 0, - ]; - - foreach (array_keys($result) as $type) { - $query = $this->db->select() - ->from("toplevelview_${type}", ['id', "${type}_object_id AS object_id"]); - - Logger::debug("querying stored objects of type %s: %s", $type, (string) $query); - - $objects = []; - - // Load objects indexed by object_id - foreach ($this->db->fetchAll($query) as $row) { - $objects[$row['object_id']] = (object) $row; - } - - // Load names from old DB - $idoObjects = $this->oldBackend->getResource()->select() - ->from('icinga_objects', ['object_id', 'name1', 'name2']) - ->where('objecttype_id', self::$idoObjectIds[$type]); - - // Amend objects with names from old DB - foreach ($idoObjects->fetchAll() as $row) { - $id = $row->object_id; - if (array_key_exists($id, $objects)) { - $idx = $row->name1; - if ($row->name2 !== null) { - $idx .= '!' . $row->name2; - } - - $objects[$id]->name = $idx; - } - } - - // Load names from new DB and index by name - $newObjects = []; - foreach ($this->backend->getResource()->fetchAll($idoObjects) as $row) { - $idx = $row->name1; - if ($row->name2 !== null) { - $idx .= '!' . $row->name2; - } - - $newObjects[$idx] = $row; - } - - // Process all objects and store new id - $errors = 0; - foreach ($objects as $object) { - if (! property_exists($object, 'name')) { - Logger::error("object %s %d has not been found in old IDO", $type, $object->object_id); - $errors++; - } else if (! array_key_exists($object->name, $newObjects)) { - Logger::error("object %s %d '%s' has not been found in new IDO", - $type, $object->object_id, $object->name); - $errors++; - } else { - $object->new_object_id = $newObjects[$object->name]->object_id; - $result[$type]++; - } - } - - if (! $removeUnknown && $errors > 0) { - throw new IcingaException("errors have occurred during IDO id migration - see log"); - } - - if (! $noop) { - foreach ($objects as $object) { - if (property_exists($object, 'new_object_id')) { - $this->db->update( - "toplevelview_${type}", - ["${type}_object_id" => $object->new_object_id], - ["${type}_object_id = ?" => $object->object_id] - ); - } else if ($removeUnknown) { - $this->db->delete( - "toplevelview_${type}", - ["${type}_object_id = ?" => $object->object_id] - ); - } - } - } - } - - return $result; - } - - /** - * @param Zend_Db_Adapter_Pdo_Sqlite $db - * @param string $target - * - * @return Zend_Db_Adapter_Pdo_Sqlite - */ - public function copySqliteDb(Zend_Db_Adapter_Pdo_Sqlite $db, $target) - { - // Lock database for copy - $db->query('PRAGMA locking_mode = EXCLUSIVE'); - $db->query('BEGIN EXCLUSIVE'); - - $file = $db->getConfig()['dbname']; - if (! copy($file, $target)) { - throw new IcingaException("could not copy '%s' to '%s'", $file, $target); - } - - $db->query('COMMIT'); - $db->query('PRAGMA locking_mode = NORMAL'); - - return new Zend_Db_Adapter_Pdo_Sqlite([ - 'dbname' => $target, - ]); - } - - protected function fetchDatabaseHierarchy($root_id) - { - $query = $this->db->select() - ->from('toplevelview_view_hierarchy AS p', array()) - ->joinInner( - 'toplevelview_view_hierarchy AS n', - 'n.root_id = p.root_id AND (n.lft BETWEEN p.lft AND p.rgt) AND n.level >= p.level', - array('id', 'level') - )->joinInner( - 'toplevelview_view AS v', - 'v.id = n.view_id', - array('name', 'display_name') - )->joinLeft( - 'toplevelview_host AS h', - 'h.view_id = v.id', - array('h.host_object_id') - )->joinLeft( - 'toplevelview_service AS s', - 's.view_id = v.id', - array('s.service_object_id') - )->joinLeft( - 'toplevelview_hostgroup AS hg', - 'hg.view_id = v.id', - array('hg.hostgroup_object_id') - )->where( - 'p.id = ?', - $root_id - )->group(array( - 'n.root_id', - 'n.lft', - // 'n.id', - 'h.host_object_id', - 's.service_object_id', - 'hg.hostgroup_object_id', - ))->order(array( - 'n.lft', - 'hg.hostgroup_object_id', - 'h.host_object_id', - 's.service_object_id', - )); - - $nodes = $this->db->fetchAll($query); - - if (empty($nodes)) { - throw new NotFoundError('Could not find tree for root_id %d', $root_id); - } - - return $nodes; - } - - protected function buildTree($root_id, $nodes, &$hosts, &$services, &$hostgroups) - { - /** @var stdClass $tree */ - $tree = null; - /** @var stdClass $currentParent */ - $currentParent = null; - $currentNode = null; - $currentLevel = null; - $currentId = null; - $chain = array(); - - $currentHostId = null; - - $hosts = array(); - $hostgroups = array(); - $services = array(); - - foreach ($nodes as $node) { - $node = (object) $node; - $node->id = (int) $node->id; - $node->level = (int) $node->level; - - if ($currentId === null || $currentId !== $node->id) { - // only add the node once (all hosts, services and hostgroups are attached on the same node) - $chain[$node->level] = $node; - - if ($tree === null || $node->id === $root_id) { - $currentParent = $tree = $node; - - // minor tweak: remove "top level view" from title - $newTitle = preg_replace('/^Top\s*Level\s*View\s*/i', '', $tree->name); - if (strlen($newTitle) > 4) { - $tree->name = $tree->display_name = $newTitle; - } - - // add old default behavior for status - $tree->host_never_unhandled = true; - $tree->notification_periods = true; - $tree->ignored_notification_periods = ['notification_none']; // migration for Director - } elseif ($node->level > $currentLevel) { - // level down - $currentParent = $chain[$node->level - 1]; - - if (! property_exists($currentParent, 'children')) { - $currentParent->children = array(); - } - $currentParent->children[] = $node; - } elseif ($node->level === $currentLevel) { - // same level - $currentParent->children[] = $node; - } elseif ($node->level < $currentLevel) { - // level up - $currentParent = $chain[$node->level - 1]; - $currentParent->children[] = $node; - } - - if ($node->name === $node->display_name) { - unset($node->display_name); - } - - $currentId = $node->id; - $currentNode = $node; - - // clear current host when node changes - $currentHostId = null; - - // remove unused values - unset($node->id); - unset($node->level); - } - - if (property_exists($node, 'host_object_id') - && $node->host_object_id !== null - && $currentHostId !== $node->host_object_id - ) { - $currentHostId = $node->host_object_id; - - $host = new stdClass; - $host->host = 'UNKNOWN_HOST_' . $node->host_object_id; - $host->type = 'host'; - $host->object_id = $node->host_object_id; - - if (! property_exists($currentNode, 'children')) { - $currentNode->children = array(); - } - - $currentNode->children['host_' . $node->host_object_id] = $host; - $hosts[$node->host_object_id][] = $host; - } - unset($currentNode->host_object_id); - - if (property_exists($node, 'service_object_id') && $node->service_object_id !== null) { - $service = new stdClass; - $service->host = 'UNKNOWN_HOST'; - $service->service = 'UNKNOWN_SERVICE_' . $node->service_object_id; - $service->type = 'service'; - $service->object_id = $node->service_object_id; - - if (! property_exists($currentNode, 'children')) { - $currentNode->children = array(); - } - $currentNode->children['hostservice_' . $node->service_object_id] = $service; - $services[$node->service_object_id][] = $service; - } - unset($currentNode->service_object_id); - - if (property_exists($node, 'hostgroup_object_id') && $node->hostgroup_object_id !== null) { - $hostgroup = new stdClass; - $hostgroup->hostgroup = 'UNKNOWN_HOSTGROUP_' . $node->hostgroup_object_id; - $hostgroup->type = 'hostgroup'; - $hostgroup->object_id = $node->hostgroup_object_id; - - if (! property_exists($currentNode, 'children')) { - $currentNode->children = array(); - } - $currentNode->children['hostgroup_' . $node->hostgroup_object_id] = $hostgroup; - $hostgroups[$node->hostgroup_object_id][] = $hostgroup; - } - unset($currentNode->hostgroup_object_id); - } - - return $tree; - } - - public function fetchTree($root_id) - { - Benchmark::measure('fetchTree: begin'); - - $nodes = $this->fetchDatabaseHierarchy($root_id); - - Benchmark::measure('fetchTree: fetchAll done'); - - $tree = $this->buildTree($root_id, $nodes, $hosts, $services, $hostgroups); - - Benchmark::measure('fetchTree: done building tree'); - - if (! empty($hosts)) { - $hostNames = $this->fetchHosts(array_keys($hosts)); - foreach ($hosts as $objectId => $nodes) { - if (array_key_exists($objectId, $hostNames)) { - foreach ($nodes as $node) { - $node->host = $hostNames[$objectId]; - } - } - } - } - - Benchmark::measure('fetchTree: done getting host info'); - - if (! empty($services)) { - $icingaServices = $this->fetchServices(array_keys($services)); - foreach ($services as $objectId => $nodes) { - if (array_key_exists($objectId, $icingaServices)) { - foreach ($nodes as $node) { - $s = $icingaServices[$objectId]; - $node->host = $s->host_name; - $node->service = $s->service_name; - } - } - } - } - - Benchmark::measure('fetchTree: done getting service info'); - - if (! empty($hostgroups)) { - $icingaHostgroups = $this->fetchHostgroups(array_keys($hostgroups)); - foreach ($hostgroups as $objectId => $nodes) { - if (array_key_exists($objectId, $icingaHostgroups)) { - foreach ($nodes as $node) { - $node->hostgroup = $icingaHostgroups[$objectId]; - } - } - } - } - - Benchmark::measure('fetchTree: done getting service info'); - - return $tree; - } - - protected function fetchHosts($ids) - { - return $this->monitoringBackend()->getResource()->select() - ->from('icinga_objects', array( - 'object_id', - 'name1', - )) - ->where('object_id', $ids) - ->where('objecttype_id', 1) - ->fetchPairs(); - } - - protected function fetchServices($ids) - { - $rows = $this->monitoringBackend()->getResource()->select() - ->from('icinga_objects', array( - 'object_id' => 'object_id', - 'host_name' => 'name1', - 'service_name' => 'name2', - )) - ->where('object_id', $ids) - ->where('objecttype_id', 2) - ->fetchAll(); - - $services = array(); - foreach ($rows as $row) { - $services[$row->object_id] = $row; - } - return $services; - } - - protected function fetchHostgroups($ids) - { - return $this->monitoringBackend()->getResource()->select() - ->from('icinga_objects', array( - 'object_id', - 'name1', - )) - ->where('object_id', $ids) - ->where('objecttype_id', 3) - ->fetchPairs(); - } - - protected function monitoringBackend() - { - if ($this->backend === null) { - throw new ProgrammingError('monitoringBackend has not been set at runtime!'); - } - return $this->backend; - } - - /** - * @param MonitoringBackend $oldBackend - * - * @return LegacyDbHelper - */ - public function setOldBackend(MonitoringBackend $oldBackend) - { - $this->oldBackend = $oldBackend; - return $this; - } - - /** - * @param Zend_Db_Adapter_Pdo_Sqlite $db - * - * @return $this - */ - public function setDb($db) - { - $this->db = $db; - return $this; - } -} From e9f4e46f47211a8a72bb970721b2f883337d5c85 Mon Sep 17 00:00:00 2001 From: Markus Opolka Date: Fri, 10 May 2024 13:14:10 +0200 Subject: [PATCH 2/9] Update .gitignore --- .gitignore | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 2dfe791..4cc7592 100644 --- a/.gitignore +++ b/.gitignore @@ -2,14 +2,11 @@ .*.sw[op] *~ /.idea/ - -# Except those related to git !.git* - -# Docker -/docker-compose.yml +*TODO* # Artifacts from vendor parts in testing +/docker-compose.yml /*.phar /Icinga /Zend From fed9c5f40b9a601737e3af28ad2b9d6bafa86e90 Mon Sep 17 00:00:00 2001 From: Markus Opolka Date: Fri, 10 May 2024 13:14:01 +0200 Subject: [PATCH 3/9] Add support for Icinga DB Web - Breaking: changed option names and removed out_of_notification_period option - This migrates the use of the Monitoring Module to Icinga DB Web - Removes CLI Command to convert Icinga 1 TLV - Removes old database models --- .gitignore | 5 +- README.md | 2 +- application/controllers/ShowController.php | 2 - application/views/helpers/Badges.php | 4 + application/views/helpers/Breadcrumb.php | 2 + application/views/helpers/Tiles.php | 3 + application/views/helpers/Tree.php | 35 ++-- doc/01-Introduction.md | 14 +- doc/02-Behavior.md | 39 +--- doc/10-User-Guide.md | 9 +- doc/20-Editor.md | 6 +- doc/21-Config-Format.md | 15 +- doc/90-Limits.md | 27 --- .../Monitoring/HostgroupQuery.php | 132 ------------- .../Monitoring/Hostgroupsummary.php | 33 ---- .../Monitoring/HostgroupsummaryQuery.php | 157 --------------- .../Monitoring/IgnoredNotificationPeriods.php | 49 ----- library/Toplevelview/Monitoring/Options.php | 33 ---- .../Toplevelview/Monitoring/Servicestatus.php | 38 ---- .../Monitoring/ServicestatusQuery.php | 87 --------- .../Toplevelview/Tree/TLVHostGroupNode.php | 183 +++++++++--------- library/Toplevelview/Tree/TLVHostNode.php | 119 +++++++----- library/Toplevelview/Tree/TLVServiceNode.php | 162 ++++++++-------- library/Toplevelview/Tree/TLVStatus.php | 5 +- library/Toplevelview/Tree/TLVTree.php | 26 +-- library/Toplevelview/Tree/TLVTreeNode.php | 3 + library/Toplevelview/ViewConfig.php | 1 - library/Toplevelview/Web/Controller.php | 23 +-- module.info | 6 +- 29 files changed, 327 insertions(+), 893 deletions(-) delete mode 100644 doc/90-Limits.md delete mode 100644 library/Toplevelview/Monitoring/HostgroupQuery.php delete mode 100644 library/Toplevelview/Monitoring/Hostgroupsummary.php delete mode 100644 library/Toplevelview/Monitoring/HostgroupsummaryQuery.php delete mode 100644 library/Toplevelview/Monitoring/IgnoredNotificationPeriods.php delete mode 100644 library/Toplevelview/Monitoring/Options.php delete mode 100644 library/Toplevelview/Monitoring/Servicestatus.php delete mode 100644 library/Toplevelview/Monitoring/ServicestatusQuery.php diff --git a/.gitignore b/.gitignore index 4cc7592..bd18c50 100644 --- a/.gitignore +++ b/.gitignore @@ -2,10 +2,13 @@ .*.sw[op] *~ /.idea/ -!.git* +\#* +.\#* *TODO* +!.git* # Artifacts from vendor parts in testing +.phplint.cache/ /docker-compose.yml /*.phar /Icinga diff --git a/README.md b/README.md index 3e093fc..36455cb 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ please see the documentation on details. ## Requirements * Icinga Web 2 >= 2.5.0 - * and its monitoring module +* Icinga DB Web >=1.0.0 * php-yaml Also see [Introduction in docs](doc/01-Introduction.md). diff --git a/application/controllers/ShowController.php b/application/controllers/ShowController.php index d0560f2..0497616 100644 --- a/application/controllers/ShowController.php +++ b/application/controllers/ShowController.php @@ -58,7 +58,6 @@ public function indexAction() $this->view->name = $name = $this->params->getRequired('name'); $this->view->view = $view = ViewConfig::loadByName($name); $tree = $view->getTree(); - $tree->setBackend($this->monitoringBackend()); if (($lifetime = $this->getParam('cache')) !== null) { $tree->setCacheLifetime($lifetime); @@ -73,7 +72,6 @@ public function treeAction() $this->view->view = $view = ViewConfig::loadByName($name); $tree = $view->getTree(); $this->view->node = $tree->getById($this->params->getRequired('id')); - $tree->setBackend($this->monitoringBackend()); if (($lifetime = $this->getParam('cache')) !== null) { $tree->setCacheLifetime($lifetime); diff --git a/application/views/helpers/Badges.php b/application/views/helpers/Badges.php index b70d6ed..2bbe5e4 100644 --- a/application/views/helpers/Badges.php +++ b/application/views/helpers/Badges.php @@ -17,6 +17,10 @@ protected function prettyTitle($identifier) return trim($s); } + /** + * badges renders a TLVNode's TLVStatus into HTML. + * A badge represents the number of status of a given node + */ public function badges(TLVStatus $status, $problemsOnly = true, $showTotal = false) { $htm = ''; diff --git a/application/views/helpers/Breadcrumb.php b/application/views/helpers/Breadcrumb.php index 3263712..d155dee 100644 --- a/application/views/helpers/Breadcrumb.php +++ b/application/views/helpers/Breadcrumb.php @@ -9,6 +9,8 @@ class Zend_View_Helper_Breadcrumb extends Zend_View_Helper_Abstract public $view; /** + * breadcrumb renders a list of TLVTreeNodes into a HTML breadcrumb list + * * @param TLVTreeNode[] $breadcrumb * * @return string diff --git a/application/views/helpers/Tiles.php b/application/views/helpers/Tiles.php index dcefbfe..594da28 100644 --- a/application/views/helpers/Tiles.php +++ b/application/views/helpers/Tiles.php @@ -8,6 +8,9 @@ class Zend_View_Helper_Tiles extends Zend_View_Helper_Abstract /** @var \Icinga\Web\View */ public $view; + /** + * tiles renders a TLVTreeNode into a HTML TLV tile + */ public function tiles(TLVTreeNode $node, $levels = 2, $classes = array()) { $htm = ''; diff --git a/application/views/helpers/Tree.php b/application/views/helpers/Tree.php index a016b68..bd7c9ea 100644 --- a/application/views/helpers/Tree.php +++ b/application/views/helpers/Tree.php @@ -9,6 +9,10 @@ class Zend_View_Helper_Tree extends Zend_View_Helper_Abstract /** @var \Icinga\Web\View */ public $view; + /** + * tree takes a TLVTreeNode and renders it recursively into HTML. + * It is used in a Controller to render the full tree. + */ public function tree(TLVTreeNode $node, $classes = array(), $level = 0) { $htm = ''; @@ -23,29 +27,23 @@ public function tree(TLVTreeNode $node, $classes = array(), $level = 0) if ($type === 'host') { $icon = 'host'; - $url = Url::fromPath( - 'monitoring/host/show', - array( - 'host' => $node->get('host') - ) - ); + $url = Url::fromPath('icingadb/host', ['name' => $node->get('host')]); } elseif ($type === 'service') { $icon = 'service'; $url = Url::fromPath( - 'monitoring/service/show', - array( - 'host' => $node->get('host'), - 'service' => $node->get('service') - ) + 'icingadb/service', + [ + 'name' => $node->get('service'), + 'host.name' => $node->get('host') + ] ); } elseif ($type === 'hostgroup') { $icon = 'cubes'; $url = Url::fromPath( - 'monitoring/list/services', + 'icingadb/services', array( - 'hostgroup' => $node->get('hostgroup'), - 'sort' => 'service_severity', - 'dir' => 'desc', + 'hostgroup.name' => $node->get('hostgroup'), + 'sort' => 'service.state.severity desc' ) ); @@ -60,11 +58,10 @@ public function tree(TLVTreeNode $node, $classes = array(), $level = 0) $htmExtra .= ' ' . $this->view->qlink( $hostTitle, - 'monitoring/list/hosts', + 'icingadb/hosts', array( - 'hostgroup' => $node->get('hostgroup'), - 'sort' => 'host_severity', - 'dir' => 'desc', + 'hostgroup.name' => $node->get('hostgroup'), + 'sort' => 'service.state.severity desc' ), null, false diff --git a/doc/01-Introduction.md b/doc/01-Introduction.md index 84d9661..37814b3 100644 --- a/doc/01-Introduction.md +++ b/doc/01-Introduction.md @@ -4,7 +4,7 @@ Introduction Top Level View is a hierarchy based status view for Icinga Web 2. You can define a hierarchical structure containing hosts, services and hostgroups. -And the view presents you an overview of the overall status of the sub-hierarchies. +This view presents you an overview of the overall status of the sub-hierarchies. With a caching layer, this view can aggregate thousands of status objects and make them easily available for overview and drill down. @@ -12,14 +12,9 @@ them easily available for overview and drill down. This view extends the status logic and behavior of Icinga Web 2 a bit, please see later chapters on details. -## Requirements - -* Icinga Web 2 >= 2.5.0 - * and its monitoring module - ## Installation -The view is a simple module for Icinga Web 2, and can be installed via git or a tarball. +Top Level View is a module for Icinga Web 2, and can be installed via git or a tarball. Only other requirement is PHP YAML (which is needed for the configuration format), make sure to reload your web server after installing the module. @@ -41,11 +36,8 @@ Or if you prefer use git. git clone https://github.com/Icinga/icingaweb2-module-toplevelview.git \ /usr/share/icingaweb2/modules/toplevelview + git checkout v0.x.x Enable the module in the web interface, or via CLI: icingacli module enable toplevelview - -## Permissions - -TODO diff --git a/doc/02-Behavior.md b/doc/02-Behavior.md index ae2444b..9e6ba0c 100644 --- a/doc/02-Behavior.md +++ b/doc/02-Behavior.md @@ -1,7 +1,7 @@ Behavior ======== -Top Level View uses additional status logic for it's views. +Top Level View uses additional status logic for its views. This does not affect the overall status behavior of Icinga 2 or Icinga Web 2, but it is important to understand the differences. @@ -30,13 +30,12 @@ Similar to Icinga Web 2 you can easily see unhandled problems by the strength of ![Unhandled problems](screenshots/colors-unhandled.png) ![Handled problems](screenshots/colors-handled.png) -## SOFT states +## SOFT and HARD states While the normal monitoring views will always show you all current states, -the Top Level Views will only show hard states. +the **Top Level Views will only show hard states**. -Which means, as long as the object doesn't have reached a hard state, the -node should be OK and green. +Which means, as long as the object doesn't have reached a hard state, the node will be OK. ## Handled and Unhandled @@ -53,7 +52,7 @@ In Top Level View, a few things are different: * Notification settings can influence a status (see next topic) * Flapping means the state is handled -## Downtime and Notification Periods +## Downtime and Notifications Since downtime and notification settings are essential for alerting, Top Level Views tries to integrate these into its status logic. @@ -62,7 +61,6 @@ The following behaviors will trigger the downtime logic: * Host or Service is in an active downtime * Notifications are disabled for the host or service -* Host or Service is out of its notification period If those conditions are met: @@ -73,29 +71,12 @@ If those conditions are met: Some features can be enabled by view, and control some additional behavior. -### Host always handled +### Option `override_host_problem_to_handled` (bool) -Option `host_never_unhandled` - -Every host problem is set to unhandled by default. +Override every host problem to handled. + +* `override_host_problem_to_handled = true` +* `override_host_problem_to_handled = false` This helps when alerting is mostly based on service states, and the host is only a container. - -This was a behavior of the old Top Level View, and is enabled on convert. - -### Enabling notification period - -Option `notification_periods` - -Checks the configured notification_period and handles "out of period" as downtime active state. - -This was a behavior of the old Top Level View, and is enabled on convert. - -Also see [limitations](90-Limits.md) for this setting. - -### Ignoring certain notification periods - -Option `ignored_notification_periods` - -This notification periods will be ignored for "out of period" checking, see above. diff --git a/doc/10-User-Guide.md b/doc/10-User-Guide.md index 29d7917..762d632 100644 --- a/doc/10-User-Guide.md +++ b/doc/10-User-Guide.md @@ -10,7 +10,7 @@ To understand the status behavior, see the [chapter about behavior](02-Behavior. Unhandled problems can be identified based on color saturation of the tiles. -In addition counters show you how many states of what kind lay below. +In addition counters show you how many states of what kind lay below. ![Unhandled colors](screenshots/colors-unhandled.png) @@ -36,7 +36,7 @@ Every Icinga status object, a host, service or hostgroup is a single tile here. Counters are meant to give you an indication about how many problems, or -even objects are there. +even objects are there. ## Viewing Icinga details @@ -59,7 +59,8 @@ over hosts in that group. ## Note about caching -Please be aware that the data display in Top Level View is cached, for 60 -seconds by default. +Please be aware that the data displayed in the Top Level View is cached for 60 +seconds by default. This can be adjusted using the URL parameter `cache`. +For example: `toplevelview/show/tree?name=example-view&cache=30` Latest state changes will only be reflected after that caching time. diff --git a/doc/20-Editor.md b/doc/20-Editor.md index 28dd5c2..81ec8eb 100644 --- a/doc/20-Editor.md +++ b/doc/20-Editor.md @@ -8,7 +8,7 @@ Please also see the [chapter on config format](21-Config-Format.md). ## Saving -You have 2 options on saving: +You have two options on saving: * Save to session - so you can test and review your edit * Save to disk - it is stored to disk and visible for everyone @@ -20,7 +20,7 @@ Also you can cancel an edit with a button below the editor, and return to disk s ## History -Config history is saved to disk, so it can be restored manually. -But there is not interface yet. +Configuration history is saved to disk, so it can be restored manually. +But there is not web interface. See `/etc/icingaweb2/modules/toplevelview/views//*.yml`. diff --git a/doc/21-Config-Format.md b/doc/21-Config-Format.md index 33236e5..b3b59f0 100644 --- a/doc/21-Config-Format.md +++ b/doc/21-Config-Format.md @@ -11,7 +11,7 @@ For details on the format see [yaml.org](http://yaml.org/). The configuration is structured in a hierarchical object structure like: - root object -> children (array) -> objects -> more children + root object -> children (array) -> objects -> more children Every node is unique in the tree, but names can be repeated. An Icinga objects can be inserted multiple times. @@ -95,24 +95,19 @@ Attributes: ## Options -Additional options are available to control status behavior of -the respective view. - -* `host_never_unhandled` (boolean) Controls the host not being displayed as an unhandled problem. See [behavior](02-Behavior.md#host-always-handled) (Default: false) -* `notification_periods` (boolean) Controls checking notification_period to be in_period. See [behavior](02-Behavior.md#enabling-notification-period) (Default: false) - +Additional options are available to control status behavior of the respective view. These options are just set on the root node: ```yaml name: Test Config with Options -host_never_unhandled: true +override_host_problem_to_handled: true children: - name: Section 1 ``` ## Examples -Here is a longer example from a testing configuration +Here is a longer example from a testing configuration: ```yaml name: Test @@ -189,4 +184,4 @@ children: - name: Notexisting Object children: - host: notexisting -``` \ No newline at end of file +``` diff --git a/doc/90-Limits.md b/doc/90-Limits.md deleted file mode 100644 index 60dc2c7..0000000 --- a/doc/90-Limits.md +++ /dev/null @@ -1,27 +0,0 @@ -Limits of this module -===================== - -A few notes about compatibility to Icinga 2. - -## Notification Period - -Currently Icinga 2 does not publish `notification_period` to the IDO, due -to the new notification logic. - -When `notification_period` is `NULL`, TLV will treat the object as **in period** -to avoid problems here. - -This feature is disabled by default, see [config format](21-Config-Format.md#options) and -[behavior](02-Behavior.md#enabling-notification-period). - -### Period ranges in Icinga 2 - -The publishing of the individual ranges is buggy in Icinga 2 and can not be used here! - -See [Icinga2 #4659](https://github.com/Icinga/icinga2/issues/4659). - -### Period ranges in general - -Not all types of ranges are published to IDO. - -**Only the very basic range type is supported:** Weekday start and end time diff --git a/library/Toplevelview/Monitoring/HostgroupQuery.php b/library/Toplevelview/Monitoring/HostgroupQuery.php deleted file mode 100644 index 62cc014..0000000 --- a/library/Toplevelview/Monitoring/HostgroupQuery.php +++ /dev/null @@ -1,132 +0,0 @@ - */ - -namespace Icinga\Module\Toplevelview\Monitoring; - -use Icinga\Module\Monitoring\Backend\Ido\Query\HostgroupQuery as IcingaHostgroupQuery; - -/** - * Patched version of HostgroupQuery - */ -class HostgroupQuery extends IcingaHostgroupQuery -{ - use IgnoredNotificationPeriods; - use Options; - - public function __construct($ds, $columns = null, $options = null) - { - $this->setOptions($options); - parent::__construct($ds, $columns); - } - - public function init() - { - if (($periods = $this->getOption('ignored_notification_periods')) !== null) { - $this->ignoreNotificationPeriods($periods); - } - - $patchedColumnMap = array( - 'servicestatus' => array( - 'service_notifications_enabled' => 'ss.notifications_enabled', - 'service_is_flapping' => 'ss.is_flapping', - 'service_state' => ' - CASE WHEN ss.has_been_checked = 0 OR ss.has_been_checked IS NULL - THEN 99 - ELSE CASE WHEN ss.state_type = 1 - THEN ss.current_state - ELSE ss.last_hard_state - END - END', - 'service_handled' => ' - CASE WHEN (ss.problem_has_been_acknowledged + COALESCE(hs.current_state, 0)) > 0 - THEN 1 - ELSE 0 - END', - 'service_handled_wo_host' => ' - CASE WHEN ss.problem_has_been_acknowledged > 0 - THEN 1 - ELSE 0 - END', - 'service_in_downtime' => ' - CASE WHEN (ss.scheduled_downtime_depth = 0) - THEN 0 - ELSE 1 - END', - ), - 'hoststatus' => array( - 'host_notifications_enabled' => 'hs.notifications_enabled', - 'host_is_flapping' => 'hs.is_flapping', - 'host_state' => ' - CASE WHEN hs.has_been_checked = 0 OR hs.has_been_checked IS NULL - THEN 99 - ELSE CASE WHEN hs.state_type = 1 - THEN hs.current_state - ELSE hs.last_hard_state - END - END', - 'host_handled' => ' - CASE WHEN hs.problem_has_been_acknowledged > 0 - THEN 1 - ELSE 0 - END', - 'host_in_downtime' => ' - CASE WHEN (hs.scheduled_downtime_depth = 0) - THEN 0 - ELSE 1 - END', - ), - 'servicenotificationperiod' => array( - 'service_notification_period' => 'ntpo.name1', - 'service_in_notification_period' => ' - CASE WHEN ntpo.object_id IS NULL - THEN 1 - ELSE CASE WHEN ntpr.timeperiod_id IS NOT NULL - THEN 1 - ELSE 0 - END - END', - ), - ); - - foreach ($patchedColumnMap as $table => $columns) { - foreach ($columns as $k => $v) { - $this->columnMap[$table][$k] = $v; - } - } - - parent::init(); - } - - protected function joinServicenotificationperiod() - { - $extraJoinCond = ''; - - if ($this->hasIgnoredNotifications()) { - $extraJoinCond .= $this->db->quoteInto( - ' AND ntpo.name1 NOT IN (?)', - $this->getIgnoredNotificationPeriods() - ); - } - - $this->select->joinLeft( - ['ntp' => $this->prefix . 'timeperiods'], - 'ntp.timeperiod_object_id = s.notification_timeperiod_object_id' - . ' AND ntp.config_type = 1 AND ntp.instance_id = s.instance_id', - [] - ); - $this->select->joinLeft( - ['ntpo' => $this->prefix . 'objects'], - 'ntpo.object_id = s.notification_timeperiod_object_id' . $extraJoinCond, - [] - ); - $this->select->joinLeft( - ['ntpr' => $this->prefix . 'timeperiod_timeranges'], - "ntpr.timeperiod_id = ntp.timeperiod_id - AND ntpr.day = DAYOFWEEK(CURRENT_DATE()) - 1 - AND ntpr.start_sec <= UNIX_TIMESTAMP() - UNIX_TIMESTAMP(CURRENT_DATE()) - AND ntpr.end_sec >= UNIX_TIMESTAMP() - UNIX_TIMESTAMP(CURRENT_DATE()) - ", - [] - ); - } -} diff --git a/library/Toplevelview/Monitoring/Hostgroupsummary.php b/library/Toplevelview/Monitoring/Hostgroupsummary.php deleted file mode 100644 index c89f09c..0000000 --- a/library/Toplevelview/Monitoring/Hostgroupsummary.php +++ /dev/null @@ -1,33 +0,0 @@ - */ - -namespace Icinga\Module\Toplevelview\Monitoring; - -use Icinga\Data\ConnectionInterface; -use Icinga\Module\Monitoring\Backend\MonitoringBackend; -use Icinga\Module\Monitoring\DataView\Hostgroupsummary as IcingaHostgroupsummary; - -/** - * Patched version of Hostgroupsummary - * - * Just to load a patched version of HostgroupsummaryQuery - */ -class Hostgroupsummary extends IcingaHostgroupsummary -{ - /** @noinspection PhpMissingParentConstructorInspection */ - /** - * @param ConnectionInterface $connection - * @param array|null $columns - * @param array|null $options - * @noinspection PhpMissingParentConstructorInspection - */ - public function __construct( - ConnectionInterface $connection, - array $columns = null, - $options = null - ) { - /** @var MonitoringBackend $connection */ - $this->connection = $connection; - $this->query = new HostgroupsummaryQuery($connection->getResource(), $columns, $options); - } -} diff --git a/library/Toplevelview/Monitoring/HostgroupsummaryQuery.php b/library/Toplevelview/Monitoring/HostgroupsummaryQuery.php deleted file mode 100644 index 5390613..0000000 --- a/library/Toplevelview/Monitoring/HostgroupsummaryQuery.php +++ /dev/null @@ -1,157 +0,0 @@ - */ - -namespace Icinga\Module\Toplevelview\Monitoring; - -use Icinga\Data\Filter\Filter; -use Icinga\Module\Monitoring\Backend\Ido\Query\HostgroupsummaryQuery as IcingaHostgroupsummaryQuery; -use Zend_Db_Expr; -use Zend_Db_Select; - -/** - * Patched version of HostgroupsummaryQuery - */ -class HostgroupsummaryQuery extends IcingaHostgroupsummaryQuery -{ - use Options; - - public function __construct($ds, $columns = null, $options = null) - { - $this->setOptions($options); - parent::__construct($ds, $columns); - } - - public function init() - { - if ($this->getOption('notification_periods') === true) { - $serviceOutDowntime =# - 'service_notifications_enabled = 1 AND service_in_downtime = 0 AND service_in_notification_period = 1'; - $serviceInDowntime = - '(service_notifications_enabled = 0 OR service_in_downtime = 1 OR service_in_notification_period = 0)'; - } else { - $serviceOutDowntime = 'service_notifications_enabled = 1 AND service_in_downtime = 0'; - $serviceInDowntime = '(service_notifications_enabled = 0 OR service_in_downtime = 1)'; - } - - $hostOutDowntime = 'host_notifications_enabled = 1 AND host_in_downtime = 0'; - $hostInDowntime = '(host_notifications_enabled = 0 OR host_in_downtime = 1)'; - - if ($this->getOption('host_never_unhandled') === true) { - $patchServicesHandled = "(service_handled_wo_host = 1 OR service_is_flapping = 1) AND $serviceOutDowntime"; - $patchServicesUnhandled = "service_handled_wo_host = 0 AND service_is_flapping = 0 AND $serviceOutDowntime"; - } else { - $patchServicesHandled = "(service_handled = 1 OR service_is_flapping = 1) AND $serviceOutDowntime"; - $patchServicesUnhandled = "service_handled = 0 AND service_is_flapping = 0 AND $serviceOutDowntime"; - } - - $patchHostsHandled = "(host_handled = 1 OR host_is_flapping = 1) AND $hostOutDowntime"; - $patchHostsUnhandled = "host_handled = 0 AND host_is_flapping = 0 AND $hostOutDowntime"; - - $patchedColumnMap = array( - 'hostgroupsummary' => array( - 'hosts_down_handled' => - "SUM(CASE WHEN host_state = 1 AND $patchHostsHandled THEN 1 ELSE 0 END)", - 'hosts_down_unhandled' => - "SUM(CASE WHEN host_state = 1 AND $patchHostsUnhandled THEN 1 ELSE 0 END)", - 'hosts_unreachable_handled' => - "SUM(CASE WHEN host_state = 2 AND $patchHostsHandled THEN 1 ELSE 0 END)", - 'hosts_unreachable_unhandled' => - "SUM(CASE WHEN host_state = 2 AND $patchHostsUnhandled THEN 1 ELSE 0 END)", - 'hosts_downtime_handled' => - "SUM(CASE WHEN host_state != 0 AND $hostInDowntime THEN 1 ELSE 0 END)", - 'hosts_downtime_active' => - "SUM(CASE WHEN $hostInDowntime THEN 1 ELSE 0 END)", - 'services_critical_handled' => - "SUM(CASE WHEN service_state = 2 AND $patchServicesHandled THEN 1 ELSE 0 END)", - 'services_critical_unhandled' => - "SUM(CASE WHEN service_state = 2 AND $patchServicesUnhandled THEN 1 ELSE 0 END)", - 'services_unknown_handled' => - "SUM(CASE WHEN service_state = 3 AND $patchServicesHandled THEN 1 ELSE 0 END)", - 'services_unknown_unhandled' => - "SUM(CASE WHEN service_state = 3 AND $patchServicesUnhandled THEN 1 ELSE 0 END)", - 'services_warning_handled' => - "SUM(CASE WHEN service_state = 1 AND $patchServicesHandled THEN 1 ELSE 0 END)", - 'services_warning_unhandled' => - "SUM(CASE WHEN service_state = 1 AND $patchServicesUnhandled THEN 1 ELSE 0 END)", - 'services_downtime_handled' => - "SUM(CASE WHEN service_state != 0 AND $serviceInDowntime THEN 1 ELSE 0 END)", - 'services_downtime_active' => - "SUM(CASE WHEN $serviceInDowntime THEN 1 ELSE 0 END)", - ) - ); - - foreach ($patchedColumnMap as $table => $columns) { - foreach ($columns as $k => $v) { - $this->columnMap[$table][$k] = $v; - } - } - parent::init(); - } - - protected function createSubQuery($queryName, $columns = array()) - { - if ($queryName === 'Hostgroup') { - // use locally patched query - return new HostgroupQuery($this->ds, $columns, $this->options); - } else { - return parent::createSubQuery($queryName, $columns); - } - } - - protected function joinBaseTables() - { - $this->countQuery = $this->createSubQuery( - 'Hostgroup', - array() - ); - $hostColumns = array( - 'hostgroup_alias', - 'hostgroup_name', - 'host_handled', - 'host_notifications_enabled', - 'host_state', - 'host_is_flapping', - 'host_in_downtime', - 'service_handled' => new Zend_Db_Expr('NULL'), - 'service_handled_wo_host' => new Zend_Db_Expr('NULL'), - 'service_state' => new Zend_Db_Expr('NULL'), - 'service_notifications_enabled' => new Zend_Db_Expr('NULL'), - 'service_is_flapping' => new Zend_Db_Expr('NULL'), - 'service_in_downtime' => new Zend_Db_Expr('NULL'), - ); - - $serviceColumns = array( - 'hostgroup_alias', - 'hostgroup_name', - 'host_handled' => new Zend_Db_Expr('NULL'), - 'host_state' => new Zend_Db_Expr('NULL'), - 'host_notifications_enabled' => new Zend_Db_Expr('NULL'), - 'host_is_flapping' => new Zend_Db_Expr('NULL'), - 'host_in_downtime' => new Zend_Db_Expr('NULL'), - 'service_handled', - 'service_handled_wo_host', - 'service_state', - 'service_notifications_enabled', - 'service_is_flapping', - 'service_in_downtime', - ); - - if ($this->getOption('notification_periods') === true) { - $hostColumns['service_in_notification_period'] = new Zend_Db_Expr('NULL'); - $serviceColumns['service_in_notification_period'] = 'service_in_notification_period'; - } - - $hosts = $this->createSubQuery('Hostgroup', $hostColumns); - // ignore empty hostgroups in this subquery - $hosts->setFilter(Filter::expression('ho.object_id', '>', 0)); - $this->subQueries[] = $hosts; - $services = $this->createSubQuery('Hostgroup', $serviceColumns); - // ignore empty hostgroups in this subquery - $services->setFilter(Filter::expression('ho.object_id', '>', 0)); - $this->subQueries[] = $services; - $this->summaryQuery = $this->db->select()->union(array($hosts, $services), Zend_Db_Select::SQL_UNION_ALL); - $this->select->from(array('hostgroupsummary' => $this->summaryQuery), array()); - $this->group(array('hostgroup_name', 'hostgroup_alias')); - $this->joinedVirtualTables['hostgroupsummary'] = true; - } -} diff --git a/library/Toplevelview/Monitoring/IgnoredNotificationPeriods.php b/library/Toplevelview/Monitoring/IgnoredNotificationPeriods.php deleted file mode 100644 index 4b9e94c..0000000 --- a/library/Toplevelview/Monitoring/IgnoredNotificationPeriods.php +++ /dev/null @@ -1,49 +0,0 @@ - */ - -namespace Icinga\Module\Toplevelview\Monitoring; - -trait IgnoredNotificationPeriods -{ - protected $ignoredNotificationPeriods = []; - - public function ignoreNotificationPeriod($name) - { - $this->ignoredNotificationPeriods[$name] = true; - return $this; - } - - /** - * @param string|array|iterable $list - * - * @return $this - */ - public function ignoreNotificationPeriods($list) - { - if (is_string($list)) { - /** @var string $list */ - $this->ignoredNotificationPeriods[$list] = true; - } else { - foreach ($list as $i) { - $this->ignoredNotificationPeriods[$i] = true; - } - } - - return $this; - } - - public function getIgnoredNotificationPeriods() - { - return array_keys($this->ignoredNotificationPeriods); - } - - public function resetIgnoredNotificationPeriods() - { - $this->ignoredNotificationPeriods = []; - } - - public function hasIgnoredNotifications() - { - return ! empty($this->ignoredNotificationPeriods); - } -} diff --git a/library/Toplevelview/Monitoring/Options.php b/library/Toplevelview/Monitoring/Options.php deleted file mode 100644 index 75f739f..0000000 --- a/library/Toplevelview/Monitoring/Options.php +++ /dev/null @@ -1,33 +0,0 @@ - */ - -namespace Icinga\Module\Toplevelview\Monitoring; - -trait Options -{ - protected $options = []; - - public function getOption($key) - { - if (array_key_exists($key, $this->options)) { - return $this->options[$key]; - } - - return null; - } - - public function setOptions($options, $flush = false) - { - if ($flush) { - $this->options = []; - } - - if (! empty($options)) { - foreach ($options as $k => $v) { - $this->options[$k] = $v; - } - } - - return $this; - } -} diff --git a/library/Toplevelview/Monitoring/Servicestatus.php b/library/Toplevelview/Monitoring/Servicestatus.php deleted file mode 100644 index e43572f..0000000 --- a/library/Toplevelview/Monitoring/Servicestatus.php +++ /dev/null @@ -1,38 +0,0 @@ - */ - -namespace Icinga\Module\Toplevelview\Monitoring; - -use Icinga\Data\ConnectionInterface; -use Icinga\Module\Monitoring\Backend\MonitoringBackend; -use Icinga\Module\Monitoring\DataView\Servicestatus as IcingaServiceStatus; - -class Servicestatus extends IcingaServiceStatus -{ - /** @noinspection PhpMissingParentConstructorInspection */ - /** - * @param ConnectionInterface $connection - * @param array|null $columns - * @noinspection PhpMissingParentConstructorInspection - */ - public function __construct(ConnectionInterface $connection, array $columns = null, $options = null) - { - /** @var MonitoringBackend $connection */ - $this->connection = $connection; - $this->query = new ServicestatusQuery($connection->getResource(), $columns, $options); - } - - /** - * {@inheritdoc} - */ - public function getColumns() - { - return array_merge( - parent::getColumns(), - array( - //'service_in_notification_period', - 'service_notification_period', - ) - ); - } -} diff --git a/library/Toplevelview/Monitoring/ServicestatusQuery.php b/library/Toplevelview/Monitoring/ServicestatusQuery.php deleted file mode 100644 index addee2f..0000000 --- a/library/Toplevelview/Monitoring/ServicestatusQuery.php +++ /dev/null @@ -1,87 +0,0 @@ - */ - -namespace Icinga\Module\Toplevelview\Monitoring; - -use Icinga\Module\Monitoring\Backend\Ido\Query\ServicestatusQuery as IcingaServicestatusQuery; - -/** - * Patched version of ServicestatusQuery - */ -class ServicestatusQuery extends IcingaServicestatusQuery -{ - use IgnoredNotificationPeriods; - use Options; - - public function __construct($ds, $columns = null, $options = null) - { - $this->setOptions($options); - parent::__construct($ds, $columns); - } - - public function init() - { - if (($periods = $this->getOption('ignored_notification_periods')) !== null) { - $this->ignoreNotificationPeriods($periods); - } - - $patchedColumnMap = array( - 'servicestatus' => array( - 'service_handled_wo_host' => 'CASE WHEN ss.problem_has_been_acknowledged > 0 THEN 1 ELSE 0 END', - ), - 'servicenotificationperiod' => array( - 'service_notification_period' => 'ntpo.name1', - 'service_in_notification_period' => ' - CASE WHEN ntpo.name1 IS NULL - THEN 1 - ELSE CASE WHEN ntpr.timeperiod_id IS NOT NULL - THEN 1 - ELSE 0 - END - END', - ), - ); - - foreach ($patchedColumnMap as $table => $columns) { - foreach ($columns as $k => $v) { - $this->columnMap[$table][$k] = $v; - } - } - - parent::init(); - } - - protected function joinServicenotificationperiod() - { - $extraJoinCond = ''; - - if ($this->hasIgnoredNotifications()) { - $extraJoinCond .= $this->db->quoteInto( - ' AND ntpo.name1 NOT IN (?)', - $this->getIgnoredNotificationPeriods() - ); - } - - $this->select->joinLeft( - ["ntp" => $this->prefix . 'timeperiods'], - 'ntp.timeperiod_object_id = s.notification_timeperiod_object_id' - . ' AND ntp.config_type = 1 AND ntp.instance_id = s.instance_id', - [] - ); - $this->select->joinLeft( - ['ntpo' => $this->prefix . 'objects'], - 'ntpo.object_id = s.notification_timeperiod_object_id' - . $extraJoinCond, - [] - ); - $this->select->joinLeft( - ['ntpr' => $this->prefix . 'timeperiod_timeranges'], - 'ntpr.timeperiod_id = ntp.timeperiod_id - AND ntpr.day = DAYOFWEEK(CURRENT_DATE()) - 1 - AND ntpr.start_sec < UNIX_TIMESTAMP() - UNIX_TIMESTAMP(CURRENT_DATE()) - AND ntpr.end_sec > UNIX_TIMESTAMP() - UNIX_TIMESTAMP(CURRENT_DATE()) - ', - [] - ); - } -} diff --git a/library/Toplevelview/Tree/TLVHostGroupNode.php b/library/Toplevelview/Tree/TLVHostGroupNode.php index a6dc7e4..b55424f 100644 --- a/library/Toplevelview/Tree/TLVHostGroupNode.php +++ b/library/Toplevelview/Tree/TLVHostGroupNode.php @@ -5,8 +5,13 @@ use Icinga\Application\Benchmark; use Icinga\Exception\NotFoundError; -use Icinga\Module\Toplevelview\Monitoring\Hostgroupsummary; +use Icinga\Module\Icingadb\Model\Hostgroupsummary; +use ipl\Stdlib\Filter; +use stdClass; +/** + * TLVHostGroupNode represents a Hostgroup in the tree + */ class TLVHostGroupNode extends TLVIcingaNode { protected $type = 'hostgroup'; @@ -23,112 +28,100 @@ public static function fetch(TLVTree $root) throw new NotFoundError('No hostgroups registered to fetch!'); } - $names = array_keys($root->registeredObjects['hostgroup']); - - $options = []; - foreach (['notification_periods', 'host_never_unhandled', 'ignored_notification_periods'] as $opt) { - $options[$opt] = $root->get($opt); + $hgFilter = Filter::any(); + foreach (array_keys($root->registeredObjects['hostgroup']) as $name) { + $hgFilter->add(Filter::equal('hostgroup_name', $name)); } - // Note: this uses a patched version of Hostsgroupsummary / HostgroupsummaryQuery ! - $hostgroups = new Hostgroupsummary( - $root->getBackend(), - array( - 'hostgroup_name', - 'hosts_down_handled', - 'hosts_down_unhandled', - 'hosts_total', - 'hosts_unreachable_handled', - 'hosts_unreachable_unhandled', - 'hosts_downtime_handled', - 'hosts_downtime_active', - 'hosts_up', - 'services_critical_handled', - 'services_critical_unhandled', - 'services_ok', - 'services_total', - 'services_unknown_handled', - 'services_unknown_unhandled', - 'services_warning_handled', - 'services_warning_unhandled', - 'services_downtime_handled', - 'services_downtime_active', - ), - $options - ); + $hostgroups = Hostgroupsummary::on($root->getDb()); - $hostgroups->where('hostgroup_name', $names); + $hostgroups->filter($hgFilter); foreach ($hostgroups as $hostgroup) { - $root->registeredObjects['hostgroup'][$hostgroup->hostgroup_name] = $hostgroup; + // TODO We cannot store the ORM Models with json_encore + // Thus I'm converting things to objects that can be stored + // Maybe there's a better way? iterator_to_array does not work. + $hg = new stdClass; + $hg->hosts_total = $hostgroup->hosts_total; + $hg->hosts_up = $hostgroup->hosts_up; + $hg->hosts_total = $hostgroup->hosts_total; + $hg->services_total = $hostgroup->services_total; + $hg->hosts_up = $hostgroup->hosts_up; + $hg->services_ok = $hostgroup->services_ok; + $hg->hosts_down_handled = $hostgroup->hosts_down_handled; + $hg->hosts_down_unhandled = $hostgroup->hosts_down_unhandled; + $hg->services_warning_handled = $hostgroup->services_warning_handled; + $hg->services_warning_unhandled = $hostgroup->services_warning_unhandled; + $hg->services_critical_handled = $hostgroup->services_critical_handled; + $hg->services_critical_unhandled = $hostgroup->services_critical_unhandled; + $hg->services_unknown_handled = $hostgroup->services_unknown_handled; + $hg->services_unknown_unhandled = $hostgroup->services_unknown_unhandled; + + $root->registeredObjects['hostgroup'][$hostgroup->name] = $hg; } Benchmark::measure('Finished fetching hostgroups'); } - public function getStatus() + /** + * getStatus returns the current status for the Hostgroup + * + * @return TLVStatus + */ + public function getStatus(): TLVStatus { - if ($this->status === null) { - $this->status = $status = new TLVStatus(); - $key = $this->getKey(); - - if (($data = $this->root->getFetched($this->type, $key)) !== null) { - $status->set('total', $data->hosts_total + $data->services_total); - $status->set('ok', $data->hosts_up + $data->services_ok); - - $status->set('critical_handled', $data->services_critical_handled); - $status->set('critical_unhandled', $data->services_critical_unhandled); - - if ($this->getRoot()->get('host_never_unhandled') === true) { - $status->add( - 'critical_handled', - $data->hosts_down_handled - + $data->hosts_unreachable_handled - + $data->hosts_down_unhandled - + $data->hosts_unreachable_unhandled - ); - } else { - $status->add( - 'critical_handled', - $data->hosts_down_handled - + $data->hosts_unreachable_handled - ); - $status->add( - 'critical_unhandled', - $data->hosts_down_unhandled - + $data->hosts_unreachable_unhandled - ); - } - - $status->set('warning_handled', $data->services_warning_handled); - $status->set('warning_unhandled', $data->services_warning_unhandled); - $status->set('unknown_handled', $data->services_unknown_handled); - $status->set('unknown_unhandled', $data->services_unknown_unhandled); - - $status->set( - 'downtime_handled', - $data->hosts_downtime_handled - + $data->services_downtime_handled - ); - $status->set( - 'downtime_active', - $data->hosts_downtime_active - + $data->services_downtime_active - ); - - // extra metadata for view - $status->setMeta('hosts_total', $data->hosts_total); - $status->setMeta( - 'hosts_unhandled', - $data->hosts_down_unhandled - + $data->hosts_unreachable_unhandled - ); - - $status->set('missing', 0); - } else { - $status->add('missing', 1); - } + if ($this->status !== null) { + return $this->status; } + + $this->status = $status = new TLVStatus(); + $key = $this->getKey(); + + $hostgroup = $this->root->getFetched($this->type, $key); + + if ($hostgroup === null) { + $this->status->add('missing', 1); + return $this->status; + } + + $status->set('total', $hostgroup->hosts_total + $hostgroup->services_total); + $status->set('ok', $hostgroup->hosts_up + $hostgroup->services_ok); + + $status->set('critical_handled', $hostgroup->services_critical_handled); + $status->set('critical_unhandled', $hostgroup->services_critical_unhandled); + + // Override the host status to handled if the option is set + if ($this->getRoot()->get('override_host_problem_to_handled') === true) { + $status->add( + 'critical_handled', + $hostgroup->hosts_down_handled + + $hostgroup->hosts_down_unhandled + ); + } else { + $status->add( + 'critical_handled', + $hostgroup->hosts_down_handled + ); + $status->add( + 'critical_unhandled', + $hostgroup->hosts_down_unhandled + ); + } + + $status->set('warning_handled', $hostgroup->services_warning_handled); + $status->set('warning_unhandled', $hostgroup->services_warning_unhandled); + $status->set('unknown_handled', $hostgroup->services_unknown_handled); + $status->set('unknown_unhandled', $hostgroup->services_unknown_unhandled); + + // extra metadata for view + $status->setMeta('hosts_total', $hostgroup->hosts_total); + $status->setMeta( + 'hosts_unhandled', + $hostgroup->hosts_down_unhandled + ); + + $status->set('missing', 0); + return $this->status; } } diff --git a/library/Toplevelview/Tree/TLVHostNode.php b/library/Toplevelview/Tree/TLVHostNode.php index f03ae47..8c33e23 100644 --- a/library/Toplevelview/Tree/TLVHostNode.php +++ b/library/Toplevelview/Tree/TLVHostNode.php @@ -5,7 +5,13 @@ use Icinga\Application\Benchmark; use Icinga\Exception\NotFoundError; +use Icinga\Module\Icingadb\Model\Host; +use ipl\Stdlib\Filter; +use stdClass; +/** + * TLVHostNode represents a Host in the tree + */ class TLVHostNode extends TLVIcingaNode { protected $type = 'host'; @@ -22,60 +28,85 @@ public static function fetch(TLVTree $root) throw new NotFoundError('No hosts registered to fetch!'); } - $names = array_keys($root->registeredObjects['host']); + $hostnameFilter = Filter::any(); + foreach (array_keys($root->registeredObjects['host']) as $name) { + $hostnameFilter->add(Filter::equal('name', $name)); + } + + $hosts = Host::on($root->getDb())->with([ + 'state' + ]); - $hosts = $root->getBackend()->select() - ->from('hoststatus', array( - 'host_name', - 'host_hard_state', - 'host_handled', - 'host_in_downtime', - 'host_notifications_enabled', - )) - ->where('host_name', $names); + $hosts->filter($hostnameFilter); foreach ($hosts as $host) { - $root->registeredObjects['host'][$host->host_name] = $host; + // TODO We cannot store the ORM Models with json_encore + // Thus I'm converting things to objects that can be stored + // Maybe there's a better way? iterator_to_array does not work. + $h = new stdClass; + $h->state = new stdClass; + $h->notifications_enabled = $host->notifications_enabled; + $h->state->hard_state = $host->state->hard_state; + $h->state->is_flapping = $host->state->is_flapping; + $h->state->is_handled = $host->state->is_handled; + $h->state->in_downtime = $host->state->in_downtime; + + $root->registeredObjects['host'][$host->name] = $h; } Benchmark::measure('Finished fetching hosts'); } - public function getStatus() + /** + * getStatus returns the current status for the Host + * + * @return TLVStatus + */ + public function getStatus(): TLVStatus { - if ($this->status === null) { - $this->status = $status = new TLVStatus(); - $key = $this->getKey(); - - if (($data = $this->root->getFetched($this->type, $key)) !== null) { - $status->zero(); - $status->add('total'); - - $state = $data->host_hard_state; - - if ($data->host_in_downtime > 0 || $data->host_notifications_enabled === '0') { - $status->add('downtime_active'); - $state = '10'; - $handled = ''; - } elseif ($data->host_handled === '1' || $this->getRoot()->get('host_never_unhandled') === true) { - $handled = '_handled'; - } else { - $handled = '_unhandled'; - } - - if ($state === '0') { - $status->add('ok'); - } elseif ($state === '1' || $state === '2') { - $status->add('critical' . $handled); - } elseif ($state === '10') { - $status->add('downtime_handled'); - } else { - $status->add('unknown_handled'); - } - } else { - $status->add('missing', 1); - } + if ($this->status !== null) { + return $this->status; + } + + $this->status = $status = new TLVStatus(); + $key = $this->getKey(); + + $host = $this->root->getFetched($this->type, $key); + + if ($host === null) { + $this->status->add('missing', 1); + return $this->status; } + + $status->zero(); + $status->add('total'); + + // We only care about the hard state in TLV + $state = $host->state->hard_state; + + if ($host->state->in_downtime || $host->notifications_enabled === false) { + // Set downtime if notifications are disabled for the host + $status->add('downtime_active'); + $state = 10; + $handled = ''; + } elseif ($host->state->is_handled || + $this->getRoot()->get('override_host_problem_to_handled')) { + // Set the state to handled if it actually is handled, and the option is set to override the state + $handled = '_handled'; + } else { + $handled = '_unhandled'; + } + + if ($state === 0) { + $status->add('ok'); + } elseif ($state === 1 || $state === 2) { + $status->add('critical' . $handled); + } elseif ($state === 10) { + $status->add('downtime_handled'); + } else { + $status->add('unknown_handled'); + } + return $this->status; } } diff --git a/library/Toplevelview/Tree/TLVServiceNode.php b/library/Toplevelview/Tree/TLVServiceNode.php index 2036714..4b5cb07 100644 --- a/library/Toplevelview/Tree/TLVServiceNode.php +++ b/library/Toplevelview/Tree/TLVServiceNode.php @@ -5,8 +5,13 @@ use Icinga\Application\Benchmark; use Icinga\Exception\NotFoundError; -use Icinga\Module\Toplevelview\Monitoring\Servicestatus; +use Icinga\Module\Icingadb\Model\Service; +use ipl\Stdlib\Filter; +use stdClass; +/** + * TLVServiceNode represents a Service in the tree + */ class TLVServiceNode extends TLVIcingaNode { protected $type = 'service'; @@ -45,99 +50,100 @@ public static function fetch(TLVTree $root) throw new NotFoundError('No services registered to fetch!'); } - $names = array_keys($root->registeredObjects['host']); - - $columns = array( - 'host_name', - 'service_description', - 'service_hard_state', - 'service_handled', - 'service_handled_wo_host', - 'service_notifications_enabled', - 'service_notification_period', - 'service_is_flapping', - 'service_in_downtime', - ); + $hostnameFilter = Filter::any(); - $options = []; - foreach (['notification_periods', 'host_never_unhandled', 'ignored_notification_periods'] as $opt) { - $options[$opt] = $root->get($opt); + foreach (array_keys($root->registeredObjects['host']) as $name) { + $hostnameFilter->add(Filter::equal('host.name', $name)); } - if ($root->get('notification_periods') === true) { - $columns[] = 'service_in_notification_period'; - } + $services = Service::on($root->getDb())->with([ + 'host', + 'state' + ]); - // Note: this uses a patched version of Servicestatus / ServicestatusQuery ! - $services = new Servicestatus($root->getBackend(), $columns, $options); - $services->where('host_name', $names); + $services->filter($hostnameFilter); foreach ($services as $service) { - $key = sprintf('%s!%s', $service->host_name, $service->service_description); + // TODO We cannot store the ORM Models with json_encore + // Thus I'm converting things to objects that can be stored + // Maybe there's a better way? iterator_to_array does not work. + $s = new stdClass; + $s->state = new stdClass; + $s->notifications_enabled = $service->notifications_enabled; + $s->display_name = $service->display_name; + $s->state->hard_state = $service->state->hard_state; + $s->state->is_flapping = $service->state->is_flapping; + $s->state->is_handled = $service->state->is_handled; + $s->state->in_downtime = $service->state->in_downtime; + + $key = sprintf('%s!%s', $service->host->name, $service->display_name); if (array_key_exists($key, $root->registeredObjects['service'])) { - $root->registeredObjects['service'][$key] = $service; + $root->registeredObjects['service'][$key] = $s; } } Benchmark::measure('Finished fetching services'); } - public function getStatus() + /** + * getStatus returns the current status for the Service + * + * @return TLVStatus + */ + public function getStatus(): TLVStatus { - if ($this->status === null) { - $this->status = $status = new TLVStatus(); - $key = $this->getKey(); - - if (($data = $this->root->getFetched($this->type, $key)) !== null) { - $status->zero(); - $status->add('total'); - - $state = $data->service_hard_state; - - if ($this->getRoot()->get('notification_periods') === true) { - $notInPeriod = $data->service_in_notification_period === '0'; - } else { - $notInPeriod = false; - } - - if ($this->getRoot()->get('host_never_unhandled') === true) { - $isHandled = $data->service_handled_wo_host === '1'; - } else { - $isHandled = $data->service_handled === '1'; - } - $isHandled = $isHandled || $data->service_is_flapping === '1'; - - if ($data->service_in_downtime > 0 - || $data->service_notifications_enabled === '0' - || $notInPeriod - ) { - $status->add('downtime_active'); - if ($state !== '0') { - $state = '10'; - } - } - - if ($isHandled) { - $handled = '_handled'; - } else { - $handled = '_unhandled'; - } - - if ($state === '0' || $state === '99') { - $status->add('ok', 1); - } elseif ($state === '1') { - $status->add('warning' . $handled, 1); - } elseif ($state === '2') { - $status->add('critical' . $handled, 1); - } elseif ($state === '10') { - $status->add('downtime_handled'); - } else { - $status->add('unknown' . $handled, 1); - } - } else { - $status->add('missing', 1); + if ($this->status !== null) { + return $this->status; + } + + $this->status = $status = new TLVStatus(); + $key = $this->getKey(); + + $service = $this->root->getFetched($this->type, $key); + + if ($service === null) { + $status->add('missing', 1); + return $this->status; + } + + $status->zero(); + $status->add('total'); + + // We only care about the hard state in TLV + $state = $service->state->hard_state; + + // Get the service's current handled state + $isHandled = $service->state->is_handled; + + // In TLV flapping means the state is handled + $isHandled = $isHandled || $service->state->is_flapping; + + // Set downtime if notifications are disabled for the service + if ($service->state->in_downtime || $service->notifications_enabled === false) { + $status->add('downtime_active'); + if ($state !== 0) { + $state = 10; } } + + if ($isHandled) { + $handled = '_handled'; + } else { + $handled = '_unhandled'; + } + + if ($state === 0 || $state === 99) { + $status->add('ok', 1); + } elseif ($state === 1) { + $status->add('warning' . $handled, 1); + } elseif ($state === 2) { + $status->add('critical' . $handled, 1); + } elseif ($state === 10) { + $status->add('downtime_handled'); + } else { + $status->add('unknown' . $handled, 1); + } + return $this->status; } } diff --git a/library/Toplevelview/Tree/TLVStatus.php b/library/Toplevelview/Tree/TLVStatus.php index afecc1e..35c28bc 100644 --- a/library/Toplevelview/Tree/TLVStatus.php +++ b/library/Toplevelview/Tree/TLVStatus.php @@ -3,6 +3,9 @@ namespace Icinga\Module\Toplevelview\Tree; +/** + * TLVStatus represents the status for a TLVTreeNode that is shown in the view + */ class TLVStatus { protected $properties = array( @@ -22,7 +25,7 @@ class TLVStatus protected static $statusPriority = array( 'critical_unhandled', 'warning_unhandled', - 'unknown_unhandled', // Note: old TLV ignored UNKNOWN basically + 'unknown_unhandled', 'critical_handled', 'warning_handled', 'unknown_handled', diff --git a/library/Toplevelview/Tree/TLVTree.php b/library/Toplevelview/Tree/TLVTree.php index 817d52c..ecfe6e3 100644 --- a/library/Toplevelview/Tree/TLVTree.php +++ b/library/Toplevelview/Tree/TLVTree.php @@ -7,14 +7,22 @@ use Icinga\Exception\IcingaException; use Icinga\Exception\NotFoundError; use Icinga\Exception\ProgrammingError; -use Icinga\Module\Monitoring\Backend\MonitoringBackend; +use Icinga\Module\Icingadb\Common\Database; use Icinga\Module\Toplevelview\Util\Json; use Icinga\Module\Toplevelview\ViewConfig; use Icinga\Web\FileCache; use stdClass; +/** + * TLVTree represents the root of the TLV tree + */ class TLVTree extends TLVTreeNode { + /** + * @var Database + */ + use Database; + protected static $titleKey = 'name'; public $registeredTypes = array(); @@ -29,11 +37,6 @@ class TLVTree extends TLVTreeNode protected $cacheLifetime = 60; - /** - * @var MonitoringBackend - */ - protected $backend; - /** * @var ViewConfig */ @@ -209,17 +212,6 @@ public function getFetched($type, $key) } } - public function getBackend() - { - return $this->backend; - } - - public function setBackend(MonitoringBackend $backend) - { - $this->backend = $backend; - return $this; - } - /** * @return int time */ diff --git a/library/Toplevelview/Tree/TLVTreeNode.php b/library/Toplevelview/Tree/TLVTreeNode.php index fd70e49..af88811 100644 --- a/library/Toplevelview/Tree/TLVTreeNode.php +++ b/library/Toplevelview/Tree/TLVTreeNode.php @@ -9,6 +9,9 @@ use Icinga\Exception\NotImplementedError; use Icinga\Exception\ProgrammingError; +/** + * TLVTreeNode represents a node in the TLV tree + */ class TLVTreeNode extends TreeNode { /** diff --git a/library/Toplevelview/ViewConfig.php b/library/Toplevelview/ViewConfig.php index dc73b7f..3fdc7eb 100644 --- a/library/Toplevelview/ViewConfig.php +++ b/library/Toplevelview/ViewConfig.php @@ -422,7 +422,6 @@ protected function getSessionVarName() public static function session() { - // TODO: is this CLI safe? return Session::getSession(); } diff --git a/library/Toplevelview/Web/Controller.php b/library/Toplevelview/Web/Controller.php index c010cd7..970224f 100644 --- a/library/Toplevelview/Web/Controller.php +++ b/library/Toplevelview/Web/Controller.php @@ -9,6 +9,10 @@ use Icinga\Module\Monitoring\Backend\MonitoringBackend; use Icinga\Web\Controller as IcingaController; +/** + * Controller wraps around the Icinga\Web\Controller to + * check for the PHP YAML extension + */ class Controller extends IcingaController { /** @var MonitoringBackend */ @@ -23,25 +27,6 @@ public function init() } } - /** - * 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, $controller = null) { if ($controller !== null) { diff --git a/module.info b/module.info index bdffc60..341704c 100644 --- a/module.info +++ b/module.info @@ -1,6 +1,8 @@ Module: toplevelview -Version: 0.3.4 -Depends: monitoring (>=2.5.0) +Version: 0.4.0 +Requires: + Libraries: icinga-php-library (>=0.13.0) + Modules: icingadb (>=1.0.0) Description: Top Level View Top Level View is a hierarchy based status view for Icinga Web 2. From b574bb7b8e36433662d753aa897f4ab42007db28 Mon Sep 17 00:00:00 2001 From: Markus Opolka Date: Fri, 14 Jun 2024 16:05:50 +0200 Subject: [PATCH 4/9] Fix dropdown menu for fullscreen view --- application/controllers/ShowController.php | 6 +++--- .../locale/de_DE/LC_MESSAGES/toplevelview.mo | Bin 2771 -> 2757 bytes .../locale/de_DE/LC_MESSAGES/toplevelview.po | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/application/controllers/ShowController.php b/application/controllers/ShowController.php index 0497616..26b6abd 100644 --- a/application/controllers/ShowController.php +++ b/application/controllers/ShowController.php @@ -39,13 +39,13 @@ public function init() ); } + $fullscreen = new Tab(array( - 'title' => $this->translate('Go Fullscreen'), - 'icon' => 'dashboard', + 'title' => $this->translate('Fullscreen'), 'url' => ((string) $tiles) . '&view=compact&showFullscreen' )); $fullscreen->setTargetBlank(); - $tabs->addAsDropdown('fullscreen', $fullscreen); + $tabs->add('fullscreen', $fullscreen); $action = $this->getRequest()->getActionName(); if ($tab = $tabs->get($action)) { diff --git a/application/locale/de_DE/LC_MESSAGES/toplevelview.mo b/application/locale/de_DE/LC_MESSAGES/toplevelview.mo index fc7286f7a78d144b43e91ff6c3166be9a48002b7..8f78507e170046d7bd4849b6bd3c15934a06ae3c 100644 GIT binary patch delta 458 zcmXZYJxjx25Ww-vQ&ZEb*w#i6f>so9Noldxw?Q20;4F$DD$+%)C>;br2-Q5Gv-R0A5x;K|WQbcZJBE9&4J@|xM_=4S-?fS2K z=#fw209G)L8(761w6Te!_=aQn+sV^h&XG?cB{{GqkQ70kf;K+KM@!@ywO*oo0V}BY z$C$@6bg_Xtz$<3)9S5<6x>y@^G20PIU&Pc9jz0WB z-KdQ@9OhLWU=c5H8R;n>=;IfTqr)^4=;JJ|qW*!a6u}6=9qL5S;gH$3Bs?)QR)5$q k-_d<+-z3X^v6L&8=L^$OBEDdpwZmF%cdu5B)}2%H2h9F44FCWD delta 473 zcmXZYu`dI06u|L!cUq^2(q2U(xf5cc;+j%)m8iicA|gcFL20x>hbEGKqoP>N+iC&w8`Z@@4a90d++_8tq<$s-0K#RnjzAK*Vu`7xPgz@j{R-T zx{ZV6V;I2<25}wpn8N_R;1IsyFn+i4D2KD;(@06m0r4bEP@$k2pW>}1a*3**qWdIf zP@PwC0#DGw2UG*RVieyohMyS1KU9mErbq``*o!vyp=)~0P3Bt%tEevC!7?5rpZxGs zFZGLRBa_=497i?45}x5Q(o-53$1fbg7}Jbm8fP$z>IJUC1UA79s)?TJ1Nx@cQ?KeN zP3rf0!&oRee$Cj`L&9~oxDc27M(|TmBvhS>vip6Z9nBUqvDCD*e{YCRg{|7W8 BHh};D diff --git a/application/locale/de_DE/LC_MESSAGES/toplevelview.po b/application/locale/de_DE/LC_MESSAGES/toplevelview.po index 9dc3946..f26fb3d 100644 --- a/application/locale/de_DE/LC_MESSAGES/toplevelview.po +++ b/application/locale/de_DE/LC_MESSAGES/toplevelview.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: Toplevelview Module (0.0.0)\n" "Report-Msgid-Bugs-To: dev@icinga.com\n" "POT-Creation-Date: 2017-10-13 11:53+0000\n" -"PO-Revision-Date: 2024-04-24 12:51+0200\n" +"PO-Revision-Date: 2024-06-14 16:05+0200\n" "Last-Translator: Markus Opolka \n" "Language-Team: \n" "Language: de_DE\n" @@ -80,8 +80,8 @@ msgid "Find previous" msgstr "Vorheriger Treffer" #: ../../../../modules/toplevelview/application/controllers/ShowController.php:43 -msgid "Go Fullscreen" -msgstr "Zur Vollbildansicht" +msgid "Fullscreen" +msgstr "Vollbild" #: ../../../../modules/toplevelview/application/views/scripts/edit/index.phtml:33 msgid "Jump to line" From 375b17ae01c36a6d7f02e214cbd813bc67b386e3 Mon Sep 17 00:00:00 2001 From: Markus Opolka Date: Fri, 12 Jul 2024 09:50:50 +0200 Subject: [PATCH 5/9] Fix state badges in tiles not having a border --- README.md | 2 +- public/css/module.less | 50 ++++++++++++++---------------------------- 2 files changed, 18 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 36455cb..1e51058 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ please see the documentation on details. ## Requirements * Icinga Web 2 >= 2.5.0 -* Icinga DB Web >=1.0.0 +* Icinga DB Web >= 1.0.0 * php-yaml Also see [Introduction in docs](doc/01-Introduction.md). diff --git a/public/css/module.less b/public/css/module.less index f68d1ef..140df6d 100644 --- a/public/css/module.less +++ b/public/css/module.less @@ -2,19 +2,13 @@ .tlv-overview-tiles .tlv-overview-tile { display: block; - float: left; - padding: 1em; margin: 1em; - position: relative; - width: 20em; height: 10em; - border: 1px solid @gray; - text-decoration: none; .tlv-title { @@ -35,16 +29,13 @@ .unsaved { display: inline-block; padding: 0.2em; - font-weight: bold; - background: @tlv-color-warning-bg; color: @tlv-color-warning-fg; } } // see Icinga Web 2: public/css/icinga/base.less -//@import '../../vendor/icingaweb2/public/css/icinga/base.less'; @tlv-color-critical-bg: @color-critical; @tlv-color-critical-fg: white; @tlv-color-critical-handled-bg: #FFCCBC; // Material Design Deep Orange 100 @@ -69,6 +60,13 @@ @tlv-color-missing-fg: #333; .tlv-status-tile { + .badges { + .badge { + border: 0.1em solid @white; + margin-left: 0.2em; + } + } + &.critical, &.down { background-color: @tlv-color-critical-bg; color: @tlv-color-critical-fg; @@ -153,7 +151,6 @@ position: relative; width: 100%; height: 100%; - padding-right: 0 !important; padding-left: 0 !important; @@ -163,9 +160,9 @@ } } +/** BEGIN of header **/ .tlv-header { position: relative; - line-height: 2em; height: 2em; @@ -190,7 +187,6 @@ display: inline-block; position: absolute; right: 0; - font-size: 1.2em; .badge { @@ -198,17 +194,16 @@ } } } +/** END of header **/ +/** BEGIN of tiles **/ .tlv-view-tiles { position: relative; - clear: both; display: flex; flex-wrap: wrap; align-content: stretch; - height: 100%; - margin-top: -2em; padding-top: 2em; @@ -217,10 +212,8 @@ flex-wrap: wrap; align-content: stretch; flex-grow: 1; - width: 100%; height: 100%; - margin-top: -2.3em; // - padding title - margin tile padding-top: 2.3em; // + padding title + margin tile } @@ -228,19 +221,16 @@ .tlv-tile { margin: 0.1em; flex-grow: 1; - overflow: hidden; .badges { display: inline-block; - font-weight: normal; margin-left: 0.2em; .badge { font-size: 0.7em; - - border: 0.1em solid white; + border: 0.1em solid @white; margin-left: 0.2em; } } @@ -250,16 +240,13 @@ display: inline-block; height: 1.8em; width: 100%; - padding: 0.1em; } > .tlv-tile { // first level min-width: 40%; - border-style: solid; border-width: 1px 1px 1px 4px; - padding: 0.1em; margin: 0.3em 0.5em; @@ -292,6 +279,7 @@ } } } +/** END of tiles **/ #layout.wide-layout .tlv-view-tiles .tlv-tile-title { font-size: 1.3em; @@ -301,12 +289,12 @@ font-size: 1.1em; } +/** BEGIN of tree **/ .tlv-view-tree { .tlv-tree-node { display: flex; flex-wrap: wrap; flex-grow: 1; - width: 100%; &.tlv-collapsed { @@ -315,9 +303,7 @@ background: none; border-style: solid; - border-width: 2px 0 0 5px; - padding: 0.2em 0 0 1em; &.tlv-collapsed { @@ -330,8 +316,8 @@ display: inline-block; font-size: 0.9em; - .badge { - border: 0.1em solid white; + .badge { + border: 0.1em solid @white; margin-left: 0.2em; } } @@ -359,16 +345,14 @@ .tlv-node-icinga { flex-grow: 1; - margin: 0.2em; padding: 0.2em 1em; white-space: nowrap; - border-radius: 0.5em; - font-size: 0.9em; } } +/** END of tree **/ .tlv-collapsible { .icon.tlv-collapse-handle::before { @@ -389,7 +373,7 @@ list-style: none; overflow: hidden; padding: 0; - + // breadcrumb badges .badges { display: inline-block; padding: 0 0 0 0.5em; From 36c1107d5d1db3ec17d3595ed9ee699d0d19b7d8 Mon Sep 17 00:00:00 2001 From: Markus Opolka Date: Fri, 12 Jul 2024 11:24:36 +0200 Subject: [PATCH 6/9] Move control links to controls div --- application/controllers/IndexController.php | 16 +++---- application/controllers/ShowController.php | 44 +++++++------------- application/views/scripts/edit/index.phtml | 2 +- application/views/scripts/index/index.phtml | 11 +++-- application/views/scripts/show/actions.phtml | 32 +++++++++++--- application/views/scripts/show/index.phtml | 4 +- application/views/scripts/show/tree.phtml | 4 +- application/views/scripts/text/index.phtml | 2 +- library/Toplevelview/Web/Controller.php | 12 ++---- 9 files changed, 64 insertions(+), 63 deletions(-) diff --git a/application/controllers/IndexController.php b/application/controllers/IndexController.php index e1353f3..eccae95 100644 --- a/application/controllers/IndexController.php +++ b/application/controllers/IndexController.php @@ -6,17 +6,19 @@ use Icinga\Module\Toplevelview\ViewConfig; use Icinga\Module\Toplevelview\Web\Controller; +/** + * IndexController loads all existing Views from their YAML files. + */ class IndexController extends Controller { public function indexAction() { - $this->getTabs()->add( - 'index', - array( - 'title' => 'Top Level View', - 'url' => 'toplevelview', - ) - )->activate('index'); + $this->getTabs()->add('index', [ + 'title' => 'Top Level View', + 'url' => 'toplevelview', + ])->activate('index'); + + // Load add views from the existing YAML files $this->view->views = ViewConfig::loadAll(); $this->setAutorefreshInterval(30); diff --git a/application/controllers/ShowController.php b/application/controllers/ShowController.php index 26b6abd..480b2f9 100644 --- a/application/controllers/ShowController.php +++ b/application/controllers/ShowController.php @@ -6,7 +6,6 @@ use Icinga\Module\Toplevelview\ViewConfig; use Icinga\Module\Toplevelview\Web\Controller; use Icinga\Web\Url; -use Icinga\Web\Widget\Tab; class ShowController extends Controller { @@ -14,39 +13,26 @@ public function init() { $tabs = $this->getTabs(); - $tiles = Url::fromPath('toplevelview/show', array( - 'name' => $this->params->getRequired('name') - )); + $url = Url::fromRequest()->setParams(clone $this->params); - $tabs->add( - 'index', - array( - 'title' => $this->translate('Tiles'), - 'url' => $tiles - ) - ); + $tiles = Url::fromPath('toplevelview/show', ['name' => $this->params->getRequired('name')]); + + $tabs->add('index', [ + 'title' => $this->translate('Tree'), + 'url' => $tiles + ]); + // Add new Tabs for the entire tree if (($id = $this->getParam('id')) !== null) { - $tabs->add( - 'tree', - array( - 'title' => $this->translate('Tree'), - 'url' => Url::fromPath('toplevelview/show/tree', array( - 'name' => $this->params->getRequired('name'), - 'id' => $id - )) - ) - ); + $tabs->add('tree', [ + 'title' => $this->translate('Tiles'), + 'url' => Url::fromPath('toplevelview/show/tree', [ + 'name' => $this->params->getRequired('name'), + 'id' => $id + ]) + ]); } - - $fullscreen = new Tab(array( - 'title' => $this->translate('Fullscreen'), - 'url' => ((string) $tiles) . '&view=compact&showFullscreen' - )); - $fullscreen->setTargetBlank(); - $tabs->add('fullscreen', $fullscreen); - $action = $this->getRequest()->getActionName(); if ($tab = $tabs->get($action)) { $tab->setActive(); diff --git a/application/views/scripts/edit/index.phtml b/application/views/scripts/edit/index.phtml index 614e962..f169c1c 100644 --- a/application/views/scripts/edit/index.phtml +++ b/application/views/scripts/edit/index.phtml @@ -1,5 +1,5 @@ compact): ?> -
+
tabs ?>
diff --git a/application/views/scripts/index/index.phtml b/application/views/scripts/index/index.phtml index 3797583..98e98e5 100644 --- a/application/views/scripts/index/index.phtml +++ b/application/views/scripts/index/index.phtml @@ -2,12 +2,8 @@ /** @var array $views */ if (! $this->compact): ?> -
+
tabs ?> -
- -
-
- +
+ +
+
$view): /** @var \Icinga\Module\Toplevelview\ViewConfig $view */ diff --git a/application/views/scripts/show/actions.phtml b/application/views/scripts/show/actions.phtml index c199437..a94a438 100644 --- a/application/views/scripts/show/actions.phtml +++ b/application/views/scripts/show/actions.phtml @@ -4,6 +4,32 @@