From 135f8b12bd91e82cdde1f30a95a42a098a60fc50 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Tue, 19 Nov 2024 21:59:37 +0100 Subject: [PATCH 1/8] (Host|Service)Controller: Let the init() render the object header --- application/controllers/HostController.php | 21 +++++-------------- application/controllers/ServiceController.php | 17 +++++---------- 2 files changed, 10 insertions(+), 28 deletions(-) diff --git a/application/controllers/HostController.php b/application/controllers/HostController.php index 259dd33f0..c85463390 100644 --- a/application/controllers/HostController.php +++ b/application/controllers/HostController.php @@ -57,6 +57,11 @@ public function init() $this->host = $host; $this->loadTabsForObject($host); + $this->addControl((new HostList([$host])) + ->setViewMode('objectHeader') + ->setDetailActionsDisabled() + ->setNoSubjectLink()); + $this->setTitleTab($this->getRequest()->getActionName()); $this->setTitle($host->display_name); } @@ -72,10 +77,6 @@ public function indexAction() $this->controls->addAttributes(['class' => 'overdue']); } - $this->addControl((new HostList([$this->host])) - ->setViewMode('objectHeader') - ->setDetailActionsDisabled() - ->setNoSubjectLink()); $this->addControl(new HostMetaInfo($this->host)); $this->addControl(new QuickActions($this->host)); @@ -97,10 +98,6 @@ public function sourceAction() $this->controls->addAttributes(['class' => 'overdue']); } - $this->addControl((new HostList([$this->host])) - ->setViewMode('objectHeader') - ->setDetailActionsDisabled() - ->setNoSubjectLink()); $this->addContent(new HostInspectionDetail( $this->host, reset($apiResult) @@ -158,10 +155,6 @@ public function historyAction() yield $this->export($history); - $this->addControl((new HostList([$this->host])) - ->setViewMode('objectHeader') - ->setDetailActionsDisabled() - ->setNoSubjectLink()); $this->addControl($sortControl); $this->addControl($limitControl); $this->addControl($viewModeSwitcher); @@ -221,10 +214,6 @@ public function servicesAction() $serviceList = (new ServiceList($services)) ->setViewMode($viewModeSwitcher->getViewMode()); - $this->addControl((new HostList([$this->host])) - ->setViewMode('objectHeader') - ->setDetailActionsDisabled() - ->setNoSubjectLink()); $this->addControl($paginationControl); $this->addControl($sortControl); $this->addControl($limitControl); diff --git a/application/controllers/ServiceController.php b/application/controllers/ServiceController.php index 25f56b729..13fca630a 100644 --- a/application/controllers/ServiceController.php +++ b/application/controllers/ServiceController.php @@ -69,6 +69,11 @@ public function init() $this->service = $service; $this->loadTabsForObject($service); + $this->addControl((new ServiceList([$service])) + ->setViewMode('objectHeader') + ->setDetailActionsDisabled() + ->setNoSubjectLink()); + $this->setTitleTab($this->getRequest()->getActionName()); $this->setTitle( t('%s on %s', ' on '), @@ -83,10 +88,6 @@ public function indexAction() $this->controls->addAttributes(['class' => 'overdue']); } - $this->addControl((new ServiceList([$this->service])) - ->setViewMode('objectHeader') - ->setDetailActionsDisabled() - ->setNoSubjectLink()); $this->addControl(new ServiceMetaInfo($this->service)); $this->addControl(new QuickActions($this->service)); @@ -108,10 +109,6 @@ public function sourceAction() $this->controls->addAttributes(['class' => 'overdue']); } - $this->addControl((new ServiceList([$this->service])) - ->setViewMode('objectHeader') - ->setDetailActionsDisabled() - ->setNoSubjectLink()); $this->addContent(new ServiceInspectionDetail( $this->service, reset($apiResult) @@ -170,10 +167,6 @@ public function historyAction() yield $this->export($history); - $this->addControl((new ServiceList([$this->service])) - ->setViewMode('objectHeader') - ->setDetailActionsDisabled() - ->setNoSubjectLink()); $this->addControl($sortControl); $this->addControl($limitControl); $this->addControl($viewModeSwitcher); From f00d507f948d0ca201a5a023976b85e52ca61cd9 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Thu, 21 Nov 2024 10:23:06 +0100 Subject: [PATCH 2/8] (Host|Service)Controller: Add parents and children tab --- application/controllers/HostController.php | 263 +++++++++++++++++- application/controllers/ServiceController.php | 235 ++++++++++++++++ 2 files changed, 496 insertions(+), 2 deletions(-) diff --git a/application/controllers/HostController.php b/application/controllers/HostController.php index c85463390..ec3bedfa1 100644 --- a/application/controllers/HostController.php +++ b/application/controllers/HostController.php @@ -12,20 +12,30 @@ use Icinga\Module\Icingadb\Common\HostLinks; use Icinga\Module\Icingadb\Common\Links; use Icinga\Module\Icingadb\Hook\TabHook\HookActions; +use Icinga\Module\Icingadb\Model\DependencyEdge; +use Icinga\Module\Icingadb\Model\DependencyNode; use Icinga\Module\Icingadb\Model\History; use Icinga\Module\Icingadb\Model\Host; use Icinga\Module\Icingadb\Model\Service; use Icinga\Module\Icingadb\Model\ServicestateSummary; use Icinga\Module\Icingadb\Redis\VolatileStateResults; +use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions; +use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher; use Icinga\Module\Icingadb\Web\Controller; use Icinga\Module\Icingadb\Widget\Detail\HostDetail; use Icinga\Module\Icingadb\Widget\Detail\HostInspectionDetail; use Icinga\Module\Icingadb\Widget\Detail\HostMetaInfo; use Icinga\Module\Icingadb\Widget\Detail\QuickActions; +use Icinga\Module\Icingadb\Widget\ItemList\DependencyNodeList; use Icinga\Module\Icingadb\Widget\ItemList\HostList; use Icinga\Module\Icingadb\Widget\ItemList\HistoryList; use Icinga\Module\Icingadb\Widget\ItemList\ServiceList; +use ipl\Orm\Query; +use ipl\Sql\Expression; +use ipl\Sql\Filter\Exists; use ipl\Stdlib\Filter; +use ipl\Web\Control\LimitControl; +use ipl\Web\Control\SortControl; use ipl\Web\Url; use ipl\Web\Widget\Tabs; @@ -224,8 +234,209 @@ public function servicesAction() $this->setAutorefreshInterval(10); } + public function parentsAction() + { + $nodesQuery = $this->fetchNodes(true); + + $limitControl = $this->createLimitControl(); + $paginationControl = $this->createPaginationControl($nodesQuery); + $sortControl = $this->createSortControl( + $nodesQuery, + [ + 'name' => $this->translate('Name'), + 'severity desc, last_state_change desc' => $this->translate('Severity'), + 'state' => $this->translate('Current State'), + 'last_state_change desc' => $this->translate('Last State Change') + ] + ); + $viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl); + + $searchBar = $this->createSearchBar( + $nodesQuery, + [ + $limitControl->getLimitParam(), + $sortControl->getSortParam(), + $viewModeSwitcher->getViewModeParam(), + 'name' + ] + ); + + if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) { + if ($searchBar->hasBeenSubmitted()) { + $filter = $this->getFilter(); + } else { + $this->addControl($searchBar); + $this->sendMultipartUpdate(); + + return; + } + } else { + $filter = $searchBar->getFilter(); + } + + $nodesQuery->filter($filter); + + $this->addControl($paginationControl); + $this->addControl($sortControl); + $this->addControl($limitControl); + $this->addControl($viewModeSwitcher); + $this->addControl($searchBar); + + $this->addContent( + (new DependencyNodeList($nodesQuery)) + ->setViewMode($viewModeSwitcher->getViewMode()) + ); + + if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) { + $this->sendMultipartUpdate(); + } + + $this->setAutorefreshInterval(10); + } + + public function childrenAction() + { + $nodesQuery = $this->fetchNodes(); + + $limitControl = $this->createLimitControl(); + $paginationControl = $this->createPaginationControl($nodesQuery); + $sortControl = $this->createSortControl( + $nodesQuery, + [ + 'name' => $this->translate('Name'), + 'severity desc, last_state_change desc' => $this->translate('Severity'), + 'state' => $this->translate('Current State'), + 'last_state_change desc' => $this->translate('Last State Change') + ] + ); + $viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl); + + $searchBar = $this->createSearchBar( + $nodesQuery, + [ + $limitControl->getLimitParam(), + $sortControl->getSortParam(), + $viewModeSwitcher->getViewModeParam(), + 'name' + ] + ); + + $searchBar->getSuggestionUrl()->setParam('isChildrenTab'); + $searchBar->getEditorUrl()->setParam('isChildrenTab'); + + if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) { + if ($searchBar->hasBeenSubmitted()) { + $filter = $this->getFilter(); + } else { + $this->addControl($searchBar); + $this->sendMultipartUpdate(); + + return; + } + } else { + $filter = $searchBar->getFilter(); + } + + $nodesQuery->filter($filter); + + $this->addControl($paginationControl); + $this->addControl($sortControl); + $this->addControl($limitControl); + $this->addControl($viewModeSwitcher); + $this->addControl($searchBar); + + $this->addContent( + (new DependencyNodeList($nodesQuery)) + ->setViewMode($viewModeSwitcher->getViewMode()) + ); + + if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) { + $this->sendMultipartUpdate(); + } + + $this->setAutorefreshInterval(10); + } + + public function completeAction(): void + { + $isChildrenTab = $this->params->shift('isChildrenTab'); + $relation = $isChildrenTab ? 'parent' : 'child'; + + $suggestions = (new ObjectSuggestions()) + ->setModel(DependencyNode::class) + ->setBaseFilter(Filter::equal("$relation.host.id", $this->host->id)) + ->forRequest($this->getServerRequest()); + + $this->getDocument()->add($suggestions); + } + + public function searchEditorAction(): void + { + $isChildrenTab = $this->params->shift('isChildrenTab'); + $redirectUrl = $isChildrenTab + ? Url::fromPath('icingadb/host/children', ['name' => $this->host->name]) + : Url::fromPath('icingadb/host/parents', ['name' => $this->host->name]); + + $editor = $this->createSearchEditor( + DependencyNode::on($this->getDb()), + $redirectUrl, + [ + LimitControl::DEFAULT_LIMIT_PARAM, + SortControl::DEFAULT_SORT_PARAM, + ViewModeSwitcher::DEFAULT_VIEW_MODE_PARAM, + 'name' + ] + ); + + if ($isChildrenTab) { + $editor->getSuggestionUrl()->setParam('isChildrenTab'); + } + + $this->getDocument()->add($editor); + $this->setTitle($this->translate('Adjust Filter')); + } + + /** + * Fetch the nodes for the current host + * + * @param bool $fetchParents Whether to fetch the parents or the children + * + * @return Query + */ + protected function fetchNodes(bool $fetchParents = false): Query + { + $query = DependencyNode::on($this->getDb()) + ->with([ + 'host', + 'host.state', + 'host.state.last_comment', + 'service', + 'service.state', + 'service.state.last_comment', + 'service.host', + 'service.host.state', + 'redundancy_group', + 'redundancy_group.state' + ]) + ->setResultSetClass(VolatileStateResults::class); + + $this->joinFix($query, $this->host->id, $fetchParents); + + $this->applyRestrictions($query); + + return $query; + } + protected function createTabs(): Tabs { + $hasDependencyNode = DependencyNode::on($this->getDb()) + ->columns('1') + ->filter(Filter::all( + Filter::equal('host_id', $this->host->id), + Filter::unlike('service_id', '*') + )) + ->first() !== null; + $tabs = $this->getTabs() ->add('index', [ 'label' => t('Host'), @@ -236,9 +447,19 @@ protected function createTabs(): Tabs 'url' => HostLinks::services($this->host) ]) ->add('history', [ - 'label' => t('History'), - 'url' => HostLinks::history($this->host) + 'label' => t('History'), + 'url' => HostLinks::history($this->host) + ]); + + if ($hasDependencyNode) { + $tabs->add('parents', [ + 'label' => $this->translate('Parents'), + 'url' => Url::fromPath('icingadb/host/parents', ['name' => $this->host->name]) + ])->add('children', [ + 'label' => $this->translate('Children'), + 'url' => Url::fromPath('icingadb/host/children', ['name' => $this->host->name]) ]); + } if ($this->hasPermission('icingadb/object/show-source')) { $tabs->add('source', [ @@ -279,4 +500,42 @@ protected function getDefaultTabControls(): array { return [(new HostList([$this->host]))->setDetailActionsDisabled()->setNoSubjectLink()]; } + + /** + * Filter the query to only include (direct) parents or children of the given object. + * + * @todo This is a workaround, remove it once https://github.com/Icinga/ipl-orm/issues/76 is fixed + * + * @param Query $query + * @param string $objectId + * @param bool $fetchParents Fetch parents if true, children otherwise + */ + protected function joinFix(Query $query, string $objectId, bool $fetchParents = false): void + { + $filterTable = $fetchParents ? 'child' : 'parent'; + $utilizeType = $fetchParents ? 'parent' : 'child'; + + $edge = DependencyEdge::on($this->getDb()) + ->utilize($utilizeType) + ->columns([new Expression('1')]) + ->filter(Filter::equal("$filterTable.host.id", $objectId)) + ->filter(Filter::unlike("$filterTable.service.id", '*')); + + $edge->getFilter()->metaData()->set('forceOptimization', false); + + $resolver = $edge->getResolver(); + + $edgeAlias = $resolver->getAlias( + $resolver->resolveRelation($resolver->qualifyPath($utilizeType, $edge->getModel()->getTableName())) + ->getTarget() + ); + + $query->filter(new Exists( + $edge->assembleSelect() + ->where( + "$edgeAlias.id = " + . $query->getResolver()->qualifyColumn('id', $query->getModel()->getTableName()) + ) + )); + } } diff --git a/application/controllers/ServiceController.php b/application/controllers/ServiceController.php index 13fca630a..9aff81b29 100644 --- a/application/controllers/ServiceController.php +++ b/application/controllers/ServiceController.php @@ -13,17 +13,24 @@ use Icinga\Module\Icingadb\Common\Links; use Icinga\Module\Icingadb\Common\ServiceLinks; use Icinga\Module\Icingadb\Hook\TabHook\HookActions; +use Icinga\Module\Icingadb\Model\DependencyNode; use Icinga\Module\Icingadb\Model\History; use Icinga\Module\Icingadb\Model\Service; use Icinga\Module\Icingadb\Redis\VolatileStateResults; +use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions; +use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher; use Icinga\Module\Icingadb\Web\Controller; use Icinga\Module\Icingadb\Widget\Detail\QuickActions; use Icinga\Module\Icingadb\Widget\Detail\ServiceDetail; use Icinga\Module\Icingadb\Widget\Detail\ServiceInspectionDetail; use Icinga\Module\Icingadb\Widget\Detail\ServiceMetaInfo; +use Icinga\Module\Icingadb\Widget\ItemList\DependencyNodeList; use Icinga\Module\Icingadb\Widget\ItemList\HistoryList; use Icinga\Module\Icingadb\Widget\ItemList\ServiceList; +use ipl\Orm\Query; use ipl\Stdlib\Filter; +use ipl\Web\Control\LimitControl; +use ipl\Web\Control\SortControl; use ipl\Web\Url; class ServiceController extends Controller @@ -96,6 +103,131 @@ public function indexAction() $this->setAutorefreshInterval(10); } + public function parentsAction() + { + $nodesQuery = $this->fetchNodes(true); + + $limitControl = $this->createLimitControl(); + $paginationControl = $this->createPaginationControl($nodesQuery); + $sortControl = $this->createSortControl( + $nodesQuery, + [ + 'name' => $this->translate('Name'), + 'severity desc, last_state_change desc' => $this->translate('Severity'), + 'state' => $this->translate('Current State'), + 'last_state_change desc' => $this->translate('Last State Change') + ] + ); + $viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl); + + $searchBar = $this->createSearchBar( + $nodesQuery, + [ + $limitControl->getLimitParam(), + $sortControl->getSortParam(), + $viewModeSwitcher->getViewModeParam(), + 'name', + 'host.name' + ] + ); + + if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) { + if ($searchBar->hasBeenSubmitted()) { + $filter = $this->getFilter(); + } else { + $this->addControl($searchBar); + $this->sendMultipartUpdate(); + + return; + } + } else { + $filter = $searchBar->getFilter(); + } + + $nodesQuery->filter($filter); + + $this->addControl($paginationControl); + $this->addControl($sortControl); + $this->addControl($limitControl); + $this->addControl($viewModeSwitcher); + $this->addControl($searchBar); + + $this->addContent( + (new DependencyNodeList($nodesQuery)) + ->setViewMode($viewModeSwitcher->getViewMode()) + ); + + if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) { + $this->sendMultipartUpdate(); + } + + $this->setAutorefreshInterval(10); + } + + public function childrenAction() + { + $nodesQuery = $this->fetchNodes(); + + $limitControl = $this->createLimitControl(); + $paginationControl = $this->createPaginationControl($nodesQuery); + $sortControl = $this->createSortControl( + $nodesQuery, + [ + 'name' => $this->translate('Name'), + 'severity desc, last_state_change desc' => $this->translate('Severity'), + 'state' => $this->translate('Current State'), + 'last_state_change desc' => $this->translate('Last State Change') + ] + ); + $viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl); + + $searchBar = $this->createSearchBar( + $nodesQuery, + [ + $limitControl->getLimitParam(), + $sortControl->getSortParam(), + $viewModeSwitcher->getViewModeParam(), + 'name', + 'host.name' + ] + ); + + $searchBar->getSuggestionUrl()->setParam('isChildrenTab'); + $searchBar->getEditorUrl()->setParam('isChildrenTab'); + + if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) { + if ($searchBar->hasBeenSubmitted()) { + $filter = $this->getFilter(); + } else { + $this->addControl($searchBar); + $this->sendMultipartUpdate(); + + return; + } + } else { + $filter = $searchBar->getFilter(); + } + + $nodesQuery->filter($filter); + + $this->addControl($paginationControl); + $this->addControl($sortControl); + $this->addControl($limitControl); + $this->addControl($viewModeSwitcher); + $this->addControl($searchBar); + + $this->addContent( + (new DependencyNodeList($nodesQuery)) + ->setViewMode($viewModeSwitcher->getViewMode()) + ); + + if (! $searchBar->hasBeenSubmitted() && $searchBar->hasBeenSent()) { + $this->sendMultipartUpdate(); + } + + $this->setAutorefreshInterval(10); + } + public function sourceAction() { $this->assertPermission('icingadb/object/show-source'); @@ -187,8 +319,95 @@ public function historyAction() } } + public function completeAction(): void + { + $isChildrenTab = $this->params->shift('isChildrenTab'); + $relation = $isChildrenTab ? 'parent' : 'child'; + + $suggestions = (new ObjectSuggestions()) + ->setModel(DependencyNode::class) + ->setBaseFilter(Filter::equal("$relation.service.id", $this->service->id)) + ->forRequest($this->getServerRequest()); + + $this->getDocument()->add($suggestions); + } + + public function searchEditorAction(): void + { + $isChildrenTab = $this->params->shift('isChildrenTab'); + $redirectUrl = $isChildrenTab + ? Url::fromPath( + 'icingadb/service/children', + ['name' => $this->service->name, 'host.name' => $this->service->host->name] + ) + : Url::fromPath( + 'icingadb/service/parents', + ['name' => $this->service->name, 'host.name' => $this->service->host->name] + ); + + $editor = $this->createSearchEditor( + DependencyNode::on($this->getDb()), + $redirectUrl, + [ + LimitControl::DEFAULT_LIMIT_PARAM, + SortControl::DEFAULT_SORT_PARAM, + ViewModeSwitcher::DEFAULT_VIEW_MODE_PARAM, + 'name', + 'host.name' + ] + ); + + if ($isChildrenTab) { + $editor->getSuggestionUrl()->setParam('isChildrenTab'); + } + + $this->getDocument()->add($editor); + $this->setTitle($this->translate('Adjust Filter')); + } + + /** + * Fetch the nodes for the current service + * + * @param bool $fetchParents Whether to fetch the parents or the children + * + * @return Query + */ + protected function fetchNodes(bool $fetchParents = false): Query + { + $query = DependencyNode::on($this->getDb()) + ->with([ + 'host', + 'host.state', + 'host.state.last_comment', + 'service', + 'service.state', + 'service.state.last_comment', + 'service.host', + 'service.host.state', + 'redundancy_group', + 'redundancy_group.state' + ]) + ->filter(Filter::equal( + sprintf('%s.service.id', $fetchParents ? 'child' : 'parent'), + $this->service->id + )) + ->setResultSetClass(VolatileStateResults::class); + + $this->applyRestrictions($query); + + return $query; + } + protected function createTabs() { + $hasDependecyNode = DependencyNode::on($this->getDb()) + ->columns('1') + ->filter(Filter::all( + Filter::equal('service_id', $this->service->id), + Filter::equal('host_id', $this->service->host_id) + )) + ->first() !== null; + $tabs = $this->getTabs() ->add('index', [ 'label' => t('Service'), @@ -199,6 +418,22 @@ protected function createTabs() 'url' => ServiceLinks::history($this->service, $this->service->host) ]); + if ($hasDependecyNode) { + $tabs->add('parents', [ + 'label' => $this->translate('Parents'), + 'url' => Url::fromPath( + 'icingadb/service/parents', + ['name' => $this->service->name, 'host.name' => $this->service->host->name] + ) + ])->add('children', [ + 'label' => $this->translate('Children'), + 'url' => Url::fromPath( + 'icingadb/service/children', + ['name' => $this->service->name, 'host.name' => $this->service->host->name] + ) + ]); + } + if ($this->hasPermission('icingadb/object/show-source')) { $tabs->add('source', [ 'label' => t('Source'), From ef6ab1fa0e6bd2900ad03810ac31e249aa344577 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Thu, 21 Nov 2024 10:24:32 +0100 Subject: [PATCH 3/8] (Host|Service)Controller: Shift the required param - Otherwise the searchbar uses it as base filter and apply it on the query --- application/controllers/HostController.php | 2 +- application/controllers/ServiceController.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/application/controllers/HostController.php b/application/controllers/HostController.php index ec3bedfa1..c84d2259d 100644 --- a/application/controllers/HostController.php +++ b/application/controllers/HostController.php @@ -49,7 +49,7 @@ class HostController extends Controller public function init() { - $name = $this->params->getRequired('name'); + $name = $this->params->shiftRequired('name'); $query = Host::on($this->getDb())->with(['state', 'icon_image', 'timeperiod']); $query diff --git a/application/controllers/ServiceController.php b/application/controllers/ServiceController.php index 9aff81b29..db6183fc4 100644 --- a/application/controllers/ServiceController.php +++ b/application/controllers/ServiceController.php @@ -43,8 +43,8 @@ class ServiceController extends Controller public function init() { - $name = $this->params->getRequired('name'); - $hostName = $this->params->getRequired('host.name'); + $name = $this->params->shiftRequired('name'); + $hostName = $this->params->shiftRequired('host.name'); $query = Service::on($this->getDb()) ->with([ From 6505ae185e7aa490a9d2f1c6623f64651d8be44e Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Thu, 21 Nov 2024 10:27:38 +0100 Subject: [PATCH 4/8] (Host|Service)Controller: Fix tab activation - Set the outer tab as active. Previously, the inner tab was activated in the setTitleTab method, but the outer tab does not know about the state of inner tabs. So whenever sendMultipartUpdate() -> getActiveTab() was called, the retured value was always null. --- application/controllers/HostController.php | 4 +--- application/controllers/ServiceController.php | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/application/controllers/HostController.php b/application/controllers/HostController.php index c84d2259d..9ffa6345c 100644 --- a/application/controllers/HostController.php +++ b/application/controllers/HostController.php @@ -480,9 +480,7 @@ protected function setTitleTab(string $name) $tab = $this->createTabs()->get($name); if ($tab !== null) { - $tab->setActive(); - - $this->setTitle($tab->getLabel()); + $this->getTabs()->activate($name); } } diff --git a/application/controllers/ServiceController.php b/application/controllers/ServiceController.php index db6183fc4..0488a7e1a 100644 --- a/application/controllers/ServiceController.php +++ b/application/controllers/ServiceController.php @@ -456,9 +456,7 @@ protected function setTitleTab(string $name) $tab = $this->createTabs()->get($name); if ($tab !== null) { - $tab->setActive(); - - $this->setTitle($tab->getLabel()); + $this->getTabs()->activate($name); } } From e4eac2736d83a25047f4e1d60745e756efba118d Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Thu, 21 Nov 2024 13:03:20 +0100 Subject: [PATCH 5/8] (Host|Service)Controller: Add return type to methods --- application/controllers/HostController.php | 16 ++++++++-------- application/controllers/ServiceController.php | 17 +++++++++-------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/application/controllers/HostController.php b/application/controllers/HostController.php index 9ffa6345c..5b2a7547a 100644 --- a/application/controllers/HostController.php +++ b/application/controllers/HostController.php @@ -47,7 +47,7 @@ class HostController extends Controller /** @var Host The host object */ protected $host; - public function init() + public function init(): void { $name = $this->params->shiftRequired('name'); @@ -76,7 +76,7 @@ public function init() $this->setTitle($host->display_name); } - public function indexAction() + public function indexAction(): void { $serviceSummary = ServicestateSummary::on($this->getDb()); $serviceSummary->filter(Filter::equal('service.host_id', $this->host->id)); @@ -95,7 +95,7 @@ public function indexAction() $this->setAutorefreshInterval(10); } - public function sourceAction() + public function sourceAction(): void { $this->assertPermission('icingadb/object/show-source'); @@ -114,7 +114,7 @@ public function sourceAction() )); } - public function historyAction() + public function historyAction(): \Generator { $compact = $this->view->compact; // TODO: Find a less-legacy way.. @@ -185,7 +185,7 @@ public function historyAction() } } - public function servicesAction() + public function servicesAction(): \Generator { if ($this->host->state->is_overdue) { $this->controls->addAttributes(['class' => 'overdue']); @@ -234,7 +234,7 @@ public function servicesAction() $this->setAutorefreshInterval(10); } - public function parentsAction() + public function parentsAction(): void { $nodesQuery = $this->fetchNodes(true); @@ -294,7 +294,7 @@ public function parentsAction() $this->setAutorefreshInterval(10); } - public function childrenAction() + public function childrenAction(): void { $nodesQuery = $this->fetchNodes(); @@ -475,7 +475,7 @@ protected function createTabs(): Tabs return $tabs; } - protected function setTitleTab(string $name) + protected function setTitleTab(string $name): void { $tab = $this->createTabs()->get($name); diff --git a/application/controllers/ServiceController.php b/application/controllers/ServiceController.php index 0488a7e1a..b43ebdb8a 100644 --- a/application/controllers/ServiceController.php +++ b/application/controllers/ServiceController.php @@ -32,6 +32,7 @@ use ipl\Web\Control\LimitControl; use ipl\Web\Control\SortControl; use ipl\Web\Url; +use ipl\Web\Widget\Tabs; class ServiceController extends Controller { @@ -41,7 +42,7 @@ class ServiceController extends Controller /** @var Service The service object */ protected $service; - public function init() + public function init(): void { $name = $this->params->shiftRequired('name'); $hostName = $this->params->shiftRequired('host.name'); @@ -89,7 +90,7 @@ public function init() ); } - public function indexAction() + public function indexAction(): void { if ($this->service->state->is_overdue) { $this->controls->addAttributes(['class' => 'overdue']); @@ -103,7 +104,7 @@ public function indexAction() $this->setAutorefreshInterval(10); } - public function parentsAction() + public function parentsAction(): void { $nodesQuery = $this->fetchNodes(true); @@ -164,7 +165,7 @@ public function parentsAction() $this->setAutorefreshInterval(10); } - public function childrenAction() + public function childrenAction(): void { $nodesQuery = $this->fetchNodes(); @@ -228,7 +229,7 @@ public function childrenAction() $this->setAutorefreshInterval(10); } - public function sourceAction() + public function sourceAction(): void { $this->assertPermission('icingadb/object/show-source'); @@ -247,7 +248,7 @@ public function sourceAction() )); } - public function historyAction() + public function historyAction(): \Generator { $compact = $this->view->compact; // TODO: Find a less-legacy way.. @@ -398,7 +399,7 @@ protected function fetchNodes(bool $fetchParents = false): Query return $query; } - protected function createTabs() + protected function createTabs(): Tabs { $hasDependecyNode = DependencyNode::on($this->getDb()) ->columns('1') @@ -451,7 +452,7 @@ protected function createTabs() return $tabs; } - protected function setTitleTab(string $name) + protected function setTitleTab(string $name): void { $tab = $this->createTabs()->get($name); From 518090ed9c87faf5b0aa2e6fb167ebb3c024d8c9 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Mon, 25 Nov 2024 12:45:01 +0100 Subject: [PATCH 6/8] Add view mode functionality for RedundancyGroup items --- .../Widget/ItemList/DependencyNodeList.php | 11 ++++++- .../RedundancyGroupListItemMinimal.php | 18 ++++++++++++ .../css/list/redundancy-group-list-item.less | 29 ++++++++++++++++--- 3 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 library/Icingadb/Widget/ItemList/RedundancyGroupListItemMinimal.php diff --git a/library/Icingadb/Widget/ItemList/DependencyNodeList.php b/library/Icingadb/Widget/ItemList/DependencyNodeList.php index c95f883fa..3457b16e9 100644 --- a/library/Icingadb/Widget/ItemList/DependencyNodeList.php +++ b/library/Icingadb/Widget/ItemList/DependencyNodeList.php @@ -28,14 +28,23 @@ protected function getItemClass(): string protected function createListItem(object $data): BaseListItem { + $viewMode = $this->getViewMode(); /** @var UnreachableParent|DependencyNode $data */ if ($data->redundancy_group_id !== null) { + if ($viewMode === 'minimal') { + return new RedundancyGroupListItemMinimal($data->redundancy_group, $this); + } + + if ($viewMode === 'detailed') { + $this->removeAttribute('class', 'default-layout'); + } + return new RedundancyGroupListItem($data->redundancy_group, $this); } $object = $data->service_id !== null ? $data->service : $data->host; - switch ($this->getViewMode()) { + switch ($viewMode) { case 'minimal': $class = $object instanceof Host ? HostListItemMinimal::class : ServiceListItemMinimal::class; break; diff --git a/library/Icingadb/Widget/ItemList/RedundancyGroupListItemMinimal.php b/library/Icingadb/Widget/ItemList/RedundancyGroupListItemMinimal.php new file mode 100644 index 000000000..be6b96cb6 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/RedundancyGroupListItemMinimal.php @@ -0,0 +1,18 @@ + * { + margin: 0 .28125em; // 0 calculated   width + + &:first-child { + margin-left: 0; + } + + &:last-child { + margin-right: 0; + } + } + + .caption .object-statistics { + justify-self: end; + } + } +} + +.item-list.minimal > .list-item.redundancy-group-list-item { + .caption .object-statistics { + font-size: 0.75em; } } From ae3b36a329041291e87771c741dd986de4ab9fab Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Mon, 25 Nov 2024 15:15:48 +0100 Subject: [PATCH 7/8] Add `csv/json` export support for parents and children tab --- application/controllers/HostController.php | 13 +++++++++---- application/controllers/ServiceController.php | 11 ++++++++--- library/Icingadb/Data/CsvResultSetUtils.php | 4 +++- library/Icingadb/Data/JsonResultSetUtils.php | 4 +++- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/application/controllers/HostController.php b/application/controllers/HostController.php index 5b2a7547a..ef99f8c98 100644 --- a/application/controllers/HostController.php +++ b/application/controllers/HostController.php @@ -38,6 +38,7 @@ use ipl\Web\Control\SortControl; use ipl\Web\Url; use ipl\Web\Widget\Tabs; +use Generator; class HostController extends Controller { @@ -114,7 +115,7 @@ public function sourceAction(): void )); } - public function historyAction(): \Generator + public function historyAction(): Generator { $compact = $this->view->compact; // TODO: Find a less-legacy way.. @@ -185,7 +186,7 @@ public function historyAction(): \Generator } } - public function servicesAction(): \Generator + public function servicesAction(): Generator { if ($this->host->state->is_overdue) { $this->controls->addAttributes(['class' => 'overdue']); @@ -234,7 +235,7 @@ public function servicesAction(): \Generator $this->setAutorefreshInterval(10); } - public function parentsAction(): void + public function parentsAction(): Generator { $nodesQuery = $this->fetchNodes(true); @@ -276,6 +277,8 @@ public function parentsAction(): void $nodesQuery->filter($filter); + yield $this->export($nodesQuery); + $this->addControl($paginationControl); $this->addControl($sortControl); $this->addControl($limitControl); @@ -294,7 +297,7 @@ public function parentsAction(): void $this->setAutorefreshInterval(10); } - public function childrenAction(): void + public function childrenAction(): Generator { $nodesQuery = $this->fetchNodes(); @@ -339,6 +342,8 @@ public function childrenAction(): void $nodesQuery->filter($filter); + yield $this->export($nodesQuery); + $this->addControl($paginationControl); $this->addControl($sortControl); $this->addControl($limitControl); diff --git a/application/controllers/ServiceController.php b/application/controllers/ServiceController.php index b43ebdb8a..64aae9694 100644 --- a/application/controllers/ServiceController.php +++ b/application/controllers/ServiceController.php @@ -33,6 +33,7 @@ use ipl\Web\Control\SortControl; use ipl\Web\Url; use ipl\Web\Widget\Tabs; +use Generator; class ServiceController extends Controller { @@ -104,7 +105,7 @@ public function indexAction(): void $this->setAutorefreshInterval(10); } - public function parentsAction(): void + public function parentsAction(): Generator { $nodesQuery = $this->fetchNodes(true); @@ -147,6 +148,8 @@ public function parentsAction(): void $nodesQuery->filter($filter); + yield $this->export($nodesQuery); + $this->addControl($paginationControl); $this->addControl($sortControl); $this->addControl($limitControl); @@ -165,7 +168,7 @@ public function parentsAction(): void $this->setAutorefreshInterval(10); } - public function childrenAction(): void + public function childrenAction(): Generator { $nodesQuery = $this->fetchNodes(); @@ -211,6 +214,8 @@ public function childrenAction(): void $nodesQuery->filter($filter); + yield $this->export($nodesQuery); + $this->addControl($paginationControl); $this->addControl($sortControl); $this->addControl($limitControl); @@ -248,7 +253,7 @@ public function sourceAction(): void )); } - public function historyAction(): \Generator + public function historyAction(): Generator { $compact = $this->view->compact; // TODO: Find a less-legacy way.. diff --git a/library/Icingadb/Data/CsvResultSetUtils.php b/library/Icingadb/Data/CsvResultSetUtils.php index 61995d3a2..862260cc8 100644 --- a/library/Icingadb/Data/CsvResultSetUtils.php +++ b/library/Icingadb/Data/CsvResultSetUtils.php @@ -6,6 +6,7 @@ use DateTime; use DateTimeZone; +use Icinga\Module\Icingadb\Model\DependencyNode; use Icinga\Module\Icingadb\Model\Host; use Icinga\Module\Icingadb\Model\Service; use ipl\Orm\Model; @@ -67,7 +68,8 @@ protected function extractKeysAndValues(Model $model, string $path = ''): array public static function stream(Query $query): void { - if ($query->getModel() instanceof Host || $query->getModel() instanceof Service) { + $model = $query->getModel(); + if ($model instanceof Host || $model instanceof Service || $model instanceof DependencyNode) { $query->setResultSetClass(VolatileCsvResults::class); } else { $query->setResultSetClass(__CLASS__); diff --git a/library/Icingadb/Data/JsonResultSetUtils.php b/library/Icingadb/Data/JsonResultSetUtils.php index 8b8857122..dc78fe094 100644 --- a/library/Icingadb/Data/JsonResultSetUtils.php +++ b/library/Icingadb/Data/JsonResultSetUtils.php @@ -6,6 +6,7 @@ use DateTime; use DateTimeZone; +use Icinga\Module\Icingadb\Model\DependencyNode; use Icinga\Module\Icingadb\Model\Host; use Icinga\Module\Icingadb\Model\Service; use Icinga\Util\Json; @@ -61,7 +62,8 @@ protected function createObject(Model $model): array public static function stream(Query $query): void { - if ($query->getModel() instanceof Host || $query->getModel() instanceof Service) { + $model = $query->getModel(); + if ($model instanceof Host || $model instanceof Service || $model instanceof DependencyNode) { $query->setResultSetClass(VolatileJsonResults::class); } else { $query->setResultSetClass(__CLASS__); From 03a160d73e8a0939d06953d3f4e8bd23433750b5 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Wed, 18 Dec 2024 12:53:57 +0100 Subject: [PATCH 8/8] Don't show `parents` `children` tab if `icingadb.schema` version `< 6` --- application/controllers/HostController.php | 19 +++++++++++------- application/controllers/ServiceController.php | 20 +++++++++++-------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/application/controllers/HostController.php b/application/controllers/HostController.php index ef99f8c98..03f7ee8d5 100644 --- a/application/controllers/HostController.php +++ b/application/controllers/HostController.php @@ -8,6 +8,7 @@ use Icinga\Exception\NotFoundError; use Icinga\Module\Icingadb\Command\Object\GetObjectCommand; use Icinga\Module\Icingadb\Command\Transport\CommandTransport; +use Icinga\Module\Icingadb\Common\Backend; use Icinga\Module\Icingadb\Common\CommandActions; use Icinga\Module\Icingadb\Common\HostLinks; use Icinga\Module\Icingadb\Common\Links; @@ -434,13 +435,17 @@ protected function fetchNodes(bool $fetchParents = false): Query protected function createTabs(): Tabs { - $hasDependencyNode = DependencyNode::on($this->getDb()) - ->columns('1') - ->filter(Filter::all( - Filter::equal('host_id', $this->host->id), - Filter::unlike('service_id', '*') - )) - ->first() !== null; + if (Backend::getDbSchemaVersion() < 6) { + $hasDependencyNode = false; + } else { + $hasDependencyNode = DependencyNode::on($this->getDb()) + ->columns('1') + ->filter(Filter::all( + Filter::equal('host_id', $this->host->id), + Filter::unlike('service_id', '*') + )) + ->first() !== null; + } $tabs = $this->getTabs() ->add('index', [ diff --git a/application/controllers/ServiceController.php b/application/controllers/ServiceController.php index 64aae9694..29c5e2572 100644 --- a/application/controllers/ServiceController.php +++ b/application/controllers/ServiceController.php @@ -406,13 +406,17 @@ protected function fetchNodes(bool $fetchParents = false): Query protected function createTabs(): Tabs { - $hasDependecyNode = DependencyNode::on($this->getDb()) - ->columns('1') - ->filter(Filter::all( - Filter::equal('service_id', $this->service->id), - Filter::equal('host_id', $this->service->host_id) - )) - ->first() !== null; + if (Backend::getDbSchemaVersion() < 6) { + $hasDependencyNode = false; + } else { + $hasDependencyNode = DependencyNode::on($this->getDb()) + ->columns('1') + ->filter(Filter::all( + Filter::equal('service_id', $this->service->id), + Filter::equal('host_id', $this->service->host_id) + )) + ->first() !== null; + } $tabs = $this->getTabs() ->add('index', [ @@ -424,7 +428,7 @@ protected function createTabs(): Tabs 'url' => ServiceLinks::history($this->service, $this->service->host) ]); - if ($hasDependecyNode) { + if ($hasDependencyNode) { $tabs->add('parents', [ 'label' => $this->translate('Parents'), 'url' => Url::fromPath(