From 892f40f9d06fc296bfc55788a45ac7756a5a7013 Mon Sep 17 00:00:00 2001 From: Sukhwinder Dhillon Date: Mon, 2 Dec 2024 13:04:51 +0100 Subject: [PATCH] Introduce (Host|Service)Header class - Use this class to create object header for host and service - Remove previous implementation - Add required changes to `object-header.less` - StateList: Remove now unused NoSubjectLink wip --- application/controllers/HostController.php | 22 +-- application/controllers/ServiceController.php | 18 +- .../Detail/BaseHostAndServiceHeader.php | 51 ++++++ .../Widget/Detail/BaseObjectHeader.php | 23 --- .../Detail/HostAndServiceHeaderUtils.php | 167 ++++++++++++++++++ .../HostHeader.php} | 25 +-- .../Widget/Detail/RedundancyGroupHeader.php | 44 ++--- .../Detail/RedundancyGroupHeaderUtils.php | 74 ++++++++ .../ServiceHeader.php} | 41 +++-- .../Widget/ItemList/BaseHostListItem.php | 16 +- .../Widget/ItemList/BaseServiceListItem.php | 18 +- library/Icingadb/Widget/ItemList/HostList.php | 2 - .../ItemList/RedundancyGroupListItem.php | 58 ++---- .../Icingadb/Widget/ItemList/ServiceList.php | 2 - .../Icingadb/Widget/ItemList/StateList.php | 2 - .../Widget/ItemList/StateListItem.php | 128 ++------------ public/css/common.less | 3 +- public/css/widget/object-header.less | 51 +++++- 18 files changed, 435 insertions(+), 310 deletions(-) create mode 100644 library/Icingadb/Widget/Detail/BaseHostAndServiceHeader.php create mode 100644 library/Icingadb/Widget/Detail/HostAndServiceHeaderUtils.php rename library/Icingadb/Widget/{ItemList/HostDetailHeader.php => Detail/HostHeader.php} (76%) create mode 100644 library/Icingadb/Widget/Detail/RedundancyGroupHeaderUtils.php rename library/Icingadb/Widget/{ItemList/ServiceDetailHeader.php => Detail/ServiceHeader.php} (61%) diff --git a/application/controllers/HostController.php b/application/controllers/HostController.php index 259dd33f0..d5dfbb608 100644 --- a/application/controllers/HostController.php +++ b/application/controllers/HostController.php @@ -19,10 +19,10 @@ use Icinga\Module\Icingadb\Redis\VolatileStateResults; use Icinga\Module\Icingadb\Web\Controller; use Icinga\Module\Icingadb\Widget\Detail\HostDetail; +use Icinga\Module\Icingadb\Widget\Detail\HostHeader; 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\HostList; use Icinga\Module\Icingadb\Widget\ItemList\HistoryList; use Icinga\Module\Icingadb\Widget\ItemList\ServiceList; use ipl\Stdlib\Filter; @@ -57,6 +57,8 @@ public function init() $this->host = $host; $this->loadTabsForObject($host); + $this->addControl(new HostHeader($this->host)); + $this->setTitleTab($this->getRequest()->getActionName()); $this->setTitle($host->display_name); } @@ -72,10 +74,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 +95,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 +152,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 +211,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); @@ -288,6 +274,6 @@ protected function getCommandTargetsUrl(): Url protected function getDefaultTabControls(): array { - return [(new HostList([$this->host]))->setDetailActionsDisabled()->setNoSubjectLink()]; + return [(new HostHeader($this->host))]; } } diff --git a/application/controllers/ServiceController.php b/application/controllers/ServiceController.php index 2857718ae..c88430aa6 100644 --- a/application/controllers/ServiceController.php +++ b/application/controllers/ServiceController.php @@ -18,10 +18,10 @@ 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\ServiceHeader; use Icinga\Module\Icingadb\Widget\Detail\ServiceInspectionDetail; use Icinga\Module\Icingadb\Widget\Detail\ServiceMetaInfo; use Icinga\Module\Icingadb\Widget\ItemList\HistoryList; -use Icinga\Module\Icingadb\Widget\ItemList\ServiceList; use ipl\Stdlib\Filter; use ipl\Web\Url; @@ -65,6 +65,8 @@ public function init() $this->service = $service; $this->loadTabsForObject($service); + $this->addControl(new ServiceHeader($this->service)); + $this->setTitleTab($this->getRequest()->getActionName()); $this->setTitle( t('%s on %s', ' on '), @@ -79,10 +81,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)); @@ -104,10 +102,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) @@ -166,10 +160,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); @@ -242,6 +232,6 @@ protected function getCommandTargetsUrl(): Url protected function getDefaultTabControls(): array { - return [(new ServiceList([$this->service]))->setDetailActionsDisabled()->setNoSubjectLink()]; + return [new ServiceHeader($this->service)]; } } diff --git a/library/Icingadb/Widget/Detail/BaseHostAndServiceHeader.php b/library/Icingadb/Widget/Detail/BaseHostAndServiceHeader.php new file mode 100644 index 000000000..36d870ea9 --- /dev/null +++ b/library/Icingadb/Widget/Detail/BaseHostAndServiceHeader.php @@ -0,0 +1,51 @@ +object; + } + + protected function getSubject(): ValidHtml + { + return new HtmlElement( + 'span', + Attributes::create(['class' => 'subject']), + Text::create($this->object->display_name) + ); + } + + protected function getStateBallSize(): string + { + return ''; // not required because the parent class overrides the assembleVisual method + } + + protected function wantIconImage(): bool + { + return isset($this->object->icon_image->icon_image); + } + + protected function assemble(): void + { + $this->add([ + $this->createVisual(), + $this->createIconImage(), + $this->createMain() + ]); + } +} diff --git a/library/Icingadb/Widget/Detail/BaseObjectHeader.php b/library/Icingadb/Widget/Detail/BaseObjectHeader.php index 6f5b96384..1149e0a40 100644 --- a/library/Icingadb/Widget/Detail/BaseObjectHeader.php +++ b/library/Icingadb/Widget/Detail/BaseObjectHeader.php @@ -7,11 +7,8 @@ use ipl\Html\Attributes; use ipl\Html\BaseHtmlElement; use ipl\Html\HtmlElement; -use ipl\Html\Text; use ipl\I18n\Translation; use ipl\Orm\Model; -use ipl\Web\Widget\StateBall; -use ipl\Web\Widget\TimeSince; abstract class BaseObjectHeader extends BaseHtmlElement { @@ -55,11 +52,6 @@ protected function assembleVisual(BaseHtmlElement $visual): void { } - protected function getStateBallSize(): string - { - return StateBall::SIZE_BIG; - } - protected function createCaption(): BaseHtmlElement { $caption = new HtmlElement('section', Attributes::create(['class' => 'caption'])); @@ -87,21 +79,6 @@ protected function createMain(): BaseHtmlElement return $main; } - protected function createTimestamp(): ?BaseHtmlElement - { - //TODO: add support for host/service - return new TimeSince($this->object->state->last_state_change->getTimestamp()); - } - - protected function createSubject(): BaseHtmlElement - { - return new HtmlElement( - 'span', - Attributes::create(['class' => 'subject']), - Text::create($this->object->display_name) - ); - } - protected function createTitle(): BaseHtmlElement { $title = new HtmlElement('div', Attributes::create(['class' => 'title'])); diff --git a/library/Icingadb/Widget/Detail/HostAndServiceHeaderUtils.php b/library/Icingadb/Widget/Detail/HostAndServiceHeaderUtils.php new file mode 100644 index 000000000..941202031 --- /dev/null +++ b/library/Icingadb/Widget/Detail/HostAndServiceHeaderUtils.php @@ -0,0 +1,167 @@ +getObject(); + $state = $object->state; + + $stateBall = new StateBall($state->getStateText(), $this->getStateBallSize()); + $stateBall->add($state->getIcon()); + if ($state->is_problem && ($state->is_handled || ! $state->is_reachable)) { + $stateBall->getAttributes()->add('class', 'handled'); + } + + $visual->addHtml($stateBall); + if ($state->state_type === 'soft') { + $visual->addHtml( + new CheckAttempt((int) $state->check_attempt, (int) $object->max_check_attempts) + ); + } + } + + protected function assembleTitle(BaseHtmlElement $title): void + { + $object = $this->getObject(); + $state = $object->state; + $title->addHtml(Html::sprintf( + $this->translate('%s is %s', ' is '), + $this->getSubject(), + Html::tag('span', ['class' => 'state-text'], $state->getStateTextTranslated()) + )); + + if ($state->affects_children) { + $total = (int) $object->affected_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 assembleCaption(BaseHtmlElement $caption): void + { + $object = $this->getObject(); + $state = $object->state; + if ($state->soft_state === null && $state->output === null) { + $caption->addHtml(Text::create($this->translate('Waiting for Icinga DB to synchronize the state.'))); + } else { + if (empty($state->output)) { + $pluginOutput = new EmptyState($this->translate('Output unavailable.')); + } else { + $pluginOutput = new PluginOutputContainer(PluginOutput::fromObject($object)); + } + + $caption->addHtml($pluginOutput); + } + } + + protected function createTimestamp(): ?BaseHtmlElement + { + $state = $this->getObject()->state; + $since = null; + if ($state->is_overdue) { + $since = new TimeSince($state->next_update->getTimestamp()); + $since->prepend($this->translate('Overdue') . ' '); + $since->prependHtml(new Icon(Icons::WARNING)); + } elseif ($state->last_state_change !== null && $state->last_state_change->getTimestamp() > 0) { + $since = new TimeSince($state->last_state_change->getTimestamp()); + } + + return $since; + } + + protected function createIconImage(): ?BaseHtmlElement + { + if (! $this->wantIconImage()) { + return null; + } + + $iconImage = HtmlElement::create('div', [ + 'class' => 'icon-image' + ]); + + $object = $this->getObject(); + if (isset($object->icon_image->icon_image)) { + $iconImage->addHtml(new IconImage($object->icon_image->icon_image, $object->icon_image_alt)); + } else { + $iconImage->addAttributes(['class' => 'placeholder']); + } + + return $iconImage; + } +} diff --git a/library/Icingadb/Widget/ItemList/HostDetailHeader.php b/library/Icingadb/Widget/Detail/HostHeader.php similarity index 76% rename from library/Icingadb/Widget/ItemList/HostDetailHeader.php rename to library/Icingadb/Widget/Detail/HostHeader.php index b7afb2327..103b976cc 100644 --- a/library/Icingadb/Widget/ItemList/HostDetailHeader.php +++ b/library/Icingadb/Widget/Detail/HostHeader.php @@ -1,21 +1,20 @@ state->state_type === 'soft') { @@ -56,14 +55,4 @@ protected function assembleVisual(BaseHtmlElement $visual): void $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/Detail/RedundancyGroupHeader.php b/library/Icingadb/Widget/Detail/RedundancyGroupHeader.php index 7ccd94abb..8eec68b44 100644 --- a/library/Icingadb/Widget/Detail/RedundancyGroupHeader.php +++ b/library/Icingadb/Widget/Detail/RedundancyGroupHeader.php @@ -4,13 +4,14 @@ namespace Icinga\Module\Icingadb\Widget\Detail; +use Icinga\Module\Icingadb\Common\ListItemMinimalLayout; use Icinga\Module\Icingadb\Model\RedundancyGroup; -use Icinga\Module\Icingadb\Model\RedundancyGroupState; use Icinga\Module\Icingadb\Model\RedundancyGroupSummary; -use Icinga\Module\Icingadb\Widget\DependencyNodeStatistics; -use ipl\Html\BaseHtmlElement; +use ipl\Html\Attributes; use ipl\Html\HtmlElement; use ipl\Html\Text; +use ipl\Html\ValidHtml; +use ipl\Orm\Model; use ipl\Web\Widget\StateBall; /** @@ -18,6 +19,9 @@ */ class RedundancyGroupHeader extends BaseObjectHeader { + use RedundancyGroupHeaderUtils; + use ListItemMinimalLayout; + /** @var RedundancyGroupSummary */ protected $summary; @@ -28,37 +32,27 @@ public function __construct(RedundancyGroup $object, RedundancyGroupSummary $sum parent::__construct($object); } - protected function assembleVisual(BaseHtmlElement $visual): void + protected function getObject(): Model { - $visual->addHtml(new StateBall($this->object->state->getStateText(), $this->getStateBallSize())); - } - - protected function assembleTitle(BaseHtmlElement $title): void - { - $title->addHtml($this->createSubject()); - if ($this->object->state->failed) { - $text = $this->translate('has no working objects'); - } else { - $text = $this->translate('has working objects'); - } - - $title->addHtml(HtmlElement::create('span', null, Text::create($text))); + return $this->object; } - protected function createStatistics(): BaseHtmlElement + protected function getSummary(): Model { - return new DependencyNodeStatistics($this->summary); + return $this->summary; } - protected function assembleHeader(BaseHtmlElement $header): void + protected function getSubject(): ValidHtml { - $header->add($this->createTitle()); - $header->add($this->createStatistics()); - $header->add($this->createTimestamp()); + return new HtmlElement( + 'span', + Attributes::create(['class' => 'subject']), + Text::create($this->object->display_name) + ); } - protected function assembleMain(BaseHtmlElement $main): void + protected function getStateBallSize(): string { - $main->add($this->createHeader()); + return StateBall::SIZE_BIG; } } diff --git a/library/Icingadb/Widget/Detail/RedundancyGroupHeaderUtils.php b/library/Icingadb/Widget/Detail/RedundancyGroupHeaderUtils.php new file mode 100644 index 000000000..96beffc62 --- /dev/null +++ b/library/Icingadb/Widget/Detail/RedundancyGroupHeaderUtils.php @@ -0,0 +1,74 @@ +addHtml(new StateBall($this->getObject()->state->getStateText(), $this->getStateBallSize())); + } + + protected function assembleTitle(BaseHtmlElement $title): void + { + $title->addHtml($this->getSubject()); + if ($this->getObject()->state->failed) { + $text = $this->translate('has no working objects'); + } else { + $text = $this->translate('has working objects'); + } + + $title->addHtml(HtmlElement::create('span', null, Text::create($text))); + } + + protected function assembleCaption(BaseHtmlElement $caption): void + { + $caption->addHtml(new DependencyNodeStatistics($this->getSummary())); + } + + protected function createTimestamp(): BaseHtmlElement + { + return new TimeSince($this->getObject()->state->last_state_change->getTimestamp()); + } +} diff --git a/library/Icingadb/Widget/ItemList/ServiceDetailHeader.php b/library/Icingadb/Widget/Detail/ServiceHeader.php similarity index 61% rename from library/Icingadb/Widget/ItemList/ServiceDetailHeader.php rename to library/Icingadb/Widget/Detail/ServiceHeader.php index 8c5094d73..b87f9d01d 100644 --- a/library/Icingadb/Widget/ItemList/ServiceDetailHeader.php +++ b/library/Icingadb/Widget/Detail/ServiceHeader.php @@ -1,21 +1,29 @@ state->state_type === 'soft') { @@ -57,13 +65,18 @@ protected function assembleVisual(BaseHtmlElement $visual): void $visual->addHtml($stateChange); } - protected function assemble(): void + protected function getSubject(): ValidHtml { - $attributes = $this->list->getAttributes(); - if (! in_array('minimal', $attributes->get('class')->getValue())) { - $attributes->add('class', 'minimal'); - } + $service = $this->object->display_name; + $host = [ + new StateBall($this->object->host->state->getStateText(), StateBall::SIZE_MEDIUM), + ' ', + $this->object->host->display_name + ]; + + $host = new Link($host, Links::host($this->object->host), ['class' => 'subject']); + $service = new HtmlElement('span', Attributes::create(['class' => 'subject']), Text::create($service)); - parent::assemble(); + return Html::sprintf(t('%s on %s', ' on '), $service, $host); } } diff --git a/library/Icingadb/Widget/ItemList/BaseHostListItem.php b/library/Icingadb/Widget/ItemList/BaseHostListItem.php index edaf6c888..734d0a89b 100644 --- a/library/Icingadb/Widget/ItemList/BaseHostListItem.php +++ b/library/Icingadb/Widget/ItemList/BaseHostListItem.php @@ -22,8 +22,6 @@ */ abstract class BaseHostListItem extends StateListItem { - use NoSubjectLink; - /** * Create new subject link * @@ -31,25 +29,13 @@ abstract class BaseHostListItem extends StateListItem */ protected function createSubject() { - if ($this->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']); - } + 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 index fe4f01462..831059291 100644 --- a/library/Icingadb/Widget/ItemList/BaseServiceListItem.php +++ b/library/Icingadb/Widget/ItemList/BaseServiceListItem.php @@ -5,12 +5,8 @@ namespace Icinga\Module\Icingadb\Widget\ItemList; use Icinga\Module\Icingadb\Common\Links; -use Icinga\Module\Icingadb\Common\NoSubjectLink; use Icinga\Module\Icingadb\Model\Service; -use ipl\Html\Attributes; use ipl\Html\Html; -use ipl\Html\HtmlElement; -use ipl\Html\Text; use ipl\Stdlib\Filter; use ipl\Web\Widget\Link; use ipl\Web\Widget\StateBall; @@ -23,8 +19,6 @@ */ abstract class BaseServiceListItem extends StateListItem { - use NoSubjectLink; - protected function createSubject() { $service = $this->item->display_name; @@ -35,23 +29,15 @@ protected function createSubject() ]; $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']); - } + $service = new Link($service, Links::service($this->item, $this->item->host), ['class' => 'subject']); - return [Html::sprintf(t('%s on %s', ' on '), $service, $host)]; + 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( diff --git a/library/Icingadb/Widget/ItemList/HostList.php b/library/Icingadb/Widget/ItemList/HostList.php index 2be1f84a7..4a74b302b 100644 --- a/library/Icingadb/Widget/ItemList/HostList.php +++ b/library/Icingadb/Widget/ItemList/HostList.php @@ -23,8 +23,6 @@ protected function getItemClass(): string $this->removeAttribute('class', 'default-layout'); return HostListItemDetailed::class; - case 'objectHeader': - return HostDetailHeader::class; default: return HostListItem::class; } diff --git a/library/Icingadb/Widget/ItemList/RedundancyGroupListItem.php b/library/Icingadb/Widget/ItemList/RedundancyGroupListItem.php index eca3c9271..47543a437 100644 --- a/library/Icingadb/Widget/ItemList/RedundancyGroupListItem.php +++ b/library/Icingadb/Widget/ItemList/RedundancyGroupListItem.php @@ -8,17 +8,16 @@ use Icinga\Module\Icingadb\Common\Database; use Icinga\Module\Icingadb\Common\ListItemCommonLayout; use Icinga\Module\Icingadb\Model\RedundancyGroup; -use Icinga\Module\Icingadb\Model\RedundancyGroupSummary; use Icinga\Module\Icingadb\Model\RedundancyGroupState; -use Icinga\Module\Icingadb\Widget\DependencyNodeStatistics; -use ipl\Html\BaseHtmlElement; +use Icinga\Module\Icingadb\Model\RedundancyGroupSummary; +use Icinga\Module\Icingadb\Widget\Detail\RedundancyGroupHeaderUtils; +use ipl\Html\ValidHtml; +use ipl\Orm\Model; use ipl\Stdlib\Filter; +use ipl\Web\Common\BaseListItem; use ipl\Web\Url; use ipl\Web\Widget\Link; use ipl\Web\Widget\StateBall; -use ipl\Html\HtmlElement; -use ipl\Html\Text; -use ipl\Web\Widget\TimeSince; /** * Redundancy group list item. Represents one database row. @@ -26,32 +25,36 @@ * @property RedundancyGroup $item * @property RedundancyGroupState $state */ -class RedundancyGroupListItem extends StateListItem +class RedundancyGroupListItem extends BaseListItem { use ListItemCommonLayout; use Database; use Auth; + use RedundancyGroupHeaderUtils; protected $defaultAttributes = ['class' => ['redundancy-group-list-item']]; protected function init(): void { - parent::init(); - $this->addAttributes(['data-action-item' => true]); } - protected function getStateBallSize(): string + protected function getObject(): Model { - return StateBall::SIZE_LARGE; + return $this->item; } - protected function createTimestamp(): BaseHtmlElement + protected function getSummary(): Model { - return new TimeSince($this->state->last_state_change->getTimestamp()); + $summary = RedundancyGroupSummary::on($this->getDb()) + ->filter(Filter::equal('id', $this->item->id)); + + $this->applyRestrictions($summary); + + return $summary->first(); } - protected function createSubject(): Link + protected function getSubject(): ValidHtml { return new Link( $this->item->display_name, @@ -60,38 +63,15 @@ protected function createSubject(): Link ); } - protected function assembleVisual(BaseHtmlElement $visual): void - { - $visual->addHtml(new StateBall($this->state->getStateText(), $this->getStateBallSize())); - } - - protected function assembleCaption(BaseHtmlElement $caption): void - { - $summary = RedundancyGroupSummary::on($this->getDb()) - ->filter(Filter::equal('id', $this->item->id)); - - $this->applyRestrictions($summary); - - $caption->addHtml(new DependencyNodeStatistics($summary->first())); - } - - protected function assembleTitle(BaseHtmlElement $title): void + protected function getStateBallSize(): string { - $title->addHtml($this->createSubject()); - if ($this->state->failed) { - $text = $this->translate('has no working objects'); - } else { - $text = $this->translate('has working objects'); - } - - $title->addHtml(HtmlElement::create('span', null, Text::create($text))); + return StateBall::SIZE_LARGE; } protected function assemble(): void { $this->add([ $this->createVisual(), - $this->createIconImage(), $this->createMain() ]); } diff --git a/library/Icingadb/Widget/ItemList/ServiceList.php b/library/Icingadb/Widget/ItemList/ServiceList.php index 8d41a701b..83e76f3c3 100644 --- a/library/Icingadb/Widget/ItemList/ServiceList.php +++ b/library/Icingadb/Widget/ItemList/ServiceList.php @@ -20,8 +20,6 @@ protected function getItemClass(): string $this->removeAttribute('class', 'default-layout'); return ServiceListItemDetailed::class; - case 'objectHeader': - return ServiceDetailHeader::class; default: return ServiceListItem::class; } diff --git a/library/Icingadb/Widget/ItemList/StateList.php b/library/Icingadb/Widget/ItemList/StateList.php index 9671c9722..1f0b525ea 100644 --- a/library/Icingadb/Widget/ItemList/StateList.php +++ b/library/Icingadb/Widget/ItemList/StateList.php @@ -5,7 +5,6 @@ namespace Icinga\Module\Icingadb\Widget\ItemList; use Icinga\Module\Icingadb\Common\DetailActions; -use Icinga\Module\Icingadb\Common\NoSubjectLink; use Icinga\Module\Icingadb\Common\ViewMode; use Icinga\Module\Icingadb\Redis\VolatileStateResults; use Icinga\Module\Icingadb\Widget\Notice; @@ -15,7 +14,6 @@ abstract class StateList extends BaseItemList { use ViewMode; - use NoSubjectLink; use DetailActions; /** @var bool Whether the list contains at least one item with an icon_image */ diff --git a/library/Icingadb/Widget/ItemList/StateListItem.php b/library/Icingadb/Widget/ItemList/StateListItem.php index f8704d8a6..f48295825 100644 --- a/library/Icingadb/Widget/ItemList/StateListItem.php +++ b/library/Icingadb/Widget/ItemList/StateListItem.php @@ -4,23 +4,12 @@ namespace Icinga\Module\Icingadb\Widget\ItemList; -use Icinga\Module\Icingadb\Common\Icons; use Icinga\Module\Icingadb\Model\State; -use Icinga\Module\Icingadb\Util\PluginOutput; -use Icinga\Module\Icingadb\Widget\CheckAttempt; -use Icinga\Module\Icingadb\Widget\IconImage; -use Icinga\Module\Icingadb\Widget\PluginOutputContainer; -use ipl\Html\Attributes; -use ipl\Html\HtmlElement; +use Icinga\Module\Icingadb\Widget\Detail\HostAndServiceHeaderUtils; +use ipl\Html\ValidHtml; use ipl\I18n\Translation; +use ipl\Orm\Model; use ipl\Web\Common\BaseListItem; -use ipl\Web\Widget\EmptyState; -use ipl\Web\Widget\TimeSince; -use ipl\Html\BaseHtmlElement; -use ipl\Html\Html; -use ipl\Html\Text; -use ipl\Web\Widget\Icon; -use ipl\Web\Widget\StateBall; /** * Host or service item of a host or service list. Represents one database row. @@ -28,6 +17,7 @@ abstract class StateListItem extends BaseListItem { use Translation; + use HostAndServiceHeaderUtils; /** @var StateList The list where the item is part of */ protected $list; @@ -46,117 +36,19 @@ protected function init(): void 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 + protected function getObject(): Model { - $title->addHtml(Html::sprintf( - $this->translate('%s is %s', ' is '), - $this->createSubject(), - Html::tag('span', ['class' => 'state-text'], $this->state->getStateTextTranslated()) - )); - - if ($this->state->affects_children) { - $total = (int) $this->item->affected_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) - )); - } + return $this->item; } - protected function assembleVisual(BaseHtmlElement $visual): void + protected function getSubject(): ValidHtml { - $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) - ); - } + return $this->createSubject(); } - protected function createTimestamp(): ?BaseHtmlElement + protected function wantIconImage(): bool { - $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; + return $this->list->hasIconImages(); } protected function assemble(): void diff --git a/public/css/common.less b/public/css/common.less index 3a28d03b0..2f4ac839a 100644 --- a/public/css/common.less +++ b/public/css/common.less @@ -383,8 +383,7 @@ div.show-more { } } -.history-list, -.objectHeader { +.history-list { .visual.small-state-change .state-change { padding-top: .25em; } diff --git a/public/css/widget/object-header.less b/public/css/widget/object-header.less index 9c6be51a8..9cd2f9be3 100644 --- a/public/css/widget/object-header.less +++ b/public/css/widget/object-header.less @@ -4,13 +4,22 @@ .visual { display: flex; - padding: 0.5em 0; + padding: 0.25em 0; align-items: center; } + .icon-image { //todo: center the image + height: 2em; + width: 2em; + line-height: 2; + text-align: center; + margin-right: .5em; + overflow: hidden; + } + .main { flex: 1 1 auto; - padding: 0.5em 0; + padding: 0.25em 0; width: 0; margin-left: .5em; @@ -27,11 +36,49 @@ & > * { margin: 0 .28125em; // 0 calculated   width + + &:first-child { + margin-left: 0; + } + + &:last-child { + margin-right: 0; + } } .subject { .text-ellipsis(); } + + .state-text { + color: @text-color; + text-transform: uppercase; + } + } + + .caption { + flex: 1 1 auto; + height: 1.5em; + margin-right: 1em; + width: 0; + + .line-clamp("reset"); + + font-size: 11/12em; + line-height: 1.5*12/11em; + + .object-statistics { //TODO: not working, fix it + margin-left: auto; + + &:not(:last-child) { + margin-right: 1em; + } + } + } + + .caption, + .caption .plugin-output { + .text-ellipsis(); } time {