diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:44:46 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:44:46 +0000 |
commit | b18bc644404e02b57635bfcc8258e85abb141146 (patch) | |
tree | 686512eacb2dba0055277ef7ec2f28695b3418ea /library/Icingadb/Widget/ItemList | |
parent | Initial commit. (diff) | |
download | icingadb-web-b18bc644404e02b57635bfcc8258e85abb141146.tar.xz icingadb-web-b18bc644404e02b57635bfcc8258e85abb141146.zip |
Adding upstream version 1.1.1.upstream/1.1.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'library/Icingadb/Widget/ItemList')
35 files changed, 2310 insertions, 0 deletions
diff --git a/library/Icingadb/Widget/ItemList/BaseCommentListItem.php b/library/Icingadb/Widget/ItemList/BaseCommentListItem.php new file mode 100644 index 0000000..de11c0c --- /dev/null +++ b/library/Icingadb/Widget/ItemList/BaseCommentListItem.php @@ -0,0 +1,131 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\NoSubjectLink; +use Icinga\Module\Icingadb\Common\ObjectLinkDisabled; +use Icinga\Module\Icingadb\Common\TicketLinks; +use ipl\Html\Html; +use Icinga\Module\Icingadb\Common\HostLink; +use Icinga\Module\Icingadb\Common\Icons; +use Icinga\Module\Icingadb\Common\Links; +use Icinga\Module\Icingadb\Widget\MarkdownLine; +use Icinga\Module\Icingadb\Common\ServiceLink; +use Icinga\Module\Icingadb\Model\Comment; +use ipl\Html\FormattedString; +use ipl\Web\Common\BaseListItem; +use ipl\Web\Widget\TimeAgo; +use ipl\Html\Attributes; +use ipl\Html\BaseHtmlElement; +use ipl\Html\HtmlElement; +use ipl\Html\Text; +use ipl\Stdlib\Filter; +use ipl\Web\Widget\Icon; +use ipl\Web\Widget\Link; +use ipl\Web\Widget\TimeUntil; + +/** + * Comment item of a comment list. Represents one database row. + * + * @property Comment $item + * @property CommentList $list + */ +abstract class BaseCommentListItem extends BaseListItem +{ + use HostLink; + use ServiceLink; + use NoSubjectLink; + use ObjectLinkDisabled; + use TicketLinks; + + protected function assembleCaption(BaseHtmlElement $caption): void + { + $markdownLine = new MarkdownLine($this->createTicketLinks($this->item->text)); + + $caption->getAttributes()->add($markdownLine->getAttributes()); + $caption->addFrom($markdownLine); + } + + protected function assembleTitle(BaseHtmlElement $title): void + { + $isAck = $this->item->entry_type === 'ack'; + $expires = $this->item->expire_time; + + $subjectText = sprintf( + $isAck ? t('%s acknowledged', '<username>..') : t('%s commented', '<username>..'), + $this->item->author + ); + + $headerParts = [ + new Icon(Icons::USER), + $this->getNoSubjectLink() + ? new HtmlElement('span', Attributes::create(['class' => 'subject']), Text::create($subjectText)) + : new Link($subjectText, Links::comment($this->item), ['class' => 'subject']) + ]; + + if ($isAck) { + $label = [Text::create('ack')]; + + if ($this->item->is_persistent) { + array_unshift($label, new Icon(Icons::IS_PERSISTENT)); + } + + $headerParts[] = Text::create(' '); + $headerParts[] = new HtmlElement('span', Attributes::create(['class' => 'ack-badge badge']), ...$label); + } + + if ($expires !== null) { + $headerParts[] = Text::create(' '); + $headerParts[] = new HtmlElement( + 'span', + Attributes::create(['class' => 'ack-badge badge']), + Text::create(t('EXPIRES')) + ); + } + + if ($this->getObjectLinkDisabled()) { + // pass + } elseif ($this->item->object_type === 'host') { + $headerParts[] = $this->createHostLink($this->item->host, true); + } else { + $headerParts[] = $this->createServiceLink($this->item->service, $this->item->service->host, true); + } + + $title->addHtml(...$headerParts); + } + + protected function assembleVisual(BaseHtmlElement $visual): void + { + $visual->addHtml(new HtmlElement( + 'div', + Attributes::create(['class' => 'user-ball']), + Text::create($this->item->author[0]) + )); + } + + protected function createTimestamp(): ?BaseHtmlElement + { + if ($this->item->expire_time) { + return Html::tag( + 'span', + FormattedString::create(t("expires %s"), new TimeUntil($this->item->expire_time->getTimestamp())) + ); + } + + return Html::tag( + 'span', + FormattedString::create(t("created %s"), new TimeAgo($this->item->entry_time->getTimestamp())) + ); + } + + protected function init(): void + { + $this->setTicketLinkEnabled($this->list->getTicketLinkEnabled()); + $this->list->addDetailFilterAttribute($this, Filter::equal('name', $this->item->name)); + $this->list->addMultiselectFilterAttribute($this, Filter::equal('name', $this->item->name)); + $this->setObjectLinkDisabled($this->list->getObjectLinkDisabled()); + $this->setNoSubjectLink($this->list->getNoSubjectLink()); + } +} diff --git a/library/Icingadb/Widget/ItemList/BaseDowntimeListItem.php b/library/Icingadb/Widget/ItemList/BaseDowntimeListItem.php new file mode 100644 index 0000000..7ebc1f6 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/BaseDowntimeListItem.php @@ -0,0 +1,212 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Date\DateFormatter; +use Icinga\Module\Icingadb\Common\HostLink; +use Icinga\Module\Icingadb\Common\Icons; +use Icinga\Module\Icingadb\Common\Links; +use Icinga\Module\Icingadb\Common\NoSubjectLink; +use Icinga\Module\Icingadb\Common\ObjectLinkDisabled; +use Icinga\Module\Icingadb\Common\ServiceLink; +use Icinga\Module\Icingadb\Common\TicketLinks; +use Icinga\Module\Icingadb\Model\Downtime; +use Icinga\Module\Icingadb\Widget\MarkdownLine; +use ipl\Html\Attributes; +use ipl\Html\BaseHtmlElement; +use ipl\Html\Html; +use ipl\Html\HtmlElement; +use ipl\Html\TemplateString; +use ipl\Html\Text; +use ipl\Stdlib\Filter; +use ipl\Web\Common\BaseListItem; +use ipl\Web\Widget\Icon; +use ipl\Web\Widget\Link; + +/** + * Downtime item of a downtime list. Represents one database row. + * + * @property Downtime $item + * @property DowntimeList $list + */ +abstract class BaseDowntimeListItem extends BaseListItem +{ + use HostLink; + use ServiceLink; + use NoSubjectLink; + use ObjectLinkDisabled; + use TicketLinks; + + /** @var int Current Time */ + protected $currentTime; + + /** @var int Duration */ + protected $duration; + + /** @var int Downtime end time */ + protected $endTime; + + /** @var bool Whether the downtime is active */ + protected $isActive; + + /** @var int Downtime start time */ + protected $startTime; + + protected function init(): void + { + if ($this->item->is_flexible && $this->item->is_in_effect) { + $this->startTime = $this->item->start_time->getTimestamp(); + $this->endTime = $this->item->end_time->getTimestamp(); + } else { + $this->startTime = $this->item->scheduled_start_time->getTimestamp(); + $this->endTime = $this->item->scheduled_end_time->getTimestamp(); + } + + $this->currentTime = time(); + + $this->isActive = $this->item->is_in_effect + || $this->item->is_flexible && $this->item->scheduled_start_time->getTimestamp() <= $this->currentTime; + + $until = ($this->isActive ? $this->endTime : $this->startTime) - $this->currentTime; + $this->duration = explode(' ', DateFormatter::formatDuration( + $until <= 3600 ? $until : $until + (3600 - ((int) $until % 3600)) + ), 2)[0]; + + $this->list->addDetailFilterAttribute($this, Filter::equal('name', $this->item->name)); + $this->list->addMultiselectFilterAttribute($this, Filter::equal('name', $this->item->name)); + $this->setObjectLinkDisabled($this->list->getObjectLinkDisabled()); + $this->setNoSubjectLink($this->list->getNoSubjectLink()); + $this->setTicketLinkEnabled($this->list->getTicketLinkEnabled()); + + if ($this->item->is_in_effect) { + $this->getAttributes()->add('class', 'in-effect'); + } + } + + protected function createProgress(): BaseHtmlElement + { + return new HtmlElement( + 'div', + Attributes::create([ + 'class' => 'progress', + 'data-animate-progress' => true, + 'data-start-time' => $this->startTime, + 'data-end-time' => $this->endTime + ]), + new HtmlElement( + 'div', + Attributes::create(['class' => 'bar']) + ) + ); + } + + protected function assembleCaption(BaseHtmlElement $caption): void + { + $markdownLine = new MarkdownLine($this->createTicketLinks($this->item->comment)); + $caption->getAttributes()->add($markdownLine->getAttributes()); + $caption->addHtml( + new HtmlElement( + 'span', + null, + new Icon(Icons::USER), + Text::create($this->item->author) + ), + Text::create(': ') + )->addFrom($markdownLine); + } + + protected function assembleTitle(BaseHtmlElement $title): void + { + if ($this->getObjectLinkDisabled()) { + $link = null; + } elseif ($this->item->object_type === 'host') { + $link = $this->createHostLink($this->item->host, true); + } else { + $link = $this->createServiceLink($this->item->service, $this->item->service->host, true); + } + + if ($this->item->is_flexible) { + if ($link !== null) { + $template = t('{{#link}}Flexible Downtime{{/link}} for %s'); + } else { + $template = t('Flexible Downtime'); + } + } else { + if ($link !== null) { + $template = t('{{#link}}Fixed Downtime{{/link}} for %s'); + } else { + $template = t('Fixed Downtime'); + } + } + + if ($this->getNoSubjectLink()) { + if ($link === null) { + $title->addHtml(HtmlElement::create('span', [ 'class' => 'subject'], $template)); + } else { + $title->addHtml(TemplateString::create( + $template, + ['link' => HtmlElement::create('span', [ 'class' => 'subject'])], + $link + )); + } + } else { + if ($link === null) { + $title->addHtml(new Link($template, Links::downtime($this->item))); + } else { + $title->addHtml(TemplateString::create( + $template, + ['link' => new Link('', Links::downtime($this->item))], + $link + )); + } + } + } + + protected function assembleVisual(BaseHtmlElement $visual): void + { + $dateTime = DateFormatter::formatDateTime($this->endTime); + + if ($this->isActive) { + $visual->addHtml(Html::sprintf( + t('%s left', '<timespan>..'), + Html::tag( + 'strong', + Html::tag( + 'time', + [ + 'datetime' => $dateTime, + 'title' => $dateTime + ], + $this->duration + ) + ) + )); + } else { + $visual->addHtml(Html::sprintf( + t('in %s', '..<timespan>'), + Html::tag('strong', $this->duration) + )); + } + } + + protected function createTimestamp(): ?BaseHtmlElement + { + $dateTime = DateFormatter::formatDateTime($this->isActive ? $this->endTime : $this->startTime); + + return Html::tag( + 'time', + [ + 'datetime' => $dateTime, + 'title' => $dateTime + ], + sprintf( + $this->isActive + ? t('expires in %s', '..<timespan>') + : t('starts in %s', '..<timespan>'), + $this->duration + ) + ); + } +} diff --git a/library/Icingadb/Widget/ItemList/BaseHistoryListItem.php b/library/Icingadb/Widget/ItemList/BaseHistoryListItem.php new file mode 100644 index 0000000..6999324 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/BaseHistoryListItem.php @@ -0,0 +1,405 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Date\DateFormatter; +use Icinga\Module\Icingadb\Common\HostLink; +use Icinga\Module\Icingadb\Common\HostStates; +use Icinga\Module\Icingadb\Common\Icons; +use Icinga\Module\Icingadb\Common\Links; +use Icinga\Module\Icingadb\Common\NoSubjectLink; +use Icinga\Module\Icingadb\Common\TicketLinks; +use Icinga\Module\Icingadb\Widget\MarkdownLine; +use Icinga\Module\Icingadb\Common\ServiceLink; +use Icinga\Module\Icingadb\Common\ServiceStates; +use Icinga\Module\Icingadb\Model\History; +use Icinga\Module\Icingadb\Util\PluginOutput; +use Icinga\Module\Icingadb\Widget\CheckAttempt; +use Icinga\Module\Icingadb\Widget\PluginOutputContainer; +use Icinga\Module\Icingadb\Widget\StateChange; +use ipl\Stdlib\Filter; +use ipl\Web\Common\BaseListItem; +use ipl\Web\Widget\EmptyState; +use ipl\Web\Widget\StateBall; +use ipl\Web\Widget\TimeAgo; +use ipl\Html\BaseHtmlElement; +use ipl\Html\HtmlElement; +use ipl\Html\Text; +use ipl\Web\Widget\Icon; +use ipl\Web\Widget\Link; + +abstract class BaseHistoryListItem extends BaseListItem +{ + use HostLink; + use NoSubjectLink; + use ServiceLink; + use TicketLinks; + + /** @var History */ + protected $item; + + /** @var HistoryList */ + protected $list; + + protected function init(): void + { + $this->setNoSubjectLink($this->list->getNoSubjectLink()); + $this->setTicketLinkEnabled($this->list->getTicketLinkEnabled()); + $this->list->addDetailFilterAttribute($this, Filter::equal('id', bin2hex($this->item->id))); + } + + abstract protected function getStateBallSize(): string; + + protected function assembleCaption(BaseHtmlElement $caption): void + { + switch ($this->item->event_type) { + case 'comment_add': + case 'comment_remove': + $markdownLine = new MarkdownLine($this->createTicketLinks($this->item->comment->comment)); + $caption->getAttributes()->add($markdownLine->getAttributes()); + $caption->add([ + new Icon(Icons::USER), + $this->item->comment->author, + ': ' + ])->addFrom($markdownLine); + + break; + case 'downtime_end': + case 'downtime_start': + $markdownLine = new MarkdownLine($this->createTicketLinks($this->item->downtime->comment)); + $caption->getAttributes()->add($markdownLine->getAttributes()); + $caption->add([ + new Icon(Icons::USER), + $this->item->downtime->author, + ': ' + ])->addFrom($markdownLine); + + break; + case 'flapping_start': + $caption + ->add(sprintf( + t('State Change Rate: %.2f%%; Start Threshold: %.2f%%'), + $this->item->flapping->percent_state_change_start, + $this->item->flapping->flapping_threshold_high + )) + ->getAttributes() + ->add('class', 'plugin-output'); + + break; + case 'flapping_end': + $caption + ->add(sprintf( + t('State Change Rate: %.2f%%; End Threshold: %.2f%%; Flapping for %s'), + $this->item->flapping->percent_state_change_end, + $this->item->flapping->flapping_threshold_low, + DateFormatter::formatDuration( + $this->item->flapping->end_time->getTimestamp() + - $this->item->flapping->start_time->getTimestamp() + ) + )) + ->getAttributes() + ->add('class', 'plugin-output'); + + break; + case 'ack_clear': + case 'ack_set': + if (! isset($this->item->acknowledgement->comment) && ! isset($this->item->acknowledgement->author)) { + $caption->addHtml(new EmptyState( + t('This acknowledgement was set before Icinga DB history recording') + )); + } else { + $markdownLine = new MarkdownLine($this->createTicketLinks($this->item->acknowledgement->comment)); + $caption->getAttributes()->add($markdownLine->getAttributes()); + $caption->add([ + new Icon(Icons::USER), + $this->item->acknowledgement->author, + ': ' + ])->addFrom($markdownLine); + } + + break; + case 'notification': + if (! empty($this->item->notification->author)) { + $caption->add([ + new Icon(Icons::USER), + $this->item->notification->author, + ': ', + $this->item->notification->text + ]); + } else { + $commandName = $this->item->object_type === 'host' + ? $this->item->host->checkcommand_name + : $this->item->service->checkcommand_name; + if (isset($commandName)) { + if (empty($this->item->notification->text)) { + $caption->addHtml(new EmptyState(t('Output unavailable.'))); + } else { + $caption->addHtml(new PluginOutputContainer( + (new PluginOutput($this->item->notification->text)) + ->setCommandName($commandName) + )); + } + } else { + $caption->addHtml(new EmptyState(t('Waiting for Icinga DB to synchronize the config.'))); + } + } + + break; + case 'state_change': + $commandName = $this->item->object_type === 'host' + ? $this->item->host->checkcommand_name + : $this->item->service->checkcommand_name; + if (isset($commandName)) { + if (empty($this->item->state->output)) { + $caption->addHtml(new EmptyState(t('Output unavailable.'))); + } else { + $caption->addHtml(new PluginOutputContainer( + (new PluginOutput($this->item->state->output)) + ->setCommandName($commandName) + )); + } + } else { + $caption->addHtml(new EmptyState(t('Waiting for Icinga DB to synchronize the config.'))); + } + + break; + } + } + + protected function assembleVisual(BaseHtmlElement $visual): void + { + switch ($this->item->event_type) { + case 'comment_add': + $visual->addHtml(HtmlElement::create( + 'div', + ['class' => ['icon-ball', 'ball-size-' . $this->getStateBallSize()]], + new Icon(Icons::COMMENT) + )); + + break; + case 'comment_remove': + case 'downtime_end': + case 'ack_clear': + $visual->addHtml(HtmlElement::create( + 'div', + ['class' => ['icon-ball', 'ball-size-' . $this->getStateBallSize()]], + new Icon(Icons::REMOVE) + )); + + break; + case 'downtime_start': + $visual->addHtml(HtmlElement::create( + 'div', + ['class' => ['icon-ball', 'ball-size-' . $this->getStateBallSize()]], + new Icon(Icons::IN_DOWNTIME) + )); + + break; + case 'ack_set': + $visual->addHtml(HtmlElement::create( + 'div', + ['class' => ['icon-ball', 'ball-size-' . $this->getStateBallSize()]], + new Icon(Icons::IS_ACKNOWLEDGED) + )); + + break; + case 'flapping_end': + case 'flapping_start': + $visual->addHtml(HtmlElement::create( + 'div', + ['class' => ['icon-ball', 'ball-size-' . $this->getStateBallSize()]], + new Icon(Icons::IS_FLAPPING) + )); + + break; + case 'notification': + $visual->addHtml(HtmlElement::create( + 'div', + ['class' => ['icon-ball', 'ball-size-' . $this->getStateBallSize()]], + new Icon(Icons::NOTIFICATION) + )); + + break; + case 'state_change': + if ($this->item->state->state_type === 'soft') { + $stateType = 'soft_state'; + $previousStateType = 'previous_soft_state'; + + if ($this->item->state->previous_soft_state === 0) { + $previousStateType = 'hard_state'; + } + + $visual->addHtml(new CheckAttempt( + (int) $this->item->state->check_attempt, + (int) $this->item->state->max_check_attempts + )); + } else { + $stateType = 'hard_state'; + $previousStateType = 'previous_hard_state'; + + if ($this->item->state->hard_state === $this->item->state->previous_hard_state) { + $previousStateType = 'previous_soft_state'; + } + } + + if ($this->item->object_type === 'host') { + $state = HostStates::text($this->item->state->$stateType); + $previousState = HostStates::text($this->item->state->$previousStateType); + } else { + $state = ServiceStates::text($this->item->state->$stateType); + $previousState = ServiceStates::text($this->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'); + } + } + + $visual->prependHtml($stateChange); + + break; + } + } + + protected function assembleTitle(BaseHtmlElement $title): void + { + switch ($this->item->event_type) { + case 'comment_add': + $subjectLabel = t('Comment added'); + + break; + case 'comment_remove': + if (! empty($this->item->comment->removed_by)) { + if ($this->item->comment->removed_by !== $this->item->comment->author) { + $subjectLabel = sprintf( + t('Comment removed by %s', '..<username>'), + $this->item->comment->removed_by + ); + } else { + $subjectLabel = t('Comment removed by author'); + } + } elseif (isset($this->item->comment->expire_time)) { + $subjectLabel = t('Comment expired'); + } else { + $subjectLabel = t('Comment removed'); + } + + break; + case 'downtime_end': + if (! empty($this->item->downtime->cancelled_by)) { + if ($this->item->downtime->cancelled_by !== $this->item->downtime->author) { + $subjectLabel = sprintf( + t('Downtime cancelled by %s', '..<username>'), + $this->item->downtime->cancelled_by + ); + } else { + $subjectLabel = t('Downtime cancelled by author'); + } + } elseif ($this->item->downtime->has_been_cancelled === 'y') { + $subjectLabel = t('Downtime cancelled'); + } else { + $subjectLabel = t('Downtime ended'); + } + + break; + case 'downtime_start': + $subjectLabel = t('Downtime started'); + + break; + case 'flapping_start': + $subjectLabel = t('Flapping started'); + + break; + case 'flapping_end': + $subjectLabel = t('Flapping stopped'); + + break; + case 'ack_set': + $subjectLabel = t('Acknowledgement set'); + + break; + case 'ack_clear': + if (! empty($this->item->acknowledgement->cleared_by)) { + if ($this->item->acknowledgement->cleared_by !== $this->item->acknowledgement->author) { + $subjectLabel = sprintf( + t('Acknowledgement cleared by %s', '..<username>'), + $this->item->acknowledgement->cleared_by + ); + } else { + $subjectLabel = t('Acknowledgement cleared by author'); + } + } elseif (isset($this->item->acknowledgement->expire_time)) { + $subjectLabel = t('Acknowledgement expired'); + } else { + $subjectLabel = t('Acknowledgement cleared'); + } + + break; + case 'notification': + $subjectLabel = sprintf( + NotificationListItem::phraseForType($this->item->notification->type), + ucfirst($this->item->object_type) + ); + + break; + case 'state_change': + $state = $this->item->state->state_type === 'hard' + ? $this->item->state->hard_state + : $this->item->state->soft_state; + if ($state === 0) { + if ($this->item->object_type === 'service') { + $subjectLabel = t('Service recovered'); + } else { + $subjectLabel = t('Host recovered'); + } + } else { + if ($this->item->state->state_type === 'hard') { + $subjectLabel = t('Hard state changed'); + } else { + $subjectLabel = t('Soft state changed'); + } + } + + break; + default: + $subjectLabel = $this->item->event_type; + + break; + } + + if ($this->getNoSubjectLink()) { + $title->addHtml(HtmlElement::create('span', ['class' => 'subject'], $subjectLabel)); + } else { + $title->addHtml(new Link($subjectLabel, Links::event($this->item), ['class' => 'subject'])); + } + + if ($this->item->object_type === 'host') { + if (isset($this->item->host->id)) { + $link = $this->createHostLink($this->item->host, true); + } + } else { + if (isset($this->item->host->id, $this->item->service->id)) { + $link = $this->createServiceLink($this->item->service, $this->item->host, true); + } + } + + $title->addHtml(Text::create(' ')); + if (isset($link)) { + $title->addHtml($link); + } + } + + protected function createTimestamp(): ?BaseHtmlElement + { + return new TimeAgo($this->item->event_time->getTimestamp()); + } +} diff --git a/library/Icingadb/Widget/ItemList/BaseHostListItem.php b/library/Icingadb/Widget/ItemList/BaseHostListItem.php new file mode 100644 index 0000000..edaf6c8 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/BaseHostListItem.php @@ -0,0 +1,56 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\Links; +use Icinga\Module\Icingadb\Common\NoSubjectLink; +use Icinga\Module\Icingadb\Model\Host; +use ipl\Html\Attributes; +use ipl\Html\BaseHtmlElement; +use ipl\Html\HtmlElement; +use ipl\Html\Text; +use ipl\Stdlib\Filter; +use ipl\Web\Widget\Link; + +/** + * Host item of a host list. Represents one database row. + * + * @property Host $item + * @property HostList $list + */ +abstract class BaseHostListItem extends StateListItem +{ + use NoSubjectLink; + + /** + * Create new subject link + * + * @return BaseHtmlElement + */ + 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']); + } + } + + 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/BaseNotificationListItem.php b/library/Icingadb/Widget/ItemList/BaseNotificationListItem.php new file mode 100644 index 0000000..b538ac4 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/BaseNotificationListItem.php @@ -0,0 +1,189 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\HostLink; +use Icinga\Module\Icingadb\Common\HostStates; +use Icinga\Module\Icingadb\Common\Icons; +use Icinga\Module\Icingadb\Common\Links; +use Icinga\Module\Icingadb\Common\NoSubjectLink; +use Icinga\Module\Icingadb\Common\ServiceLink; +use Icinga\Module\Icingadb\Common\ServiceStates; +use Icinga\Module\Icingadb\Util\PluginOutput; +use Icinga\Module\Icingadb\Widget\PluginOutputContainer; +use Icinga\Module\Icingadb\Widget\StateChange; +use ipl\Stdlib\Filter; +use ipl\Web\Common\BaseListItem; +use ipl\Web\Widget\EmptyState; +use ipl\Web\Widget\TimeAgo; +use InvalidArgumentException; +use ipl\Html\BaseHtmlElement; +use ipl\Html\HtmlElement; +use ipl\Html\Text; +use ipl\Web\Widget\Icon; +use ipl\Web\Widget\Link; + +abstract class BaseNotificationListItem extends BaseListItem +{ + use HostLink; + use NoSubjectLink; + use ServiceLink; + + /** @var NotificationList */ + protected $list; + + protected function init(): void + { + $this->setNoSubjectLink($this->list->getNoSubjectLink()); + $this->list->addDetailFilterAttribute($this, Filter::equal('id', bin2hex($this->item->history->id))); + } + + /** + * Get a localized phrase for the given notification type + * + * @param string $type + * + * @return string + */ + public static function phraseForType(string $type): string + { + switch ($type) { + case 'acknowledgement': + return t('Problem acknowledged'); + case 'custom': + return t('Custom Notification triggered'); + case 'downtime_end': + return t('Downtime ended'); + case 'downtime_removed': + return t('Downtime removed'); + case 'downtime_start': + return t('Downtime started'); + case 'flapping_end': + return t('Flapping stopped'); + case 'flapping_start': + return t('Flapping started'); + case 'problem': + return t('%s ran into a problem'); + case 'recovery': + return t('%s recovered'); + default: + throw new InvalidArgumentException(sprintf('Type %s is not a valid notification type', $type)); + } + } + + abstract protected function getStateBallSize(); + + protected function assembleCaption(BaseHtmlElement $caption): void + { + if (in_array($this->item->type, ['flapping_end', 'flapping_start', 'problem', 'recovery'])) { + $commandName = $this->item->object_type === 'host' + ? $this->item->host->checkcommand_name + : $this->item->service->checkcommand_name; + if (isset($commandName)) { + if (empty($this->item->text)) { + $caption->addHtml(new EmptyState(t('Output unavailable.'))); + } else { + $caption->addHtml(new PluginOutputContainer( + (new PluginOutput($this->item->text)) + ->setCommandName($commandName) + )); + } + } else { + $caption->addHtml(new EmptyState(t('Waiting for Icinga DB to synchronize the config.'))); + } + } else { + $caption->add([ + new Icon(Icons::USER), + $this->item->author, + ': ', + $this->item->text + ]); + } + } + + protected function assembleVisual(BaseHtmlElement $visual): void + { + switch ($this->item->type) { + case 'acknowledgement': + $visual->addHtml(HtmlElement::create( + 'div', + ['class' => ['icon-ball', 'ball-size-' . $this->getStateBallSize()]], + new Icon(Icons::IS_ACKNOWLEDGED) + )); + + break; + case 'custom': + $visual->addHtml(HtmlElement::create( + 'div', + ['class' => ['icon-ball', 'ball-size-' . $this->getStateBallSize()]], + new Icon(Icons::NOTIFICATION) + )); + + break; + case 'downtime_end': + case 'downtime_removed': + case 'downtime_start': + $visual->addHtml(HtmlElement::create( + 'div', + ['class' => ['icon-ball', 'ball-size-' . $this->getStateBallSize()]], + new Icon(Icons::IN_DOWNTIME) + )); + + break; + case 'flapping_end': + case 'flapping_start': + $visual->addHtml(HtmlElement::create( + 'div', + ['class' => ['icon-ball', 'ball-size-' . $this->getStateBallSize()]], + new Icon(Icons::IS_FLAPPING) + )); + + break; + case 'problem': + case 'recovery': + if ($this->item->object_type === 'host') { + $state = HostStates::text($this->item->state); + $previousHardState = HostStates::text($this->item->previous_hard_state); + } else { + $state = ServiceStates::text($this->item->state); + $previousHardState = ServiceStates::text($this->item->previous_hard_state); + } + + $visual->addHtml(new StateChange($state, $previousHardState)); + + break; + } + } + + protected function assembleTitle(BaseHtmlElement $title): void + { + if ($this->getNoSubjectLink()) { + $title->addHtml(HtmlElement::create( + 'span', + ['class' => 'subject'], + sprintf(self::phraseForType($this->item->type), ucfirst($this->item->object_type)) + )); + } else { + $title->addHtml(new Link( + sprintf(self::phraseForType($this->item->type), ucfirst($this->item->object_type)), + Links::event($this->item->history), + ['class' => 'subject'] + )); + } + + if ($this->item->object_type === 'host') { + $link = $this->createHostLink($this->item->host, true); + } else { + $link = $this->createServiceLink($this->item->service, $this->item->host, true); + } + + $title->addHtml(Text::create(' '), $link); + } + + protected function createTimestamp(): ?BaseHtmlElement + { + return new TimeAgo($this->item->send_time->getTimestamp()); + } +} diff --git a/library/Icingadb/Widget/ItemList/BaseServiceListItem.php b/library/Icingadb/Widget/ItemList/BaseServiceListItem.php new file mode 100644 index 0000000..fe4f014 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/BaseServiceListItem.php @@ -0,0 +1,70 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +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; + +/** + * Service item of a service list. Represents one database row. + * + * @property Service $item + * @property ServiceList $list + */ +abstract class BaseServiceListItem extends StateListItem +{ + use NoSubjectLink; + + protected function createSubject() + { + $service = $this->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', '<service> on <host>'), $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/CommandTransportList.php b/library/Icingadb/Widget/ItemList/CommandTransportList.php new file mode 100644 index 0000000..61d771d --- /dev/null +++ b/library/Icingadb/Widget/ItemList/CommandTransportList.php @@ -0,0 +1,26 @@ +<?php + +/* Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\DetailActions; +use ipl\Web\Common\BaseOrderedItemList; +use ipl\Web\Url; + +class CommandTransportList extends BaseOrderedItemList +{ + use DetailActions; + + protected function init(): void + { + $this->getAttributes()->add('class', 'command-transport-list'); + $this->initializeDetailActions(); + $this->setDetailUrl(Url::fromPath('icingadb/command-transport/show')); + } + + protected function getItemClass(): string + { + return CommandTransportListItem::class; + } +} diff --git a/library/Icingadb/Widget/ItemList/CommandTransportListItem.php b/library/Icingadb/Widget/ItemList/CommandTransportListItem.php new file mode 100644 index 0000000..9873403 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/CommandTransportListItem.php @@ -0,0 +1,71 @@ +<?php + +/* Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use ipl\Html\BaseHtmlElement; +use ipl\Html\HtmlElement; +use ipl\Html\Text; +use ipl\Stdlib\Filter; +use ipl\Web\Common\BaseOrderedListItem; +use ipl\Web\Url; +use ipl\Web\Widget\Icon; +use ipl\Web\Widget\Link; + +class CommandTransportListItem extends BaseOrderedListItem +{ + protected function init(): void + { + $this->list->addDetailFilterAttribute($this, Filter::equal('name', $this->item->name)); + } + + protected function assembleHeader(BaseHtmlElement $header): void + { + } + + protected function assembleMain(BaseHtmlElement $main): void + { + $main->addHtml(new Link( + new HtmlElement('strong', null, Text::create($this->item->name)), + Url::fromPath('icingadb/command-transport/show', ['name' => $this->item->name]) + )); + + $main->addHtml(new Link( + new Icon('trash', ['title' => sprintf(t('Remove command transport "%s"'), $this->item->name)]), + Url::fromPath('icingadb/command-transport/remove', ['name' => $this->item->name]), + [ + 'class' => 'pull-right action-link', + 'data-icinga-modal' => true, + 'data-no-icinga-ajax' => true + ] + )); + + if ($this->getOrder() + 1 < $this->list->count()) { + $main->addHtml((new Link( + new Icon('arrow-down'), + Url::fromPath('icingadb/command-transport/sort', [ + 'name' => $this->item->name, + 'pos' => $this->getOrder() + 1 + ]), + ['class' => 'pull-right action-link'] + ))->setBaseTarget('_self')); + } + + if ($this->getOrder() > 0) { + $main->addHtml((new Link( + new Icon('arrow-up'), + Url::fromPath('icingadb/command-transport/sort', [ + 'name' => $this->item->name, + 'pos' => $this->getOrder() - 1 + ]), + ['class' => 'pull-right action-link'] + ))->setBaseTarget('_self')); + } + } + + protected function createVisual(): ?BaseHtmlElement + { + return null; + } +} diff --git a/library/Icingadb/Widget/ItemList/CommentList.php b/library/Icingadb/Widget/ItemList/CommentList.php new file mode 100644 index 0000000..5cf65ae --- /dev/null +++ b/library/Icingadb/Widget/ItemList/CommentList.php @@ -0,0 +1,49 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\CaptionDisabled; +use Icinga\Module\Icingadb\Common\DetailActions; +use Icinga\Module\Icingadb\Common\Links; +use Icinga\Module\Icingadb\Common\NoSubjectLink; +use Icinga\Module\Icingadb\Common\ObjectLinkDisabled; +use Icinga\Module\Icingadb\Common\TicketLinks; +use Icinga\Module\Icingadb\Common\ViewMode; +use ipl\Web\Common\BaseItemList; +use ipl\Web\Url; + +class CommentList extends BaseItemList +{ + use CaptionDisabled; + use NoSubjectLink; + use ObjectLinkDisabled; + use ViewMode; + use TicketLinks; + use DetailActions; + + protected $defaultAttributes = ['class' => 'comment-list']; + + protected function getItemClass(): string + { + $viewMode = $this->getViewMode(); + + $this->addAttributes(['class' => $viewMode]); + + if ($viewMode === 'minimal') { + return CommentListItemMinimal::class; + } elseif ($viewMode === 'detailed') { + $this->removeAttribute('class', 'default-layout'); + } + + return CommentListItem::class; + } + + protected function init(): void + { + $this->initializeDetailActions(); + $this->setMultiselectUrl(Links::commentsDetails()); + $this->setDetailUrl(Url::fromPath('icingadb/comment')); + } +} diff --git a/library/Icingadb/Widget/ItemList/CommentListItem.php b/library/Icingadb/Widget/ItemList/CommentListItem.php new file mode 100644 index 0000000..3bbd0c2 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/CommentListItem.php @@ -0,0 +1,12 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\ListItemCommonLayout; + +class CommentListItem extends BaseCommentListItem +{ + use ListItemCommonLayout; +} diff --git a/library/Icingadb/Widget/ItemList/CommentListItemMinimal.php b/library/Icingadb/Widget/ItemList/CommentListItemMinimal.php new file mode 100644 index 0000000..3c23ccd --- /dev/null +++ b/library/Icingadb/Widget/ItemList/CommentListItemMinimal.php @@ -0,0 +1,21 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\ListItemMinimalLayout; + +class CommentListItemMinimal extends BaseCommentListItem +{ + use ListItemMinimalLayout; + + protected function init(): void + { + parent::init(); + + if ($this->list->isCaptionDisabled()) { + $this->setCaptionDisabled(); + } + } +} diff --git a/library/Icingadb/Widget/ItemList/DowntimeList.php b/library/Icingadb/Widget/ItemList/DowntimeList.php new file mode 100644 index 0000000..591ad98 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/DowntimeList.php @@ -0,0 +1,49 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\CaptionDisabled; +use Icinga\Module\Icingadb\Common\DetailActions; +use Icinga\Module\Icingadb\Common\Links; +use Icinga\Module\Icingadb\Common\NoSubjectLink; +use Icinga\Module\Icingadb\Common\ObjectLinkDisabled; +use Icinga\Module\Icingadb\Common\TicketLinks; +use Icinga\Module\Icingadb\Common\ViewMode; +use ipl\Web\Common\BaseItemList; +use ipl\Web\Url; + +class DowntimeList extends BaseItemList +{ + use CaptionDisabled; + use NoSubjectLink; + use ObjectLinkDisabled; + use ViewMode; + use TicketLinks; + use DetailActions; + + protected $defaultAttributes = ['class' => 'downtime-list']; + + protected function getItemClass(): string + { + $viewMode = $this->getViewMode(); + + $this->addAttributes(['class' => $viewMode]); + + if ($viewMode === 'minimal') { + return DowntimeListItemMinimal::class; + } elseif ($viewMode === 'detailed') { + $this->removeAttribute('class', 'default-layout'); + } + + return DowntimeListItem::class; + } + + protected function init(): void + { + $this->initializeDetailActions(); + $this->setMultiselectUrl(Links::downtimesDetails()); + $this->setDetailUrl(Url::fromPath('icingadb/downtime')); + } +} diff --git a/library/Icingadb/Widget/ItemList/DowntimeListItem.php b/library/Icingadb/Widget/ItemList/DowntimeListItem.php new file mode 100644 index 0000000..cb7e9b3 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/DowntimeListItem.php @@ -0,0 +1,23 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\ListItemCommonLayout; +use ipl\Html\BaseHtmlElement; + +class DowntimeListItem extends BaseDowntimeListItem +{ + use ListItemCommonLayout; + + protected function assembleMain(BaseHtmlElement $main): void + { + if ($this->item->is_in_effect) { + $main->add($this->createProgress()); + } + + $main->add($this->createHeader()); + $main->add($this->createCaption()); + } +} diff --git a/library/Icingadb/Widget/ItemList/DowntimeListItemMinimal.php b/library/Icingadb/Widget/ItemList/DowntimeListItemMinimal.php new file mode 100644 index 0000000..b8581d2 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/DowntimeListItemMinimal.php @@ -0,0 +1,21 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\ListItemMinimalLayout; + +class DowntimeListItemMinimal extends BaseDowntimeListItem +{ + use ListItemMinimalLayout; + + protected function init(): void + { + parent::init(); + + if ($this->list->isCaptionDisabled()) { + $this->setCaptionDisabled(); + } + } +} diff --git a/library/Icingadb/Widget/ItemList/HistoryList.php b/library/Icingadb/Widget/ItemList/HistoryList.php new file mode 100644 index 0000000..d3b6232 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/HistoryList.php @@ -0,0 +1,57 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\CaptionDisabled; +use Icinga\Module\Icingadb\Common\DetailActions; +use Icinga\Module\Icingadb\Common\LoadMore; +use Icinga\Module\Icingadb\Common\NoSubjectLink; +use Icinga\Module\Icingadb\Common\TicketLinks; +use Icinga\Module\Icingadb\Common\ViewMode; +use ipl\Orm\ResultSet; +use ipl\Web\Common\BaseItemList; +use ipl\Web\Url; + +class HistoryList extends BaseItemList +{ + use CaptionDisabled; + use NoSubjectLink; + use ViewMode; + use LoadMore; + use TicketLinks; + use DetailActions; + + protected $defaultAttributes = ['class' => 'history-list']; + + protected function init(): void + { + /** @var ResultSet $data */ + $data = $this->data; + $this->data = $this->getIterator($data); + $this->initializeDetailActions(); + $this->setDetailUrl(Url::fromPath('icingadb/event')); + } + + protected function getItemClass(): string + { + switch ($this->getViewMode()) { + case 'minimal': + return HistoryListItemMinimal::class; + case 'detailed': + $this->removeAttribute('class', 'default-layout'); + + return HistoryListItemDetailed::class; + default: + return HistoryListItem::class; + } + } + + protected function assemble(): void + { + $this->addAttributes(['class' => $this->getViewMode()]); + + parent::assemble(); + } +} diff --git a/library/Icingadb/Widget/ItemList/HistoryListItem.php b/library/Icingadb/Widget/ItemList/HistoryListItem.php new file mode 100644 index 0000000..c44a807 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/HistoryListItem.php @@ -0,0 +1,18 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\ListItemCommonLayout; +use ipl\Web\Widget\StateBall; + +class HistoryListItem extends BaseHistoryListItem +{ + use ListItemCommonLayout; + + protected function getStateBallSize(): string + { + return StateBall::SIZE_LARGE; + } +} diff --git a/library/Icingadb/Widget/ItemList/HistoryListItemDetailed.php b/library/Icingadb/Widget/ItemList/HistoryListItemDetailed.php new file mode 100644 index 0000000..7129d2d --- /dev/null +++ b/library/Icingadb/Widget/ItemList/HistoryListItemDetailed.php @@ -0,0 +1,18 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\ListItemDetailedLayout; +use ipl\Web\Widget\StateBall; + +class HistoryListItemDetailed extends BaseHistoryListItem +{ + use ListItemDetailedLayout; + + protected function getStateBallSize(): string + { + return StateBall::SIZE_LARGE; + } +} diff --git a/library/Icingadb/Widget/ItemList/HistoryListItemMinimal.php b/library/Icingadb/Widget/ItemList/HistoryListItemMinimal.php new file mode 100644 index 0000000..5a7f214 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/HistoryListItemMinimal.php @@ -0,0 +1,27 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\ListItemMinimalLayout; +use ipl\Web\Widget\StateBall; + +class HistoryListItemMinimal extends BaseHistoryListItem +{ + use ListItemMinimalLayout; + + protected function init(): void + { + parent::init(); + + if ($this->list->isCaptionDisabled()) { + $this->setCaptionDisabled(); + } + } + + protected function getStateBallSize(): string + { + return StateBall::SIZE_BIG; + } +} diff --git a/library/Icingadb/Widget/ItemList/HostDetailHeader.php b/library/Icingadb/Widget/ItemList/HostDetailHeader.php new file mode 100644 index 0000000..97176da --- /dev/null +++ b/library/Icingadb/Widget/ItemList/HostDetailHeader.php @@ -0,0 +1,67 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\HostStates; +use Icinga\Module\Icingadb\Widget\StateChange; +use ipl\Html\BaseHtmlElement; +use ipl\Web\Widget\StateBall; + +class HostDetailHeader extends HostListItemMinimal +{ + protected function getStateBallSize(): string + { + return ''; + } + + protected function assembleVisual(BaseHtmlElement $visual): void + { + if ($this->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_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 new file mode 100644 index 0000000..2be1f84 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/HostList.php @@ -0,0 +1,39 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\Links; +use ipl\Web\Url; + +/** + * Host list + */ +class HostList extends StateList +{ + protected $defaultAttributes = ['class' => '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 new file mode 100644 index 0000000..2eae660 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/HostListItem.php @@ -0,0 +1,18 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\ListItemCommonLayout; +use ipl\Web\Widget\StateBall; + +class HostListItem extends BaseHostListItem +{ + use ListItemCommonLayout; + + protected function getStateBallSize(): string + { + return StateBall::SIZE_LARGE; + } +} diff --git a/library/Icingadb/Widget/ItemList/HostListItemDetailed.php b/library/Icingadb/Widget/ItemList/HostListItemDetailed.php new file mode 100644 index 0000000..255bdcc --- /dev/null +++ b/library/Icingadb/Widget/ItemList/HostListItemDetailed.php @@ -0,0 +1,108 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\ListItemDetailedLayout; +use Icinga\Module\Icingadb\Util\PerfDataSet; +use Icinga\Module\Icingadb\Widget\ItemList\CommentList; +use ipl\Html\Attributes; +use ipl\Html\BaseHtmlElement; +use ipl\Html\HtmlElement; +use ipl\Html\HtmlString; +use ipl\Html\Text; +use ipl\Web\Widget\Icon; +use ipl\Web\Widget\StateBall; + +class HostListItemDetailed extends BaseHostListItem +{ + use ListItemDetailedLayout; + + /** @var int Max pie charts to be shown */ + const PIE_CHART_LIMIT = 5; + + protected function getStateBallSize(): string + { + return StateBall::SIZE_LARGE; + } + + protected function assembleFooter(BaseHtmlElement $footer): void + { + $statusIcons = new HtmlElement('div', Attributes::create(['class' => '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 new file mode 100644 index 0000000..f04b991 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/HostListItemMinimal.php @@ -0,0 +1,18 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\ListItemMinimalLayout; +use ipl\Web\Widget\StateBall; + +class HostListItemMinimal extends BaseHostListItem +{ + use ListItemMinimalLayout; + + protected function getStateBallSize(): string + { + return StateBall::SIZE_BIG; + } +} diff --git a/library/Icingadb/Widget/ItemList/NotificationList.php b/library/Icingadb/Widget/ItemList/NotificationList.php new file mode 100644 index 0000000..3a16b0b --- /dev/null +++ b/library/Icingadb/Widget/ItemList/NotificationList.php @@ -0,0 +1,55 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\CaptionDisabled; +use Icinga\Module\Icingadb\Common\DetailActions; +use Icinga\Module\Icingadb\Common\LoadMore; +use Icinga\Module\Icingadb\Common\NoSubjectLink; +use Icinga\Module\Icingadb\Common\ViewMode; +use ipl\Orm\ResultSet; +use ipl\Web\Common\BaseItemList; +use ipl\Web\Url; + +class NotificationList extends BaseItemList +{ + use CaptionDisabled; + use NoSubjectLink; + use ViewMode; + use LoadMore; + use DetailActions; + + protected $defaultAttributes = ['class' => 'notification-list']; + + protected function init(): void + { + /** @var ResultSet $data */ + $data = $this->data; + $this->data = $this->getIterator($data); + $this->initializeDetailActions(); + $this->setDetailUrl(Url::fromPath('icingadb/event')); + } + + protected function getItemClass(): string + { + switch ($this->getViewMode()) { + case 'minimal': + return NotificationListItemMinimal::class; + case 'detailed': + $this->removeAttribute('class', 'default-layout'); + + return NotificationListItemDetailed::class; + default: + return NotificationListItem::class; + } + } + + protected function assemble(): void + { + $this->addAttributes(['class' => $this->getViewMode()]); + + parent::assemble(); + } +} diff --git a/library/Icingadb/Widget/ItemList/NotificationListItem.php b/library/Icingadb/Widget/ItemList/NotificationListItem.php new file mode 100644 index 0000000..683762f --- /dev/null +++ b/library/Icingadb/Widget/ItemList/NotificationListItem.php @@ -0,0 +1,18 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\ListItemCommonLayout; +use ipl\Web\Widget\StateBall; + +class NotificationListItem extends BaseNotificationListItem +{ + use ListItemCommonLayout; + + protected function getStateBallSize(): string + { + return StateBall::SIZE_LARGE; + } +} diff --git a/library/Icingadb/Widget/ItemList/NotificationListItemDetailed.php b/library/Icingadb/Widget/ItemList/NotificationListItemDetailed.php new file mode 100644 index 0000000..0a7449e --- /dev/null +++ b/library/Icingadb/Widget/ItemList/NotificationListItemDetailed.php @@ -0,0 +1,18 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\ListItemDetailedLayout; +use ipl\Web\Widget\StateBall; + +class NotificationListItemDetailed extends BaseNotificationListItem +{ + use ListItemDetailedLayout; + + protected function getStateBallSize(): string + { + return StateBall::SIZE_LARGE; + } +} diff --git a/library/Icingadb/Widget/ItemList/NotificationListItemMinimal.php b/library/Icingadb/Widget/ItemList/NotificationListItemMinimal.php new file mode 100644 index 0000000..dd6d226 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/NotificationListItemMinimal.php @@ -0,0 +1,27 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\ListItemMinimalLayout; +use ipl\Web\Widget\StateBall; + +class NotificationListItemMinimal extends BaseNotificationListItem +{ + use ListItemMinimalLayout; + + protected function init(): void + { + parent::init(); + + if ($this->list->isCaptionDisabled()) { + $this->setCaptionDisabled(); + } + } + + protected function getStateBallSize(): string + { + return StateBall::SIZE_BIG; + } +} diff --git a/library/Icingadb/Widget/ItemList/PageSeparatorItem.php b/library/Icingadb/Widget/ItemList/PageSeparatorItem.php new file mode 100644 index 0000000..3e252eb --- /dev/null +++ b/library/Icingadb/Widget/ItemList/PageSeparatorItem.php @@ -0,0 +1,36 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use ipl\Html\BaseHtmlElement; +use ipl\Html\Html; + +class PageSeparatorItem extends BaseHtmlElement +{ + protected $defaultAttributes = ['class' => 'list-item page-separator']; + + /** @var int */ + protected $pageNumber; + + /** @var string */ + protected $tag = 'li'; + + public function __construct(int $pageNumber) + { + $this->pageNumber = $pageNumber; + } + + protected function assemble() + { + $this->add(Html::tag( + 'a', + [ + 'id' => 'page-' . $this->pageNumber, + 'data-icinga-no-scroll-on-focus' => true + ], + $this->pageNumber + )); + } +} diff --git a/library/Icingadb/Widget/ItemList/ServiceDetailHeader.php b/library/Icingadb/Widget/ItemList/ServiceDetailHeader.php new file mode 100644 index 0000000..2f0dbbd --- /dev/null +++ b/library/Icingadb/Widget/ItemList/ServiceDetailHeader.php @@ -0,0 +1,67 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\ServiceStates; +use Icinga\Module\Icingadb\Widget\StateChange; +use ipl\Html\BaseHtmlElement; +use ipl\Web\Widget\StateBall; + +class ServiceDetailHeader extends ServiceListItemMinimal +{ + protected function getStateBallSize(): string + { + return ''; + } + + protected function assembleVisual(BaseHtmlElement $visual): void + { + if ($this->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_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 new file mode 100644 index 0000000..8d41a70 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/ServiceList.php @@ -0,0 +1,36 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\Links; +use ipl\Web\Url; + +class ServiceList extends StateList +{ + protected $defaultAttributes = ['class' => '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 new file mode 100644 index 0000000..a974581 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/ServiceListItem.php @@ -0,0 +1,18 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\ListItemCommonLayout; +use ipl\Web\Widget\StateBall; + +class ServiceListItem extends BaseServiceListItem +{ + use ListItemCommonLayout; + + protected function getStateBallSize(): string + { + return StateBall::SIZE_LARGE; + } +} diff --git a/library/Icingadb/Widget/ItemList/ServiceListItemDetailed.php b/library/Icingadb/Widget/ItemList/ServiceListItemDetailed.php new file mode 100644 index 0000000..1613599 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/ServiceListItemDetailed.php @@ -0,0 +1,112 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\ListItemDetailedLayout; +use Icinga\Module\Icingadb\Util\PerfDataSet; +use Icinga\Module\Icingadb\Widget\ItemList\CommentList; +use ipl\Html\Attributes; +use ipl\Html\BaseHtmlElement; +use ipl\Html\HtmlElement; +use ipl\Html\HtmlString; +use ipl\Html\Text; +use ipl\Web\Widget\Icon; +use ipl\Web\Widget\StateBall; + +class ServiceListItemDetailed extends BaseServiceListItem +{ + use ListItemDetailedLayout; + + /** @var int Max pie charts to be shown */ + const PIE_CHART_LIMIT = 5; + + protected function getStateBallSize(): string + { + return StateBall::SIZE_LARGE; + } + + protected function assembleFooter(BaseHtmlElement $footer): void + { + $statusIcons = new HtmlElement('div', Attributes::create(['class' => '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 new file mode 100644 index 0000000..e7a1bc6 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/ServiceListItemMinimal.php @@ -0,0 +1,18 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +namespace Icinga\Module\Icingadb\Widget\ItemList; + +use Icinga\Module\Icingadb\Common\ListItemMinimalLayout; +use ipl\Web\Widget\StateBall; + +class ServiceListItemMinimal extends BaseServiceListItem +{ + use ListItemMinimalLayout; + + protected function getStateBallSize(): string + { + return StateBall::SIZE_BIG; + } +} diff --git a/library/Icingadb/Widget/ItemList/StateList.php b/library/Icingadb/Widget/ItemList/StateList.php new file mode 100644 index 0000000..1e6dcb9 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/StateList.php @@ -0,0 +1,60 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +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; +use ipl\Html\HtmlDocument; +use ipl\Web\Common\BaseItemList; + +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 */ + protected $hasIconImages = false; + + /** + * Get whether the list contains at least one item with an icon_image + * + * @return bool + */ + public function hasIconImages(): bool + { + return $this->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('Icinga 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 new file mode 100644 index 0000000..d0b3363 --- /dev/null +++ b/library/Icingadb/Widget/ItemList/StateListItem.php @@ -0,0 +1,140 @@ +<?php + +/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */ + +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\HtmlElement; +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. + */ +abstract class StateListItem extends BaseListItem +{ + /** @var StateList The list where the item is part of */ + protected $list; + + /** @var State The state of the item */ + protected $state; + + protected function init(): void + { + $this->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(t('Waiting for Icinga DB to synchronize the state.'))); + } else { + if (empty($this->state->output)) { + $pluginOutput = new EmptyState(t('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( + t('%s is %s', '<hostname> is <state-text>'), + $this->createSubject(), + Html::tag('span', ['class' => 'state-text'], $this->state->getStateTextTranslated()) + )); + } + + protected function assembleVisual(BaseHtmlElement $visual): void + { + $stateBall = new StateBall($this->state->getStateText(), $this->getStateBallSize()); + $stateBall->add($this->state->getIcon()); + if ($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(t('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() + ]); + } +} |