diff --git a/application/controllers/HostController.php b/application/controllers/HostController.php index 63ff03d5..31e41c80 100644 --- a/application/controllers/HostController.php +++ b/application/controllers/HostController.php @@ -26,11 +26,10 @@ 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\ObjectHeader; use Icinga\Module\Icingadb\Widget\Detail\QuickActions; use Icinga\Module\Icingadb\Widget\ItemList\ObjectList; -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; @@ -69,10 +68,7 @@ class HostController extends Controller $this->host = $host; $this->loadTabsForObject($host); - $this->addControl((new HostList([$host])) - ->setViewMode('objectHeader') - ->setDetailActionsDisabled() - ->setNoSubjectLink()); + $this->addControl(new ObjectHeader($host)); $this->setTitleTab($this->getRequest()->getActionName()); $this->setTitle($host->display_name); @@ -223,7 +219,7 @@ class HostController extends Controller yield $this->export($services); - $serviceList = (new ServiceList($services)) + $serviceList = (new ObjectList($services)) ->setViewMode($viewModeSwitcher->getViewMode()); $this->addControl($paginationControl); @@ -537,7 +533,7 @@ class HostController extends Controller protected function getDefaultTabControls(): array { - return [(new HostList([$this->host]))->setDetailActionsDisabled()->setNoSubjectLink()]; + return [new ObjectHeader($this->host)]; } /** diff --git a/application/controllers/HostgroupController.php b/application/controllers/HostgroupController.php index e48adca2..d5f07afe 100644 --- a/application/controllers/HostgroupController.php +++ b/application/controllers/HostgroupController.php @@ -12,7 +12,7 @@ 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\ItemList\HostList; +use Icinga\Module\Icingadb\Widget\ItemList\ObjectList; use Icinga\Module\Icingadb\Widget\ItemTable\HostgroupTableRow; use ipl\Html\Html; use ipl\Stdlib\Filter; @@ -109,8 +109,10 @@ class HostgroupController extends Controller yield $this->export($hosts); - $hostList = (new HostList($hosts->execute())) - ->setViewMode($viewModeSwitcher->getViewMode()); + $hostList = (new ObjectList($hosts)) + ->setViewMode($viewModeSwitcher->getViewMode()) + ->setMultiselectUrl(Links::hostsDetails()) + ->setDetailUrl(Url::fromPath('icingadb/host')); // ICINGAWEB_EXPORT_FORMAT is not set yet and $this->format is inaccessible, yeah... if ($this->getRequest()->getParam('format') === 'pdf') { diff --git a/application/controllers/HostsController.php b/application/controllers/HostsController.php index fff7139a..114b46ce 100644 --- a/application/controllers/HostsController.php +++ b/application/controllers/HostsController.php @@ -15,8 +15,8 @@ use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions; use Icinga\Module\Icingadb\Web\Controller; use Icinga\Module\Icingadb\Widget\Detail\MultiselectQuickActions; use Icinga\Module\Icingadb\Widget\Detail\ObjectsDetail; -use Icinga\Module\Icingadb\Widget\ItemList\HostList; use Icinga\Module\Icingadb\Widget\HostStatusBar; +use Icinga\Module\Icingadb\Widget\ItemList\ObjectList; use Icinga\Module\Icingadb\Widget\ItemTable\HostItemTable; use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher; use Icinga\Module\Icingadb\Widget\ShowMore; @@ -104,8 +104,10 @@ class HostsController extends Controller $hostList = (new HostItemTable($results, HostItemTable::applyColumnMetaData($hosts, $columns))) ->setSort($sortControl->getSort()); } else { - $hostList = (new HostList($results)) - ->setViewMode($viewModeSwitcher->getViewMode()); + $hostList = (new ObjectList($results)) + ->setViewMode($viewModeSwitcher->getViewMode()) + ->setMultiselectUrl(Links::hostsDetails()) + ->setDetailUrl(Url::fromPath('icingadb/host')); } $this->addContent($hostList); @@ -166,9 +168,8 @@ class HostsController extends Controller $summary->comments_total = $comments->count(); $this->addControl( - (new HostList($results)) + (new ObjectList($results)) ->setViewMode('minimal') - ->setDetailActionsDisabled() ); $this->addControl(new ShowMore( $results, diff --git a/application/controllers/ServiceController.php b/application/controllers/ServiceController.php index 8886aa96..e68587e0 100644 --- a/application/controllers/ServiceController.php +++ b/application/controllers/ServiceController.php @@ -20,13 +20,13 @@ 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\ObjectHeader; 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\ObjectList; 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; @@ -79,10 +79,7 @@ class ServiceController extends Controller $this->service = $service; $this->loadTabsForObject($service); - $this->addControl((new ServiceList([$service])) - ->setViewMode('objectHeader') - ->setDetailActionsDisabled() - ->setNoSubjectLink()); + $this->addControl(new ObjectHeader($service)); $this->setTitleTab($this->getRequest()->getActionName()); $this->setTitle( @@ -515,6 +512,6 @@ class ServiceController extends Controller protected function getDefaultTabControls(): array { - return [(new ServiceList([$this->service]))->setDetailActionsDisabled()->setNoSubjectLink()]; + return [new ObjectHeader($this->service)]; } } diff --git a/application/controllers/ServicegroupController.php b/application/controllers/ServicegroupController.php index c3dea15b..4e78204a 100644 --- a/application/controllers/ServicegroupController.php +++ b/application/controllers/ServicegroupController.php @@ -12,7 +12,7 @@ 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\ItemList\ServiceList; +use Icinga\Module\Icingadb\Widget\ItemList\ObjectList; use Icinga\Module\Icingadb\Widget\ItemTable\ServicegroupTableRow; use ipl\Html\Html; use ipl\Stdlib\Filter; @@ -117,8 +117,10 @@ class ServicegroupController extends Controller yield $this->export($services); - $serviceList = (new ServiceList($services->execute())) - ->setViewMode($viewModeSwitcher->getViewMode()); + $serviceList = (new ObjectList($services)) + ->setViewMode($viewModeSwitcher->getViewMode()) + ->setMultiselectUrl(Links::servicesDetails()) + ->setDetailUrl(Url::fromPath('icingadb/service')); // ICINGAWEB_EXPORT_FORMAT is not set yet and $this->format is inaccessible, yeah... if ($this->getRequest()->getParam('format') === 'pdf') { diff --git a/application/controllers/ServicesController.php b/application/controllers/ServicesController.php index c39f8b5d..dcf8281f 100644 --- a/application/controllers/ServicesController.php +++ b/application/controllers/ServicesController.php @@ -17,7 +17,7 @@ use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions; use Icinga\Module\Icingadb\Web\Controller; use Icinga\Module\Icingadb\Widget\Detail\MultiselectQuickActions; use Icinga\Module\Icingadb\Widget\Detail\ObjectsDetail; -use Icinga\Module\Icingadb\Widget\ItemList\ServiceList; +use Icinga\Module\Icingadb\Widget\ItemList\ObjectList; use Icinga\Module\Icingadb\Widget\ItemTable\ServiceItemTable; use Icinga\Module\Icingadb\Widget\ServiceStatusBar; use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher; @@ -115,8 +115,10 @@ class ServicesController extends Controller $serviceList = (new ServiceItemTable($results, ServiceItemTable::applyColumnMetaData($services, $columns))) ->setSort($sortControl->getSort()); } else { - $serviceList = (new ServiceList($results)) - ->setViewMode($viewModeSwitcher->getViewMode()); + $serviceList = (new ObjectList($results)) + ->setViewMode($viewModeSwitcher->getViewMode()) + ->setMultiselectUrl(Links::servicesDetails()) + ->setDetailUrl(Url::fromPath('icingadb/service')); } $this->addContent($serviceList); @@ -182,9 +184,8 @@ class ServicesController extends Controller $summary->comments_total = $comments->count(); $this->addControl( - (new ServiceList($results)) + (new ObjectList($results)) ->setViewMode('minimal') - ->setDetailActionsDisabled() ); $this->addControl(new ShowMore( $results, diff --git a/library/Icingadb/View/BaseHostAndServiceRenderer.php b/library/Icingadb/View/BaseHostAndServiceRenderer.php new file mode 100644 index 00000000..81de4aa9 --- /dev/null +++ b/library/Icingadb/View/BaseHostAndServiceRenderer.php @@ -0,0 +1,283 @@ + */ +abstract class BaseHostAndServiceRenderer implements ItemRenderer +{ + use Translation; + + abstract protected function createSubject($item, string $layout): ValidHtml; + + public function assembleVisual($item, HtmlDocument $visual, string $layout): void + { + if ($layout === 'header') { + if ($item->state->state_type === 'soft') { + $stateType = 'soft_state'; + $previousStateType = 'previous_soft_state'; + + if ($item->state->previous_soft_state === 0) { + $previousStateType = 'hard_state'; + } + } else { + $stateType = 'hard_state'; + $previousStateType = 'previous_hard_state'; + + if ($item->state->hard_state === $item->state->previous_hard_state) { + $previousStateType = 'previous_soft_state'; + } + } + + if ($item instanceof Host) { + $state = HostStates::text($item->state->$stateType); + $previousState = HostStates::text($item->state->$previousStateType); + } else { + $state = ServiceStates::text($item->state->$stateType); + $previousState = ServiceStates::text($item->state->$previousStateType); + } + + $stateChange = new StateChange($state, $previousState); + if ($stateType === 'soft_state') { + $stateChange->setCurrentStateBallSize(StateBall::SIZE_MEDIUM_LARGE); + } + + if ($previousStateType === 'previous_soft_state') { + $stateChange->setPreviousStateBallSize(StateBall::SIZE_MEDIUM_LARGE); + if ($stateType === 'soft_state') { + $visual->getAttributes()->add('class', 'small-state-change'); + } + } + + $stateChange->setIcon($item->state->getIcon()); + $stateChange->setHandled( + $item->state->is_problem && ($item->state->is_handled || ! $item->state->is_reachable) + ); + + $visual->addHtml($stateChange); + + return; + } + + $ballSize = $layout === 'minimal' ? StateBall::SIZE_BIG : StateBall::SIZE_LARGE; + + $stateBall = new StateBall($item->state->getStateText(), $ballSize); + $stateBall->add($item->state->getIcon()); + if ($item->state->is_problem && ($item->state->is_handled || ! $item->state->is_reachable)) { + $stateBall->getAttributes()->add('class', 'handled'); + } + + $visual->addHtml($stateBall); + if ($layout !== 'minimal' && $item->state->state_type === 'soft') { + $visual->addHtml( + new CheckAttempt((int) $item->state->check_attempt, (int) $item->max_check_attempts) + ); + } + } + + public function assembleCaption($item, HtmlDocument $caption, string $layout): void + { + if ($item->state->soft_state === null && $item->state->output === null) { + $caption->addHtml(Text::create($this->translate('Waiting for Icinga DB to synchronize the state.'))); + } else { + if (empty($item->state->output)) { + $pluginOutput = new EmptyState($this->translate('Output unavailable.')); + } else { + $pluginOutput = new PluginOutputContainer(PluginOutput::fromObject($item)); + } + + $caption->addHtml($pluginOutput); + } + } + + public function assembleTitle($item, HtmlDocument $title, string $layout): void + { + $title->addHtml(Html::sprintf( + $this->translate('%s is %s', ' is '), + $this->createSubject($item, $layout), + Html::tag('span', ['class' => 'state-text'], $item->state->getStateTextTranslated()) + )); + + if (isset($item->state->affects_children) && $item->state->affects_children) { + $total = (int) $item->total_children; + + if ($total > 1000) { + $total = '1000+'; + $tooltip = $this->translate('Up to 1000+ affected objects'); + } else { + $tooltip = sprintf( + $this->translatePlural( + '%d affected object', + 'Up to %d affected objects', + $total + ), + $total + ); + } + + $icon = new Icon(Icons::UNREACHABLE); + + $title->addHtml(new HtmlElement( + 'span', + Attributes::create([ + 'class' => 'affected-objects', + 'title' => $tooltip + ]), + $icon, + Text::create($total) + )); + } + } + + public function assembleExtendedInfo($item, HtmlDocument $info, string $layout): void + { + if ($item->state->is_overdue) { + $since = new TimeSince($item->state->next_update->getTimestamp()); + $since->prepend($this->translate('Overdue') . ' '); + $since->prependHtml(new Icon(Icons::WARNING)); + + $info->addHtml($since); + } elseif ($item->state->last_state_change !== null && $item->state->last_state_change->getTimestamp() > 0) { + $info->addHtml(new TimeSince($item->state->last_state_change->getTimestamp())); + } + } + + public function assembleFooter($item, HtmlDocument $footer, string $layout): void + { + $pieChartLimit = 5; + $statusIcons = new HtmlElement('div', Attributes::create(['class' => 'status-icons'])); + $isService = $item instanceof Service; + + if ( + ($isService && $item->state->last_comment->service_id === $item->id) + || $item->state->last_comment->host_id === $item->id + ) { + $comment = $item->state->last_comment; + + if ($isService) { + $comment->service = $item; + } else { + $comment->host = $item; + } + + $comment = (new CommentList([$comment])) + ->setNoSubjectLink() + ->setObjectLinkDisabled() + ->setDetailActionsDisabled(); + + $statusIcons->addHtml( + new HtmlElement( + 'div', + Attributes::create(['class' => 'comment-wrapper']), + new HtmlElement('div', Attributes::create(['class' => 'comment-popup']), $comment), + (new Icon('comments', ['class' => 'comment-icon'])) + ) + ); + } + + if ($item->state->is_flapping) { + $title = $isService + ? sprintf( + $this->translate('Service "%s" on "%s" is in flapping state'), + $item->display_name, $item->host->display_name + ) + : sprintf( + $this->translate('Host "%s" is in flapping state'), + $item->display_name + ); + + $statusIcons->addHtml(new Icon('random', ['title' => $title])); + } + + if (! $item->notifications_enabled) { + $statusIcons->addHtml( + new Icon('bell-slash', ['title' => $this->translate('Notifications disabled')]) + ); + } + + if (! $item->active_checks_enabled) { + $statusIcons->addHtml( + new Icon('eye-slash', ['title' => $this->translate('Active checks disabled')]) + ); + } + + $performanceData = new HtmlElement('div', Attributes::create(['class' => 'performance-data'])); + if ($item->state->performance_data) { + $pieChartData = PerfDataSet::fromString($item->state->normalized_performance_data)->asArray(); + + $pies = []; + foreach ($pieChartData as $i => $perfdata) { + if ($perfdata->isVisualizable()) { + $pies[] = $perfdata->asInlinePie()->render(); + } + + // Check if number of visualizable pie charts is larger than $PIE_CHART_LIMIT + if (count($pies) > $pieChartLimit) { + break; + } + } + + $maxVisiblePies = $pieChartLimit - 2; + $numOfPies = count($pies); + foreach ($pies as $i => $pie) { + if ( + // Show max. 5 elements: if there are more than 5, show 4 + `…` + $i > $maxVisiblePies && $numOfPies > $pieChartLimit + ) { + $performanceData->addHtml(new HtmlElement('span', null, Text::create('…'))); + break; + } + + $performanceData->addHtml(HtmlString::create($pie)); + } + } + + if (! $statusIcons->isEmpty()) { + $footer->addHtml($statusIcons); + } + + if (! $performanceData->isEmpty()) { + $footer->addHtml($performanceData); + } + } + + public function assemble($item, string $name, HtmlDocument $element, string $layout): bool + { + if ($name === 'icon-image') { + if (isset($item->icon_image->icon_image)) { + $element->addHtml(new IconImage($item->icon_image->icon_image, $item->icon_image_alt)); + } + + return true; + } + + return false; + } +} diff --git a/library/Icingadb/View/HostRenderer.php b/library/Icingadb/View/HostRenderer.php new file mode 100644 index 00000000..7eb0abb5 --- /dev/null +++ b/library/Icingadb/View/HostRenderer.php @@ -0,0 +1,29 @@ +get('class')->addValue('host'); + } + + protected function createSubject($item, string $layout): ValidHtml + { + if ($layout === 'header') { + return new HtmlElement('span', new Attributes(['class' => 'subject']), new Text($item->display_name)); + } + + return new Link($item->display_name, Links::host($item), ['class' => 'subject']); + } +} diff --git a/library/Icingadb/View/ServiceRenderer.php b/library/Icingadb/View/ServiceRenderer.php new file mode 100644 index 00000000..9b0381b4 --- /dev/null +++ b/library/Icingadb/View/ServiceRenderer.php @@ -0,0 +1,41 @@ +get('class')->addValue('service'); + } + + protected function createSubject($item, string $layout): ValidHtml + { + $service = $item->display_name; + $host = [ + new StateBall($item->host->state->getStateText(), StateBall::SIZE_MEDIUM), + ' ', + $item->host->display_name + ]; + + $host = new Link($host, Links::host($item->host), ['class' => 'subject']); + if ($layout === 'header') { + $service = new HtmlElement('span', new Attributes(['class' => 'subject']), new Text($service)); + } else { + $service = new Link($service, Links::service($item, $item->host), ['class' => 'subject']); + } + + return Html::sprintf($this->translate('%s on %s', ' on '), $service, $host); + } +} diff --git a/library/Icingadb/Widget/Detail/ObjectHeader.php b/library/Icingadb/Widget/Detail/ObjectHeader.php index 7f119153..4178ee59 100644 --- a/library/Icingadb/Widget/Detail/ObjectHeader.php +++ b/library/Icingadb/Widget/Detail/ObjectHeader.php @@ -5,8 +5,12 @@ namespace Icinga\Module\Icingadb\Widget\Detail; use Icinga\Exception\NotImplementedError; +use Icinga\Module\Icingadb\Model\Host; use Icinga\Module\Icingadb\Model\RedundancyGroup; +use Icinga\Module\Icingadb\Model\Service; +use Icinga\Module\Icingadb\View\HostRenderer; use Icinga\Module\Icingadb\View\RedundancyGroupRenderer; +use Icinga\Module\Icingadb\View\ServiceRenderer; use ipl\Html\BaseHtmlElement; use ipl\Orm\Model; use ipl\Web\Layout\HeaderItemLayout; @@ -28,6 +32,15 @@ class ObjectHeader extends BaseHtmlElement switch (true) { case $this->object instanceof RedundancyGroup: $renderer = new RedundancyGroupRenderer(); + + break; + case $this->object instanceof Service: + $renderer = new ServiceRenderer(); + + break; + case $this->object instanceof Host: + $renderer = new HostRenderer(); + break; default: throw new NotImplementedError('Not implemented'); diff --git a/library/Icingadb/Widget/ItemList/BaseHostListItem.php b/library/Icingadb/Widget/ItemList/BaseHostListItem.php deleted file mode 100644 index edaf6c88..00000000 --- a/library/Icingadb/Widget/ItemList/BaseHostListItem.php +++ /dev/null @@ -1,56 +0,0 @@ -getNoSubjectLink()) { - return new HtmlElement( - 'span', - Attributes::create(['class' => 'subject']), - Text::create($this->item->display_name) - ); - } else { - return new Link($this->item->display_name, Links::host($this->item), ['class' => 'subject']); - } - } - - protected function init(): void - { - parent::init(); - - if ($this->list->getNoSubjectLink()) { - $this->setNoSubjectLink(); - } - - $this->list->addDetailFilterAttribute($this, Filter::equal('name', $this->item->name)) - ->addMultiselectFilterAttribute($this, Filter::equal('host.name', $this->item->name)); - } -} diff --git a/library/Icingadb/Widget/ItemList/BaseServiceListItem.php b/library/Icingadb/Widget/ItemList/BaseServiceListItem.php deleted file mode 100644 index fe4f0146..00000000 --- a/library/Icingadb/Widget/ItemList/BaseServiceListItem.php +++ /dev/null @@ -1,70 +0,0 @@ -item->display_name; - $host = [ - new StateBall($this->item->host->state->getStateText(), StateBall::SIZE_MEDIUM), - ' ', - $this->item->host->display_name - ]; - - $host = new Link($host, Links::host($this->item->host), ['class' => 'subject']); - if ($this->getNoSubjectLink()) { - $service = new HtmlElement('span', Attributes::create(['class' => 'subject']), Text::create($service)); - } else { - $service = new Link($service, Links::service($this->item, $this->item->host), ['class' => 'subject']); - } - - return [Html::sprintf(t('%s on %s', ' on '), $service, $host)]; - } - - protected function init(): void - { - parent::init(); - - if ($this->list->getNoSubjectLink()) { - $this->setNoSubjectLink(); - } - - $this->list->addMultiselectFilterAttribute( - $this, - Filter::all( - Filter::equal('service.name', $this->item->name), - Filter::equal('host.name', $this->item->host->name) - ) - ); - $this->list->addDetailFilterAttribute( - $this, - Filter::all( - Filter::equal('name', $this->item->name), - Filter::equal('host.name', $this->item->host->name) - ) - ); - } -} diff --git a/library/Icingadb/Widget/ItemList/HostDetailHeader.php b/library/Icingadb/Widget/ItemList/HostDetailHeader.php deleted file mode 100644 index b7afb232..00000000 --- a/library/Icingadb/Widget/ItemList/HostDetailHeader.php +++ /dev/null @@ -1,69 +0,0 @@ -state->state_type === 'soft') { - $stateType = 'soft_state'; - $previousStateType = 'previous_soft_state'; - - if ($this->state->previous_soft_state === 0) { - $previousStateType = 'hard_state'; - } - } else { - $stateType = 'hard_state'; - $previousStateType = 'previous_hard_state'; - - if ($this->state->hard_state === $this->state->previous_hard_state) { - $previousStateType = 'previous_soft_state'; - } - } - - $state = HostStates::text($this->state->$stateType); - $previousState = HostStates::text($this->state->$previousStateType); - - $stateChange = new StateChange($state, $previousState); - if ($stateType === 'soft_state') { - $stateChange->setCurrentStateBallSize(StateBall::SIZE_MEDIUM_LARGE); - } - - if ($previousStateType === 'previous_soft_state') { - $stateChange->setPreviousStateBallSize(StateBall::SIZE_MEDIUM_LARGE); - if ($stateType === 'soft_state') { - $visual->getAttributes()->add('class', 'small-state-change'); - } - } - - $stateChange->setIcon($this->state->getIcon()); - $stateChange->setHandled( - $this->state->is_problem && ($this->state->is_handled || ! $this->state->is_reachable) - ); - - $visual->addHtml($stateChange); - } - - protected function assemble(): void - { - $attributes = $this->list->getAttributes(); - if (! in_array('minimal', $attributes->get('class')->getValue())) { - $attributes->add('class', 'minimal'); - } - - parent::assemble(); - } -} diff --git a/library/Icingadb/Widget/ItemList/HostList.php b/library/Icingadb/Widget/ItemList/HostList.php deleted file mode 100644 index 2be1f84a..00000000 --- a/library/Icingadb/Widget/ItemList/HostList.php +++ /dev/null @@ -1,39 +0,0 @@ - 'host-list']; - - protected function getItemClass(): string - { - switch ($this->getViewMode()) { - case 'minimal': - return HostListItemMinimal::class; - case 'detailed': - $this->removeAttribute('class', 'default-layout'); - - return HostListItemDetailed::class; - case 'objectHeader': - return HostDetailHeader::class; - default: - return HostListItem::class; - } - } - - protected function init(): void - { - $this->initializeDetailActions(); - $this->setMultiselectUrl(Links::hostsDetails()); - $this->setDetailUrl(Url::fromPath('icingadb/host')); - } -} diff --git a/library/Icingadb/Widget/ItemList/HostListItem.php b/library/Icingadb/Widget/ItemList/HostListItem.php deleted file mode 100644 index 2eae6600..00000000 --- a/library/Icingadb/Widget/ItemList/HostListItem.php +++ /dev/null @@ -1,18 +0,0 @@ - 'status-icons'])); - - if ($this->item->state->last_comment->host_id === $this->item->id) { - $comment = $this->item->state->last_comment; - $comment->host = $this->item; - $comment = (new CommentList([$comment])) - ->setNoSubjectLink() - ->setObjectLinkDisabled() - ->setDetailActionsDisabled(); - - $statusIcons->addHtml( - new HtmlElement( - 'div', - Attributes::create(['class' => 'comment-wrapper']), - new HtmlElement('div', Attributes::create(['class' => 'comment-popup']), $comment), - (new Icon('comments', ['class' => 'comment-icon'])) - ) - ); - } - - if ($this->item->state->is_flapping) { - $statusIcons->addHtml(new Icon( - 'random', - [ - 'title' => sprintf(t('Host "%s" is in flapping state'), $this->item->display_name), - ] - )); - } - - if (! $this->item->notifications_enabled) { - $statusIcons->addHtml(new Icon('bell-slash', ['title' => t('Notifications disabled')])); - } - - if (! $this->item->active_checks_enabled) { - $statusIcons->addHtml(new Icon('eye-slash', ['title' => t('Active checks disabled')])); - } - - $performanceData = new HtmlElement('div', Attributes::create(['class' => 'performance-data'])); - if ($this->item->state->performance_data) { - $pieChartData = PerfDataSet::fromString($this->item->state->normalized_performance_data)->asArray(); - - $pies = []; - foreach ($pieChartData as $i => $perfdata) { - if ($perfdata->isVisualizable()) { - $pies[] = $perfdata->asInlinePie()->render(); - } - - // Check if number of visualizable pie charts is larger than PIE_CHART_LIMIT - if (count($pies) > HostListItemDetailed::PIE_CHART_LIMIT) { - break; - } - } - - $maxVisiblePies = HostListItemDetailed::PIE_CHART_LIMIT - 2; - $numOfPies = count($pies); - foreach ($pies as $i => $pie) { - if ( - // Show max. 5 elements: if there are more than 5, show 4 + `…` - $i > $maxVisiblePies && $numOfPies > HostListItemDetailed::PIE_CHART_LIMIT - ) { - $performanceData->addHtml(new HtmlElement('span', null, Text::create('…'))); - break; - } - - $performanceData->addHtml(HtmlString::create($pie)); - } - } - - if (! $statusIcons->isEmpty()) { - $footer->addHtml($statusIcons); - } - - if (! $performanceData->isEmpty()) { - $footer->addHtml($performanceData); - } - } -} diff --git a/library/Icingadb/Widget/ItemList/HostListItemMinimal.php b/library/Icingadb/Widget/ItemList/HostListItemMinimal.php deleted file mode 100644 index f04b991e..00000000 --- a/library/Icingadb/Widget/ItemList/HostListItemMinimal.php +++ /dev/null @@ -1,18 +0,0 @@ - + * @extends ItemList // TODO: fix type */ class ObjectList extends ItemList { @@ -41,6 +45,10 @@ class ObjectList extends ItemList parent::__construct($data, function (Model $item) { if ($item instanceof RedundancyGroup) { return new RedundancyGroupRenderer(); + } elseif ($item instanceof Service) { + return new ServiceRenderer(); + } elseif ($item instanceof Host) { + return new HostRenderer(); } else { throw new NotImplementedError('Not implemented'); } @@ -115,32 +123,64 @@ class ObjectList extends ItemList return $layout; } + /** + * @param object $data + * + * @return ListItem + */ protected function createListItem(object $data) { - /** @var UnreachableParent|DependencyNode $data */ - if ($data->redundancy_group_id !== null) { - return (new ListItem($data->redundancy_group, $this)) - ->addAttributes(['data-action-item' => true]); + if ($data instanceof DependencyNode) { + if (isset($data->redundancy_group_id)) { + $object = $data->redundancy_group; + } else { + $object = isset($data->service_id) ? $data->service : $data->host; + } + } else { + $object = $data; } - // TODO: Adjust the remaining stuff once self::getItemLayout supports host and services - - $object = $data->service_id !== null ? $data->service : $data->host; - - switch (false) { - case MinimalItemLayout::class: - $class = $object instanceof Host ? HostListItemMinimal::class : ServiceListItemMinimal::class; - break; - case DetailedItemLayout::class: - $this->removeAttribute('class', 'default-layout'); - - $class = $object instanceof Host ? HostListItemDetailed::class : ServiceListItemDetailed::class; - break; - default: - $class = $object instanceof Host ? HostListItem::class : ServiceListItem::class; + if (isset($object->icon_image->icon_image)) { + $this->setHasIconImages(true); } - return new $class($object, $this); + $item = parent::createListItem($object); + + if ($this->getDetailActionsDisabled()) { + return $item; + } + + switch (true) { + case $object instanceof RedundancyGroup: + $this->addDetailFilterAttribute($item, Filter::equal('id', bin2hex($object->id))); + + break; + case $object instanceof Service: + $this->addDetailFilterAttribute( + $item, + Filter::all( + Filter::equal('name', $object->name), + Filter::equal('host.name', $object->host->name) + ) + ); + + $this->addMultiSelectFilterAttribute( + $item, + Filter::all( + Filter::equal('service.name', $object->name), + Filter::equal('host.name', $object->host->name) + ) + ); + + break; + case $object instanceof Host: + $this->addDetailFilterAttribute($item, Filter::equal('name', $object->name)); + $this->addMultiSelectFilterAttribute($item, Filter::equal('host.name', $object->name)); + + break; + } + + return $item; } protected function assemble(): void diff --git a/library/Icingadb/Widget/ItemList/ServiceDetailHeader.php b/library/Icingadb/Widget/ItemList/ServiceDetailHeader.php deleted file mode 100644 index 8c5094d7..00000000 --- a/library/Icingadb/Widget/ItemList/ServiceDetailHeader.php +++ /dev/null @@ -1,69 +0,0 @@ -state->state_type === 'soft') { - $stateType = 'soft_state'; - $previousStateType = 'previous_soft_state'; - - if ($this->state->previous_soft_state === 0) { - $previousStateType = 'hard_state'; - } - } else { - $stateType = 'hard_state'; - $previousStateType = 'previous_hard_state'; - - if ($this->state->hard_state === $this->state->previous_hard_state) { - $previousStateType = 'previous_soft_state'; - } - } - - $state = ServiceStates::text($this->state->$stateType); - $previousState = ServiceStates::text($this->state->$previousStateType); - - $stateChange = new StateChange($state, $previousState); - if ($stateType === 'soft_state') { - $stateChange->setCurrentStateBallSize(StateBall::SIZE_MEDIUM_LARGE); - } - - if ($previousStateType === 'previous_soft_state') { - $stateChange->setPreviousStateBallSize(StateBall::SIZE_MEDIUM_LARGE); - if ($stateType === 'soft_state') { - $visual->getAttributes()->add('class', 'small-state-change'); - } - } - - $stateChange->setIcon($this->state->getIcon()); - $stateChange->setHandled( - $this->state->is_problem && ($this->state->is_handled || ! $this->state->is_reachable) - ); - - $visual->addHtml($stateChange); - } - - protected function assemble(): void - { - $attributes = $this->list->getAttributes(); - if (! in_array('minimal', $attributes->get('class')->getValue())) { - $attributes->add('class', 'minimal'); - } - - parent::assemble(); - } -} diff --git a/library/Icingadb/Widget/ItemList/ServiceList.php b/library/Icingadb/Widget/ItemList/ServiceList.php deleted file mode 100644 index 8d41a701..00000000 --- a/library/Icingadb/Widget/ItemList/ServiceList.php +++ /dev/null @@ -1,36 +0,0 @@ - 'service-list']; - - protected function getItemClass(): string - { - switch ($this->getViewMode()) { - case 'minimal': - return ServiceListItemMinimal::class; - case 'detailed': - $this->removeAttribute('class', 'default-layout'); - - return ServiceListItemDetailed::class; - case 'objectHeader': - return ServiceDetailHeader::class; - default: - return ServiceListItem::class; - } - } - - protected function init(): void - { - $this->initializeDetailActions(); - $this->setMultiselectUrl(Links::servicesDetails()); - $this->setDetailUrl(Url::fromPath('icingadb/service')); - } -} diff --git a/library/Icingadb/Widget/ItemList/ServiceListItem.php b/library/Icingadb/Widget/ItemList/ServiceListItem.php deleted file mode 100644 index a9745817..00000000 --- a/library/Icingadb/Widget/ItemList/ServiceListItem.php +++ /dev/null @@ -1,18 +0,0 @@ - 'status-icons'])); - - if ($this->item->state->last_comment->service_id === $this->item->id) { - $comment = $this->item->state->last_comment; - $comment->service = $this->item; - $comment = (new CommentList([$comment])) - ->setNoSubjectLink() - ->setObjectLinkDisabled() - ->setDetailActionsDisabled(); - - $statusIcons->addHtml( - new HtmlElement( - 'div', - Attributes::create(['class' => 'comment-wrapper']), - new HtmlElement('div', Attributes::create(['class' => 'comment-popup']), $comment), - (new Icon('comments', ['class' => 'comment-icon'])) - ) - ); - } - - if ($this->item->state->is_flapping) { - $statusIcons->addHtml(new Icon( - 'random', - [ - 'title' => sprintf( - t('Service "%s" on "%s" is in flapping state'), - $this->item->display_name, - $this->item->host->display_name - ), - ] - )); - } - - if (! $this->item->notifications_enabled) { - $statusIcons->addHtml(new Icon('bell-slash', ['title' => t('Notifications disabled')])); - } - - if (! $this->item->active_checks_enabled) { - $statusIcons->addHtml(new Icon('eye-slash', ['title' => t('Active checks disabled')])); - } - - $performanceData = new HtmlElement('div', Attributes::create(['class' => 'performance-data'])); - if ($this->item->state->performance_data) { - $pieChartData = PerfDataSet::fromString($this->item->state->normalized_performance_data)->asArray(); - - $pies = []; - foreach ($pieChartData as $i => $perfdata) { - if ($perfdata->isVisualizable()) { - $pies[] = $perfdata->asInlinePie()->render(); - } - - // Check if number of visualizable pie charts is larger than PIE_CHART_LIMIT - if (count($pies) > ServiceListItemDetailed::PIE_CHART_LIMIT) { - break; - } - } - - $maxVisiblePies = ServiceListItemDetailed::PIE_CHART_LIMIT - 2; - $numOfPies = count($pies); - foreach ($pies as $i => $pie) { - if ( - // Show max. 5 elements: if there are more than 5, show 4 + `…` - $i > $maxVisiblePies && $numOfPies > ServiceListItemDetailed::PIE_CHART_LIMIT - ) { - $performanceData->addHtml(new HtmlElement('span', null, Text::create('…'))); - break; - } - - $performanceData->addHtml(HtmlString::create($pie)); - } - } - - if (! $statusIcons->isEmpty()) { - $footer->addHtml($statusIcons); - } - - if (! $performanceData->isEmpty()) { - $footer->addHtml($performanceData); - } - } -} diff --git a/library/Icingadb/Widget/ItemList/ServiceListItemMinimal.php b/library/Icingadb/Widget/ItemList/ServiceListItemMinimal.php deleted file mode 100644 index e7a1bc66..00000000 --- a/library/Icingadb/Widget/ItemList/ServiceListItemMinimal.php +++ /dev/null @@ -1,18 +0,0 @@ -hasIconImages; - } - - /** - * Set whether the list contains at least one item with an icon_image - * - * @param bool $hasIconImages - * - * @return $this - */ - public function setHasIconImages(bool $hasIconImages): self - { - $this->hasIconImages = $hasIconImages; - - return $this; - } - - protected function assemble(): void - { - $this->addAttributes(['class' => $this->getViewMode()]); - - parent::assemble(); - - if ($this->data instanceof VolatileStateResults && $this->data->isRedisUnavailable()) { - $this->prependWrapper((new HtmlDocument())->addHtml(new Notice( - t('Redis is currently unavailable. The shown information might be outdated.') - ))); - } - } -} diff --git a/library/Icingadb/Widget/ItemList/StateListItem.php b/library/Icingadb/Widget/ItemList/StateListItem.php deleted file mode 100644 index b0d9dc92..00000000 --- a/library/Icingadb/Widget/ItemList/StateListItem.php +++ /dev/null @@ -1,174 +0,0 @@ -state = $this->item->state; - - if (isset($this->item->icon_image->icon_image)) { - $this->list->setHasIconImages(true); - } - } - - abstract protected function createSubject(); - - abstract protected function getStateBallSize(): string; - - /** - * @return ?BaseHtmlElement - */ - protected function createIconImage(): ?BaseHtmlElement - { - if (! $this->list->hasIconImages()) { - return null; - } - - $iconImage = HtmlElement::create('div', [ - 'class' => 'icon-image', - ]); - - $this->assembleIconImage($iconImage); - - return $iconImage; - } - - protected function assembleCaption(BaseHtmlElement $caption): void - { - if ($this->state->soft_state === null && $this->state->output === null) { - $caption->addHtml(Text::create($this->translate('Waiting for Icinga DB to synchronize the state.'))); - } else { - if (empty($this->state->output)) { - $pluginOutput = new EmptyState($this->translate('Output unavailable.')); - } else { - $pluginOutput = new PluginOutputContainer(PluginOutput::fromObject($this->item)); - } - - $caption->addHtml($pluginOutput); - } - } - - protected function assembleIconImage(BaseHtmlElement $iconImage): void - { - if (isset($this->item->icon_image->icon_image)) { - $iconImage->addHtml(new IconImage($this->item->icon_image->icon_image, $this->item->icon_image_alt)); - } else { - $iconImage->addAttributes(['class' => 'placeholder']); - } - } - - protected function assembleTitle(BaseHtmlElement $title): void - { - $title->addHtml(Html::sprintf( - $this->translate('%s is %s', ' is '), - $this->createSubject(), - Html::tag('span', ['class' => 'state-text'], $this->state->getStateTextTranslated()) - )); - - if (isset($this->state->affects_children) && $this->state->affects_children) { - $total = (int) $this->item->total_children; - - if ($total > 1000) { - $total = '1000+'; - $tooltip = $this->translate('Up to 1000+ affected objects'); - } else { - $tooltip = sprintf( - $this->translatePlural( - '%d affected object', - 'Up to %d affected objects', - $total - ), - $total - ); - } - - $icon = new Icon(Icons::UNREACHABLE); - - $title->addHtml(new HtmlElement( - 'span', - Attributes::create([ - 'class' => 'affected-objects', - 'title' => $tooltip - ]), - $icon, - Text::create($total) - )); - } - } - - protected function assembleVisual(BaseHtmlElement $visual): void - { - $stateBall = new StateBall($this->state->getStateText(), $this->getStateBallSize()); - $stateBall->add($this->state->getIcon()); - if ($this->state->is_problem && ($this->state->is_handled || ! $this->state->is_reachable)) { - $stateBall->getAttributes()->add('class', 'handled'); - } - - $visual->addHtml($stateBall); - if ($this->state->state_type === 'soft') { - $visual->addHtml( - new CheckAttempt((int) $this->state->check_attempt, (int) $this->item->max_check_attempts) - ); - } - } - - protected function createTimestamp(): ?BaseHtmlElement - { - $since = null; - if ($this->state->is_overdue) { - $since = new TimeSince($this->state->next_update->getTimestamp()); - $since->prepend($this->translate('Overdue') . ' '); - $since->prependHtml(new Icon(Icons::WARNING)); - } elseif ($this->state->last_state_change !== null && $this->state->last_state_change->getTimestamp() > 0) { - $since = new TimeSince($this->state->last_state_change->getTimestamp()); - } - - return $since; - } - - protected function assemble(): void - { - if ($this->state->is_overdue) { - $this->addAttributes(['class' => 'overdue']); - } - - $this->add([ - $this->createVisual(), - $this->createIconImage(), - $this->createMain() - ]); - } -} diff --git a/public/css/common.less b/public/css/common.less index 3a6b5d13..b5f17b1a 100644 --- a/public/css/common.less +++ b/public/css/common.less @@ -384,7 +384,8 @@ div.show-more { } .history-list, -.objectHeader { +.header-item-layout.host, +.header-item-layout.service { .visual.small-state-change .state-change { padding-top: .25em; } diff --git a/public/css/list/item-list.less b/public/css/list/item-list.less index aead7cde..256ca42f 100644 --- a/public/css/list/item-list.less +++ b/public/css/list/item-list.less @@ -56,10 +56,6 @@ > .empty-state { padding: .25em; } - - .check-attempt { - display: none; // TODO: The new renderer shouldn't render it in the first place - } } .item-list.minimal { @@ -143,7 +139,8 @@ // TODO: Can be simplified (`.controls .list-item`) once the only list-items in controls are those in the multi select views .controls .item-list:not(.detailed):not(.minimal) .list-item { - .plugin-output { + .plugin-output { //TODO: why ?? multiselect items has bigger gap now + //seems dead code, remove it when cleaning up line-height: 1.5 }