From 645ae00155bcab9e5d889ad8f71e190eabb81a36 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Tue, 19 Nov 2024 21:59:37 +0100 Subject: [PATCH 01/14] (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 921c1df52..d9608da76 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 ad8d71b253b60cedd91d3d6eb2cb32efb61b509f Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Thu, 21 Nov 2024 10:23:06 +0100 Subject: [PATCH 02/14] (Host|Service)Controller: Add parents and children tab --- application/controllers/HostController.php | 263 +++++++++++++++++- application/controllers/ServiceController.php | 236 ++++++++++++++++ 2 files changed, 497 insertions(+), 2 deletions(-) diff --git a/application/controllers/HostController.php b/application/controllers/HostController.php index c85463390..aff433b7d 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([new Expression('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 d9608da76..9265fe3f2 100644 --- a/application/controllers/ServiceController.php +++ b/application/controllers/ServiceController.php @@ -13,17 +13,25 @@ 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\Sql\Expression; use ipl\Stdlib\Filter; +use ipl\Web\Control\LimitControl; +use ipl\Web\Control\SortControl; use ipl\Web\Url; class ServiceController extends Controller @@ -96,6 +104,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 +320,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([new Expression('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 +419,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 d5d36d72335c8679c24a9180aa76b43067efa68f Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Thu, 21 Nov 2024 10:24:32 +0100 Subject: [PATCH 03/14] (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 aff433b7d..353c670ed 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 9265fe3f2..697d8d2b9 100644 --- a/application/controllers/ServiceController.php +++ b/application/controllers/ServiceController.php @@ -44,8 +44,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 5e436dc57fe3ea7eb2bec88468918565a36f8f6a Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Thu, 21 Nov 2024 10:27:38 +0100 Subject: [PATCH 04/14] (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 353c670ed..01986867c 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 697d8d2b9..1727dc945 100644 --- a/application/controllers/ServiceController.php +++ b/application/controllers/ServiceController.php @@ -457,9 +457,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 1ceb351e047fb0d3547057113012c5d1cfaf9b96 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Thu, 21 Nov 2024 13:03:20 +0100 Subject: [PATCH 05/14] (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 01986867c..1c81bdcaf 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 1727dc945..c6416c643 100644 --- a/application/controllers/ServiceController.php +++ b/application/controllers/ServiceController.php @@ -33,6 +33,7 @@ use ipl\Web\Control\LimitControl; use ipl\Web\Control\SortControl; use ipl\Web\Url; +use ipl\Web\Widget\Tabs; class ServiceController extends Controller { @@ -42,7 +43,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'); @@ -90,7 +91,7 @@ public function init() ); } - public function indexAction() + public function indexAction(): void { if ($this->service->state->is_overdue) { $this->controls->addAttributes(['class' => 'overdue']); @@ -104,7 +105,7 @@ public function indexAction() $this->setAutorefreshInterval(10); } - public function parentsAction() + public function parentsAction(): void { $nodesQuery = $this->fetchNodes(true); @@ -165,7 +166,7 @@ public function parentsAction() $this->setAutorefreshInterval(10); } - public function childrenAction() + public function childrenAction(): void { $nodesQuery = $this->fetchNodes(); @@ -229,7 +230,7 @@ public function childrenAction() $this->setAutorefreshInterval(10); } - public function sourceAction() + public function sourceAction(): void { $this->assertPermission('icingadb/object/show-source'); @@ -248,7 +249,7 @@ public function sourceAction() )); } - public function historyAction() + public function historyAction(): \Generator { $compact = $this->view->compact; // TODO: Find a less-legacy way.. @@ -399,7 +400,7 @@ protected function fetchNodes(bool $fetchParents = false): Query return $query; } - protected function createTabs() + protected function createTabs(): Tabs { $hasDependecyNode = DependencyNode::on($this->getDb()) ->columns([new Expression('1')]) @@ -452,7 +453,7 @@ protected function createTabs() return $tabs; } - protected function setTitleTab(string $name) + protected function setTitleTab(string $name): void { $tab = $this->createTabs()->get($name); From 18ba5a3dfbaf4cf99d7754663b873c14e732d249 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Mon, 25 Nov 2024 12:45:01 +0100 Subject: [PATCH 06/14] Add view mode functionality for RedundancyGroup items --- .../Widget/ItemList/DependencyNodeList.php | 11 ++++++++++- .../RedundancyGroupListItemMinimal.php | 18 ++++++++++++++++++ .../css/list/redundancy-group-list-item.less | 11 ++++++++--- 3 files changed, 36 insertions(+), 4 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 @@ + .redundancy-group-list-item { + .caption .object-statistics { + font-size: 0.75em; } } From cda2f4bb0559656fca52bfdd76a1a9983c208ef3 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Mon, 25 Nov 2024 15:15:48 +0100 Subject: [PATCH 07/14] 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 1c81bdcaf..d47a8e3dc 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 c6416c643..ec09701f6 100644 --- a/application/controllers/ServiceController.php +++ b/application/controllers/ServiceController.php @@ -34,6 +34,7 @@ use ipl\Web\Control\SortControl; use ipl\Web\Url; use ipl\Web\Widget\Tabs; +use Generator; class ServiceController extends Controller { @@ -105,7 +106,7 @@ public function indexAction(): void $this->setAutorefreshInterval(10); } - public function parentsAction(): void + public function parentsAction(): Generator { $nodesQuery = $this->fetchNodes(true); @@ -148,6 +149,8 @@ public function parentsAction(): void $nodesQuery->filter($filter); + yield $this->export($nodesQuery); + $this->addControl($paginationControl); $this->addControl($sortControl); $this->addControl($limitControl); @@ -166,7 +169,7 @@ public function parentsAction(): void $this->setAutorefreshInterval(10); } - public function childrenAction(): void + public function childrenAction(): Generator { $nodesQuery = $this->fetchNodes(); @@ -212,6 +215,8 @@ public function childrenAction(): void $nodesQuery->filter($filter); + yield $this->export($nodesQuery); + $this->addControl($paginationControl); $this->addControl($sortControl); $this->addControl($limitControl); @@ -249,7 +254,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 b69fb09c9072bbf2bfb7519275a2cd42e71e7204 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Wed, 18 Dec 2024 12:53:57 +0100 Subject: [PATCH 08/14] Don't show `parents` `children` tab if `icingadb.schema` does not support it --- 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 d47a8e3dc..c900f570c 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([new Expression('1')]) - ->filter(Filter::all( - Filter::equal('host_id', $this->host->id), - Filter::unlike('service_id', '*') - )) - ->first() !== null; + if (! Backend::supportsDependencies()) { + $hasDependencyNode = false; + } else { + $hasDependencyNode = DependencyNode::on($this->getDb()) + ->columns([new Expression('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 ec09701f6..0d5a51645 100644 --- a/application/controllers/ServiceController.php +++ b/application/controllers/ServiceController.php @@ -407,13 +407,17 @@ protected function fetchNodes(bool $fetchParents = false): Query protected function createTabs(): Tabs { - $hasDependecyNode = DependencyNode::on($this->getDb()) - ->columns([new Expression('1')]) - ->filter(Filter::all( - Filter::equal('service_id', $this->service->id), - Filter::equal('host_id', $this->service->host_id) - )) - ->first() !== null; + if (! Backend::supportsDependencies()) { + $hasDependencyNode = false; + } else { + $hasDependencyNode = DependencyNode::on($this->getDb()) + ->columns([new Expression('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', [ @@ -425,7 +429,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( From 6aa32fa9f8b7d1a895cc14dfcdd1f2251266c5f4 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Thu, 9 Jan 2025 11:21:02 +0100 Subject: [PATCH 09/14] (Host|Service)Controller: Use Translation trait method --- application/controllers/HostController.php | 20 +++++++++---------- application/controllers/ServiceController.php | 12 +++++------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/application/controllers/HostController.php b/application/controllers/HostController.php index c900f570c..76b187991 100644 --- a/application/controllers/HostController.php +++ b/application/controllers/HostController.php @@ -63,7 +63,7 @@ public function init(): void /** @var Host $host */ $host = $query->first(); if ($host === null) { - throw new NotFoundError(t('Host not found')); + throw new NotFoundError($this->translate('Host not found')); } $this->host = $host; @@ -150,7 +150,7 @@ public function historyAction(): Generator $sortControl = $this->createSortControl( $history, [ - 'history.event_time desc, history.event_type desc' => t('Event Time') + 'history.event_time desc, history.event_type desc' => $this->translate('Event Time') ] ); $viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl, true); @@ -214,10 +214,10 @@ public function servicesAction(): Generator $sortControl = $this->createSortControl( $services, [ - 'service.display_name' => t('Name'), - 'service.state.severity desc,service.state.last_state_change desc' => t('Severity'), - 'service.state.soft_state' => t('Current State'), - 'service.state.last_state_change desc' => t('Last State Change') + 'service.display_name' => $this->translate('Name'), + 'service.state.severity desc,service.state.last_state_change desc' => $this->translate('Severity'), + 'service.state.soft_state' => $this->translate('Current State'), + 'service.state.last_state_change desc' => $this->translate('Last State Change') ] ); @@ -449,15 +449,15 @@ protected function createTabs(): Tabs $tabs = $this->getTabs() ->add('index', [ - 'label' => t('Host'), + 'label' => $this->translate('Host'), 'url' => Links::host($this->host) ]) ->add('services', [ - 'label' => t('Services'), + 'label' => $this->translate('Services'), 'url' => HostLinks::services($this->host) ]) ->add('history', [ - 'label' => t('History'), + 'label' => $this->translate('History'), 'url' => HostLinks::history($this->host) ]); @@ -473,7 +473,7 @@ protected function createTabs(): Tabs if ($this->hasPermission('icingadb/object/show-source')) { $tabs->add('source', [ - 'label' => t('Source'), + 'label' => $this->translate('Source'), 'url' => Links::hostSource($this->host) ]); } diff --git a/application/controllers/ServiceController.php b/application/controllers/ServiceController.php index 0d5a51645..c086c3976 100644 --- a/application/controllers/ServiceController.php +++ b/application/controllers/ServiceController.php @@ -73,7 +73,7 @@ public function init(): void /** @var Service $service */ $service = $query->first(); if ($service === null) { - throw new NotFoundError(t('Service not found')); + throw new NotFoundError($this->translate('Service not found')); } $this->service = $service; @@ -86,7 +86,7 @@ public function init(): void $this->setTitleTab($this->getRequest()->getActionName()); $this->setTitle( - t('%s on %s', ' on '), + $this->translate('%s on %s', ' on '), $service->display_name, $service->host->display_name ); @@ -289,7 +289,7 @@ public function historyAction(): Generator $sortControl = $this->createSortControl( $history, [ - 'history.event_time desc, history.event_type desc' => t('Event Time') + 'history.event_time desc, history.event_type desc' => $this->translate('Event Time') ] ); $viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl, true); @@ -421,11 +421,11 @@ protected function createTabs(): Tabs $tabs = $this->getTabs() ->add('index', [ - 'label' => t('Service'), + 'label' => $this->translate('Service'), 'url' => Links::service($this->service, $this->service->host) ]) ->add('history', [ - 'label' => t('History'), + 'label' => $this->translate('History'), 'url' => ServiceLinks::history($this->service, $this->service->host) ]); @@ -447,7 +447,7 @@ protected function createTabs(): Tabs if ($this->hasPermission('icingadb/object/show-source')) { $tabs->add('source', [ - 'label' => t('Source'), + 'label' => $this->translate('Source'), 'url' => Links::serviceSource($this->service, $this->service->host) ]); } From 41601abff78c095e3292b995f9f6043535ec3d35 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Fri, 10 Jan 2025 13:18:26 +0100 Subject: [PATCH 10/14] (Host|Service)Controller: Sort parents children tab list by default to `severity` - Disable the default sorting for the `$hasDependencyNode` and UnreachableParent's `$rootQuery`, as sort is not required here and triggers an error, because the sort columns are not retrieved. --- application/controllers/HostController.php | 3 +++ application/controllers/ServiceController.php | 3 +++ library/Icingadb/Model/DependencyNode.php | 5 +++++ library/Icingadb/Model/UnreachableParent.php | 3 ++- 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/application/controllers/HostController.php b/application/controllers/HostController.php index 76b187991..2c1f53c46 100644 --- a/application/controllers/HostController.php +++ b/application/controllers/HostController.php @@ -251,6 +251,7 @@ public function parentsAction(): Generator 'last_state_change desc' => $this->translate('Last State Change') ] ); + $viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl); $searchBar = $this->createSearchBar( @@ -313,6 +314,7 @@ public function childrenAction(): Generator 'last_state_change desc' => $this->translate('Last State Change') ] ); + $viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl); $searchBar = $this->createSearchBar( @@ -444,6 +446,7 @@ protected function createTabs(): Tabs Filter::equal('host_id', $this->host->id), Filter::unlike('service_id', '*') )) + ->disableDefaultSort() ->first() !== null; } diff --git a/application/controllers/ServiceController.php b/application/controllers/ServiceController.php index c086c3976..58c50d650 100644 --- a/application/controllers/ServiceController.php +++ b/application/controllers/ServiceController.php @@ -121,6 +121,7 @@ public function parentsAction(): Generator 'last_state_change desc' => $this->translate('Last State Change') ] ); + $viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl); $searchBar = $this->createSearchBar( @@ -184,6 +185,7 @@ public function childrenAction(): Generator 'last_state_change desc' => $this->translate('Last State Change') ] ); + $viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl); $searchBar = $this->createSearchBar( @@ -416,6 +418,7 @@ protected function createTabs(): Tabs Filter::equal('service_id', $this->service->id), Filter::equal('host_id', $this->service->host_id) )) + ->disableDefaultSort() ->first() !== null; } diff --git a/library/Icingadb/Model/DependencyNode.php b/library/Icingadb/Model/DependencyNode.php index 0788debc9..eb4e285ba 100644 --- a/library/Icingadb/Model/DependencyNode.php +++ b/library/Icingadb/Model/DependencyNode.php @@ -83,6 +83,11 @@ public function getSearchColumns(): array ]; } + public function getDefaultSort(): array + { + return ['severity DESC', 'last_state_change DESC']; + } + public function createBehaviors(Behaviors $behaviors): void { $behaviors->add(new Binary([ diff --git a/library/Icingadb/Model/UnreachableParent.php b/library/Icingadb/Model/UnreachableParent.php index 7c20301cd..d7ae01b82 100644 --- a/library/Icingadb/Model/UnreachableParent.php +++ b/library/Icingadb/Model/UnreachableParent.php @@ -137,7 +137,8 @@ private static function selectNodes(Connection $db, Model $root): Select 'service_id' => new Expression("COALESCE(%s, $binaryCast)", ['service_id']), 'redundancy_group_id' => new Expression($binaryCast), 'is_group_member' => new Expression($booleanCast) - ]); + ]) + ->disableDefaultSort(); if ($root instanceof Host) { $rootQuery->filter(Filter::all( Filter::equal('host_id', $root->id), From 3602d5ab653ceea46f485450f834da764159af25 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Fri, 17 Jan 2025 08:40:35 +0100 Subject: [PATCH 11/14] (Host/Service)Controller: Change method name and fix syntax --- application/controllers/HostController.php | 26 +++++++++---------- application/controllers/ServiceController.php | 26 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/application/controllers/HostController.php b/application/controllers/HostController.php index 2c1f53c46..b4d370c75 100644 --- a/application/controllers/HostController.php +++ b/application/controllers/HostController.php @@ -238,7 +238,7 @@ public function servicesAction(): Generator public function parentsAction(): Generator { - $nodesQuery = $this->fetchNodes(true); + $nodesQuery = $this->fetchDependencyNodes(true); $limitControl = $this->createLimitControl(); $paginationControl = $this->createPaginationControl($nodesQuery); @@ -301,7 +301,7 @@ public function parentsAction(): Generator public function childrenAction(): Generator { - $nodesQuery = $this->fetchNodes(); + $nodesQuery = $this->fetchDependencyNodes(); $limitControl = $this->createLimitControl(); $paginationControl = $this->createPaginationControl($nodesQuery); @@ -405,13 +405,13 @@ public function searchEditorAction(): void } /** - * Fetch the nodes for the current host + * Fetch the dependency nodes of the current host * - * @param bool $fetchParents Whether to fetch the parents or the children + * @param bool $parents Whether to fetch the parents or the children * * @return Query */ - protected function fetchNodes(bool $fetchParents = false): Query + protected function fetchDependencyNodes(bool $parents = false): Query { $query = DependencyNode::on($this->getDb()) ->with([ @@ -428,7 +428,7 @@ protected function fetchNodes(bool $fetchParents = false): Query ]) ->setResultSetClass(VolatileStateResults::class); - $this->joinFix($query, $this->host->id, $fetchParents); + $this->joinFix($query, $this->host->id, $parents); $this->applyRestrictions($query); @@ -441,13 +441,13 @@ protected function createTabs(): Tabs $hasDependencyNode = false; } else { $hasDependencyNode = DependencyNode::on($this->getDb()) - ->columns([new Expression('1')]) - ->filter(Filter::all( - Filter::equal('host_id', $this->host->id), - Filter::unlike('service_id', '*') - )) - ->disableDefaultSort() - ->first() !== null; + ->columns([new Expression('1')]) + ->filter(Filter::all( + Filter::equal('host_id', $this->host->id), + Filter::unlike('service_id', '*') + )) + ->disableDefaultSort() + ->first() !== null; } $tabs = $this->getTabs() diff --git a/application/controllers/ServiceController.php b/application/controllers/ServiceController.php index 58c50d650..0a0a452bc 100644 --- a/application/controllers/ServiceController.php +++ b/application/controllers/ServiceController.php @@ -108,7 +108,7 @@ public function indexAction(): void public function parentsAction(): Generator { - $nodesQuery = $this->fetchNodes(true); + $nodesQuery = $this->fetchDependencyNodes(true); $limitControl = $this->createLimitControl(); $paginationControl = $this->createPaginationControl($nodesQuery); @@ -172,7 +172,7 @@ public function parentsAction(): Generator public function childrenAction(): Generator { - $nodesQuery = $this->fetchNodes(); + $nodesQuery = $this->fetchDependencyNodes(); $limitControl = $this->createLimitControl(); $paginationControl = $this->createPaginationControl($nodesQuery); @@ -375,13 +375,13 @@ public function searchEditorAction(): void } /** - * Fetch the nodes for the current service + * Fetch the dependency nodes of the current service * - * @param bool $fetchParents Whether to fetch the parents or the children + * @param bool $parents Whether to fetch the parents or the children * * @return Query */ - protected function fetchNodes(bool $fetchParents = false): Query + protected function fetchDependencyNodes(bool $parents = false): Query { $query = DependencyNode::on($this->getDb()) ->with([ @@ -397,7 +397,7 @@ protected function fetchNodes(bool $fetchParents = false): Query 'redundancy_group.state' ]) ->filter(Filter::equal( - sprintf('%s.service.id', $fetchParents ? 'child' : 'parent'), + sprintf('%s.service.id', $parents ? 'child' : 'parent'), $this->service->id )) ->setResultSetClass(VolatileStateResults::class); @@ -413,13 +413,13 @@ protected function createTabs(): Tabs $hasDependencyNode = false; } else { $hasDependencyNode = DependencyNode::on($this->getDb()) - ->columns([new Expression('1')]) - ->filter(Filter::all( - Filter::equal('service_id', $this->service->id), - Filter::equal('host_id', $this->service->host_id) - )) - ->disableDefaultSort() - ->first() !== null; + ->columns([new Expression('1')]) + ->filter(Filter::all( + Filter::equal('service_id', $this->service->id), + Filter::equal('host_id', $this->service->host_id) + )) + ->disableDefaultSort() + ->first() !== null; } $tabs = $this->getTabs() From ebdb48a3f565469a8b8e21da6a4ca240ea77d18d Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Fri, 17 Jan 2025 12:00:38 +0100 Subject: [PATCH 12/14] (Host/Service)Controller: Add separated `(searchEditor/complete)Action` for `parent & children tab` --- application/controllers/HostController.php | 74 ++++++++++----- application/controllers/ServiceController.php | 89 ++++++++++++------- 2 files changed, 108 insertions(+), 55 deletions(-) diff --git a/application/controllers/HostController.php b/application/controllers/HostController.php index b4d370c75..d30ea765d 100644 --- a/application/controllers/HostController.php +++ b/application/controllers/HostController.php @@ -317,18 +317,22 @@ public function childrenAction(): Generator $viewModeSwitcher = $this->createViewModeSwitcher($paginationControl, $limitControl); - $searchBar = $this->createSearchBar( - $nodesQuery, - [ - $limitControl->getLimitParam(), - $sortControl->getSortParam(), - $viewModeSwitcher->getViewModeParam(), - 'name' - ] - ); - - $searchBar->getSuggestionUrl()->setParam('isChildrenTab'); - $searchBar->getEditorUrl()->setParam('isChildrenTab'); + $preserveParams = [ + $limitControl->getLimitParam(), + $sortControl->getSortParam(), + $viewModeSwitcher->getViewModeParam(), + 'name' + ]; + + $requestParams = Url::fromRequest()->onlyWith($preserveParams)->getParams(); + $searchBar = $this->createSearchBar($nodesQuery, $preserveParams) + ->setEditorUrl( + Url::fromPath('icingadb/host/children-search-editor') + ->setParams($requestParams) + )->setSuggestionUrl( + Url::fromPath('icingadb/host/children-complete') + ->setParams(clone $requestParams) + ); if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) { if ($searchBar->hasBeenSubmitted()) { @@ -367,12 +371,19 @@ public function childrenAction(): Generator public function completeAction(): void { - $isChildrenTab = $this->params->shift('isChildrenTab'); - $relation = $isChildrenTab ? 'parent' : 'child'; + $suggestions = (new ObjectSuggestions()) + ->setModel(DependencyNode::class) + ->setBaseFilter(Filter::equal("child.host.id", $this->host->id)) + ->forRequest($this->getServerRequest()); + + $this->getDocument()->add($suggestions); + } + public function childrenCompleteAction(): void + { $suggestions = (new ObjectSuggestions()) ->setModel(DependencyNode::class) - ->setBaseFilter(Filter::equal("$relation.host.id", $this->host->id)) + ->setBaseFilter(Filter::equal("parent.host.id", $this->host->id)) ->forRequest($this->getServerRequest()); $this->getDocument()->add($suggestions); @@ -380,14 +391,9 @@ public function completeAction(): void 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, + Url::fromPath('icingadb/host/parents', ['name' => $this->host->name]), [ LimitControl::DEFAULT_LIMIT_PARAM, SortControl::DEFAULT_SORT_PARAM, @@ -396,9 +402,29 @@ public function searchEditorAction(): void ] ); - if ($isChildrenTab) { - $editor->getSuggestionUrl()->setParam('isChildrenTab'); - } + $this->getDocument()->add($editor); + $this->setTitle($this->translate('Adjust Filter')); + } + + public function childrenSearchEditorAction(): void + { + $preserveParams = [ + LimitControl::DEFAULT_LIMIT_PARAM, + SortControl::DEFAULT_SORT_PARAM, + ViewModeSwitcher::DEFAULT_VIEW_MODE_PARAM, + 'name' + ]; + + $editor = $this->createSearchEditor( + DependencyNode::on($this->getDb()), + Url::fromPath('icingadb/host/children', ['name' => $this->host->name]), + $preserveParams + ); + + $editor->setSuggestionUrl( + Url::fromPath('icingadb/host/children-complete') + ->setParams(Url::fromRequest()->onlyWith($preserveParams)->getParams()) + ); $this->getDocument()->add($editor); $this->setTitle($this->translate('Adjust Filter')); diff --git a/application/controllers/ServiceController.php b/application/controllers/ServiceController.php index 0a0a452bc..fbfb08ef6 100644 --- a/application/controllers/ServiceController.php +++ b/application/controllers/ServiceController.php @@ -188,19 +188,23 @@ public function childrenAction(): Generator $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'); + $preserveParams = [ + $limitControl->getLimitParam(), + $sortControl->getSortParam(), + $viewModeSwitcher->getViewModeParam(), + 'name', + 'host.name' + ]; + + $requestParams = Url::fromRequest()->onlyWith($preserveParams)->getParams(); + $searchBar = $this->createSearchBar($nodesQuery, $preserveParams) + ->setEditorUrl( + Url::fromPath('icingadb/service/children-search-editor') + ->setParams($requestParams) + )->setSuggestionUrl( + Url::fromPath('icingadb/service/children-complete') + ->setParams(clone $requestParams) + ); if ($searchBar->hasBeenSent() && ! $searchBar->isValid()) { if ($searchBar->hasBeenSubmitted()) { @@ -330,12 +334,19 @@ public function historyAction(): Generator public function completeAction(): void { - $isChildrenTab = $this->params->shift('isChildrenTab'); - $relation = $isChildrenTab ? 'parent' : 'child'; + $suggestions = (new ObjectSuggestions()) + ->setModel(DependencyNode::class) + ->setBaseFilter(Filter::equal("child.service.id", $this->service->id)) + ->forRequest($this->getServerRequest()); + + $this->getDocument()->add($suggestions); + } + public function childrenCompleteAction(): void + { $suggestions = (new ObjectSuggestions()) ->setModel(DependencyNode::class) - ->setBaseFilter(Filter::equal("$relation.service.id", $this->service->id)) + ->setBaseFilter(Filter::equal("parent.service.id", $this->service->id)) ->forRequest($this->getServerRequest()); $this->getDocument()->add($suggestions); @@ -343,20 +354,12 @@ public function completeAction(): void 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, + Url::fromPath( + 'icingadb/service/parents', + ['name' => $this->service->name, 'host.name' => $this->service->host->name] + ), [ LimitControl::DEFAULT_LIMIT_PARAM, SortControl::DEFAULT_SORT_PARAM, @@ -366,9 +369,33 @@ public function searchEditorAction(): void ] ); - if ($isChildrenTab) { - $editor->getSuggestionUrl()->setParam('isChildrenTab'); - } + $this->getDocument()->add($editor); + $this->setTitle($this->translate('Adjust Filter')); + } + + public function childrenSearchEditorAction(): void + { + $preserveParams = [ + LimitControl::DEFAULT_LIMIT_PARAM, + SortControl::DEFAULT_SORT_PARAM, + ViewModeSwitcher::DEFAULT_VIEW_MODE_PARAM, + 'name', + 'host.name' + ]; + + $editor = $this->createSearchEditor( + DependencyNode::on($this->getDb()), + Url::fromPath( + 'icingadb/service/children', + ['name' => $this->service->name, 'host.name' => $this->service->host->name] + ), + $preserveParams + ); + + $editor->setSuggestionUrl( + Url::fromPath('icingadb/service/children-complete') + ->setParams(Url::fromRequest()->onlyWith($preserveParams)->getParams()) + ); $this->getDocument()->add($editor); $this->setTitle($this->translate('Adjust Filter')); From efe8d7706830618af3b7a1ee886d74a940253e3e Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Mon, 20 Jan 2025 14:44:40 +0100 Subject: [PATCH 13/14] RedundancyGroup: Don't wrap state msg into `span` and translate it with context This msg should be handled same as the host word, no element wrapping. This way, the margin arround the msg is applied properly. - Add the missing margin-right to subject in case of `.default-layout` list --- .../Widget/Detail/RedundancyGroupHeader.php | 15 ++++++++++----- .../Widget/ItemList/RedundancyGroupListItem.php | 15 ++++++++++----- public/css/list/redundancy-group-list-item.less | 6 ++++++ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/library/Icingadb/Widget/Detail/RedundancyGroupHeader.php b/library/Icingadb/Widget/Detail/RedundancyGroupHeader.php index d98c7b0f5..850a94802 100644 --- a/library/Icingadb/Widget/Detail/RedundancyGroupHeader.php +++ b/library/Icingadb/Widget/Detail/RedundancyGroupHeader.php @@ -8,6 +8,7 @@ use Icinga\Module\Icingadb\Model\RedundancyGroupSummary; use Icinga\Module\Icingadb\Widget\DependencyNodeStatistics; use ipl\Html\BaseHtmlElement; +use ipl\Html\Html; use ipl\Html\HtmlElement; use ipl\Html\Text; use ipl\Web\Widget\StateBall; @@ -37,14 +38,18 @@ protected function assembleVisual(BaseHtmlElement $visual): void protected function assembleTitle(BaseHtmlElement $title): void { - $title->addHtml($this->createSubject()); + $subject = $this->createSubject(); if ($this->object->state->failed) { - $text = $this->translate('has no working objects'); + $title->addHtml(Html::sprintf( + $this->translate('%s has no working objects', ' has ...'), + $subject + )); } else { - $text = $this->translate('has working objects'); + $title->addHtml(Html::sprintf( + $this->translate('%s has working objects', ' has ...'), + $subject + )); } - - $title->addHtml(HtmlElement::create('span', null, Text::create($text))); } protected function createStatistics(): BaseHtmlElement diff --git a/library/Icingadb/Widget/ItemList/RedundancyGroupListItem.php b/library/Icingadb/Widget/ItemList/RedundancyGroupListItem.php index 7b56f5cac..70a2c782e 100644 --- a/library/Icingadb/Widget/ItemList/RedundancyGroupListItem.php +++ b/library/Icingadb/Widget/ItemList/RedundancyGroupListItem.php @@ -12,6 +12,7 @@ use Icinga\Module\Icingadb\Model\RedundancyGroupState; use Icinga\Module\Icingadb\Widget\DependencyNodeStatistics; use ipl\Html\BaseHtmlElement; +use ipl\Html\Html; use ipl\Stdlib\Filter; use ipl\Web\Url; use ipl\Web\Widget\Link; @@ -80,14 +81,18 @@ protected function assembleCaption(BaseHtmlElement $caption): void protected function assembleTitle(BaseHtmlElement $title): void { - $title->addHtml($this->createSubject()); + $subject = $this->createSubject(); if ($this->state->failed) { - $text = $this->translate('has no working objects'); + $title->addHtml(Html::sprintf( + $this->translate('%s has no working objects', ' has ...'), + $subject + )); } else { - $text = $this->translate('has working objects'); + $title->addHtml(Html::sprintf( + $this->translate('%s has working objects', ' has ...'), + $subject + )); } - - $title->addHtml(HtmlElement::create('span', null, Text::create($text))); } protected function assemble(): void diff --git a/public/css/list/redundancy-group-list-item.less b/public/css/list/redundancy-group-list-item.less index a246e4ea2..5fec5aef8 100644 --- a/public/css/list/redundancy-group-list-item.less +++ b/public/css/list/redundancy-group-list-item.less @@ -9,3 +9,9 @@ font-size: 0.75em; } } + +.item-list.default-layout > .redundancy-group-list-item { + .title > .subject { + margin-right: .28125em; + } +} From 667fd2fe5bd3378be08832efb55e002303593a9d Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Mon, 20 Jan 2025 15:37:41 +0100 Subject: [PATCH 14/14] (Host/Service)Controller: Add missing `onlyWithCustomVarSources()` call --- application/controllers/HostController.php | 2 ++ application/controllers/ServiceController.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/application/controllers/HostController.php b/application/controllers/HostController.php index d30ea765d..ed716fd8b 100644 --- a/application/controllers/HostController.php +++ b/application/controllers/HostController.php @@ -373,6 +373,7 @@ public function completeAction(): void { $suggestions = (new ObjectSuggestions()) ->setModel(DependencyNode::class) + ->onlyWithCustomVarSources(['host', 'service', 'hostgroup', 'servicegroup']) ->setBaseFilter(Filter::equal("child.host.id", $this->host->id)) ->forRequest($this->getServerRequest()); @@ -383,6 +384,7 @@ public function childrenCompleteAction(): void { $suggestions = (new ObjectSuggestions()) ->setModel(DependencyNode::class) + ->onlyWithCustomVarSources(['host', 'service', 'hostgroup', 'servicegroup']) ->setBaseFilter(Filter::equal("parent.host.id", $this->host->id)) ->forRequest($this->getServerRequest()); diff --git a/application/controllers/ServiceController.php b/application/controllers/ServiceController.php index fbfb08ef6..c7c540a86 100644 --- a/application/controllers/ServiceController.php +++ b/application/controllers/ServiceController.php @@ -336,6 +336,7 @@ public function completeAction(): void { $suggestions = (new ObjectSuggestions()) ->setModel(DependencyNode::class) + ->onlyWithCustomVarSources(['host', 'service', 'hostgroup', 'servicegroup']) ->setBaseFilter(Filter::equal("child.service.id", $this->service->id)) ->forRequest($this->getServerRequest()); @@ -346,6 +347,7 @@ public function childrenCompleteAction(): void { $suggestions = (new ObjectSuggestions()) ->setModel(DependencyNode::class) + ->onlyWithCustomVarSources(['host', 'service', 'hostgroup', 'servicegroup']) ->setBaseFilter(Filter::equal("parent.service.id", $this->service->id)) ->forRequest($this->getServerRequest());