summaryrefslogtreecommitdiffstats
path: root/library/Icingadb/Widget/ItemList
diff options
context:
space:
mode:
Diffstat (limited to 'library/Icingadb/Widget/ItemList')
-rw-r--r--library/Icingadb/Widget/ItemList/BaseCommentListItem.php131
-rw-r--r--library/Icingadb/Widget/ItemList/BaseDowntimeListItem.php217
-rw-r--r--library/Icingadb/Widget/ItemList/BaseHistoryListItem.php404
-rw-r--r--library/Icingadb/Widget/ItemList/BaseHostListItem.php56
-rw-r--r--library/Icingadb/Widget/ItemList/BaseNotificationListItem.php189
-rw-r--r--library/Icingadb/Widget/ItemList/BaseServiceListItem.php70
-rw-r--r--library/Icingadb/Widget/ItemList/CommandTransportList.php22
-rw-r--r--library/Icingadb/Widget/ItemList/CommandTransportListItem.php70
-rw-r--r--library/Icingadb/Widget/ItemList/CommentList.php44
-rw-r--r--library/Icingadb/Widget/ItemList/CommentListItem.php12
-rw-r--r--library/Icingadb/Widget/ItemList/CommentListItemMinimal.php21
-rw-r--r--library/Icingadb/Widget/ItemList/DowntimeList.php44
-rw-r--r--library/Icingadb/Widget/ItemList/DowntimeListItem.php23
-rw-r--r--library/Icingadb/Widget/ItemList/DowntimeListItemMinimal.php21
-rw-r--r--library/Icingadb/Widget/ItemList/HistoryList.php58
-rw-r--r--library/Icingadb/Widget/ItemList/HistoryListItem.php18
-rw-r--r--library/Icingadb/Widget/ItemList/HistoryListItemDetailed.php18
-rw-r--r--library/Icingadb/Widget/ItemList/HistoryListItemMinimal.php27
-rw-r--r--library/Icingadb/Widget/ItemList/HostDetailHeader.php72
-rw-r--r--library/Icingadb/Widget/ItemList/HostList.php36
-rw-r--r--library/Icingadb/Widget/ItemList/HostListItem.php18
-rw-r--r--library/Icingadb/Widget/ItemList/HostListItemDetailed.php103
-rw-r--r--library/Icingadb/Widget/ItemList/HostListItemMinimal.php18
-rw-r--r--library/Icingadb/Widget/ItemList/HostgroupList.php33
-rw-r--r--library/Icingadb/Widget/ItemList/HostgroupListItem.php84
-rw-r--r--library/Icingadb/Widget/ItemList/NotificationList.php56
-rw-r--r--library/Icingadb/Widget/ItemList/NotificationListItem.php18
-rw-r--r--library/Icingadb/Widget/ItemList/NotificationListItemDetailed.php18
-rw-r--r--library/Icingadb/Widget/ItemList/NotificationListItemMinimal.php27
-rw-r--r--library/Icingadb/Widget/ItemList/PageSeparatorItem.php36
-rw-r--r--library/Icingadb/Widget/ItemList/ServiceDetailHeader.php72
-rw-r--r--library/Icingadb/Widget/ItemList/ServiceList.php33
-rw-r--r--library/Icingadb/Widget/ItemList/ServiceListItem.php18
-rw-r--r--library/Icingadb/Widget/ItemList/ServiceListItemDetailed.php107
-rw-r--r--library/Icingadb/Widget/ItemList/ServiceListItemMinimal.php18
-rw-r--r--library/Icingadb/Widget/ItemList/ServicegroupList.php33
-rw-r--r--library/Icingadb/Widget/ItemList/ServicegroupListItem.php68
-rw-r--r--library/Icingadb/Widget/ItemList/StateList.php31
-rw-r--r--library/Icingadb/Widget/ItemList/StateListItem.php129
-rw-r--r--library/Icingadb/Widget/ItemList/UserList.php29
-rw-r--r--library/Icingadb/Widget/ItemList/UserListItem.php62
-rw-r--r--library/Icingadb/Widget/ItemList/UsergroupList.php29
-rw-r--r--library/Icingadb/Widget/ItemList/UsergroupListItem.php62
43 files changed, 2655 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..5fbd033
--- /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\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\NoSubjectLink;
+use Icinga\Module\Icingadb\Common\ObjectLinkDisabled;
+use Icinga\Module\Icingadb\Common\ServiceLink;
+use Icinga\Module\Icingadb\Model\Comment;
+use Icinga\Module\Icingadb\Common\BaseListItem;
+use ipl\Html\FormattedString;
+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)
+ {
+ $markdownLine = new MarkdownLine($this->createTicketLinks($this->item->text));
+
+ $caption->getAttributes()->add($markdownLine->getAttributes());
+ $caption->addFrom($markdownLine);
+ }
+
+ protected function assembleTitle(BaseHtmlElement $title)
+ {
+ $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 != 0) {
+ $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)
+ {
+ $visual->addHtml(new HtmlElement(
+ 'div',
+ Attributes::create(['class' => 'user-ball']),
+ Text::create($this->item->author[0])
+ ));
+ }
+
+ protected function createTimestamp()
+ {
+ if ($this->item->expire_time) {
+ return Html::tag(
+ 'span',
+ FormattedString::create(t("expires %s"), new TimeUntil($this->item->expire_time))
+ );
+ }
+
+ return Html::tag(
+ 'span',
+ FormattedString::create(t("created %s"), new TimeAgo($this->item->entry_time))
+ );
+ }
+
+ protected function init()
+ {
+ $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..04e8e1b
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/BaseDowntimeListItem.php
@@ -0,0 +1,217 @@
+<?php
+
+/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\Icingadb\Widget\ItemList;
+
+use Icinga\Date\DateFormatter;
+use Icinga\Module\Icingadb\Common\BaseListItem;
+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\BaseHtmlElement;
+use ipl\Html\Html;
+use ipl\Html\HtmlElement;
+use ipl\Html\TemplateString;
+use ipl\Html\Text;
+use ipl\Stdlib\Filter;
+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()
+ {
+ if ($this->item->is_flexible && $this->item->is_in_effect) {
+ $this->startTime = $this->item->start_time;
+ $this->endTime = $this->item->end_time;
+ } else {
+ $this->startTime = $this->item->scheduled_start_time;
+ $this->endTime = $this->item->scheduled_end_time;
+ }
+
+ $this->currentTime = time();
+
+ $this->isActive = $this->item->is_in_effect
+ || $this->item->is_flexible && $this->item->scheduled_start_time <= $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
+ {
+ $ref = floor(
+ (float) ($this->currentTime - $this->startTime)
+ / (float) ($this->endTime - $this->startTime)
+ * 100
+ );
+
+ $progress = Html::tag(
+ 'div',
+ ['class' => 'progress'],
+ Html::tag(
+ 'div',
+ [
+ 'class' => 'progress-bar',
+ 'style' => sprintf('width: %F%%', $ref)
+ ]
+ )
+ );
+
+ return $progress;
+ }
+
+ protected function assembleCaption(BaseHtmlElement $caption)
+ {
+ $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)
+ {
+ 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)
+ {
+ $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()
+ {
+ $dateTime = DateFormatter::formatDateTime($this->endTime);
+
+ 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..f7408d7
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/BaseHistoryListItem.php
@@ -0,0 +1,404 @@
+<?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\TicketLinks;
+use Icinga\Module\Icingadb\Widget\EmptyState;
+use Icinga\Module\Icingadb\Widget\MarkdownLine;
+use Icinga\Module\Icingadb\Common\NoSubjectLink;
+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\Common\BaseListItem;
+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\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()
+ {
+ $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)
+ {
+ 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 - $this->item->flapping->start_time
+ )
+ ))
+ ->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)
+ {
+ 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)
+ {
+ 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 (isset($this->item->downtime->cancel_time)) {
+ $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 === '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()
+ {
+ return new TimeAgo($this->item->event_time);
+ }
+}
diff --git a/library/Icingadb/Widget/ItemList/BaseHostListItem.php b/library/Icingadb/Widget/ItemList/BaseHostListItem.php
new file mode 100644
index 0000000..99a8c63
--- /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()
+ {
+ 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..962fe3e
--- /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\Common\BaseListItem;
+use Icinga\Module\Icingadb\Widget\EmptyState;
+use Icinga\Module\Icingadb\Widget\PluginOutputContainer;
+use Icinga\Module\Icingadb\Widget\StateChange;
+use ipl\Stdlib\Filter;
+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()
+ {
+ $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)
+ {
+ 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)
+ {
+ 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)
+ {
+ 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()
+ {
+ return new TimeAgo($this->item->send_time);
+ }
+}
diff --git a/library/Icingadb/Widget/ItemList/BaseServiceListItem.php b/library/Icingadb/Widget/ItemList/BaseServiceListItem.php
new file mode 100644
index 0000000..208e496
--- /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()
+ {
+ 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..50ae06d
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/CommandTransportList.php
@@ -0,0 +1,22 @@
+<?php
+
+/* Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\Icingadb\Widget\ItemList;
+
+use Icinga\Module\Icingadb\Common\BaseOrderedItemList;
+use ipl\Web\Url;
+
+class CommandTransportList extends BaseOrderedItemList
+{
+ protected function init()
+ {
+ $this->getAttributes()->add('class', 'command-transport-list');
+ $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..c15e1bc
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/CommandTransportListItem.php
@@ -0,0 +1,70 @@
+<?php
+
+/* Icinga DB Web | (c) 2021 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\Icingadb\Widget\ItemList;
+
+use Icinga\Module\Icingadb\Common\BaseOrderedListItem;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\HtmlElement;
+use ipl\Html\Text;
+use ipl\Stdlib\Filter;
+use ipl\Web\Url;
+use ipl\Web\Widget\Icon;
+use ipl\Web\Widget\Link;
+
+class CommandTransportListItem extends BaseOrderedListItem
+{
+ protected function init()
+ {
+ $this->list->addDetailFilterAttribute($this, Filter::equal('name', $this->item->name));
+ }
+
+ protected function assembleHeader(BaseHtmlElement $header)
+ {
+ }
+
+ protected function assembleMain(BaseHtmlElement $main)
+ {
+ $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()
+ {
+ }
+}
diff --git a/library/Icingadb/Widget/ItemList/CommentList.php b/library/Icingadb/Widget/ItemList/CommentList.php
new file mode 100644
index 0000000..db164ff
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/CommentList.php
@@ -0,0 +1,44 @@
+<?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\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 Icinga\Module\Icingadb\Common\BaseItemList;
+use ipl\Web\Url;
+
+class CommentList extends BaseItemList
+{
+ use CaptionDisabled;
+ use NoSubjectLink;
+ use ObjectLinkDisabled;
+ use ViewMode;
+ use TicketLinks;
+
+ protected $defaultAttributes = ['class' => 'comment-list'];
+
+ protected function getItemClass(): string
+ {
+ $viewMode = $this->getViewMode();
+
+ $this->addAttributes(['class' => $viewMode]);
+
+ if ($viewMode === 'minimal') {
+ return CommentListItemMinimal::class;
+ }
+
+ return CommentListItem::class;
+ }
+
+ protected function init()
+ {
+ $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..fc204b3
--- /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()
+ {
+ 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..964fd20
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/DowntimeList.php
@@ -0,0 +1,44 @@
+<?php
+
+/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\Icingadb\Widget\ItemList;
+
+use Icinga\Module\Icingadb\Common\BaseItemList;
+use Icinga\Module\Icingadb\Common\CaptionDisabled;
+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\Url;
+
+class DowntimeList extends BaseItemList
+{
+ use CaptionDisabled;
+ use NoSubjectLink;
+ use ObjectLinkDisabled;
+ use ViewMode;
+ use TicketLinks;
+
+ protected $defaultAttributes = ['class' => 'downtime-list'];
+
+ protected function getItemClass(): string
+ {
+ $viewMode = $this->getViewMode();
+
+ $this->addAttributes(['class' => $viewMode]);
+
+ if ($viewMode === 'minimal') {
+ return DowntimeListItemMinimal::class;
+ }
+
+ return DowntimeListItem::class;
+ }
+
+ protected function init()
+ {
+ $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..0a00986
--- /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)
+ {
+ 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..58ef3d8
--- /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()
+ {
+ 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..dbb7cea
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/HistoryList.php
@@ -0,0 +1,58 @@
+<?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\LoadMore;
+use Icinga\Module\Icingadb\Common\NoSubjectLink;
+use Icinga\Module\Icingadb\Common\TicketLinks;
+use Icinga\Module\Icingadb\Common\ViewMode;
+use Icinga\Module\Icingadb\Common\BaseItemList;
+use ipl\Orm\ResultSet;
+use ipl\Web\Url;
+
+class HistoryList extends BaseItemList
+{
+ use CaptionDisabled;
+ use NoSubjectLink;
+ use ViewMode;
+ use LoadMore;
+ use TicketLinks;
+
+ protected $defaultAttributes = ['class' => 'history-list'];
+
+ /** @var ResultSet */
+ protected $data;
+
+ public function __construct(ResultSet $data)
+ {
+ parent::__construct($data);
+ }
+
+ protected function init()
+ {
+ $this->data = $this->getIterator($this->data);
+ $this->setDetailUrl(Url::fromPath('icingadb/event'));
+ }
+
+ protected function getItemClass(): string
+ {
+ switch ($this->getViewMode()) {
+ case 'minimal':
+ return HistoryListItemMinimal::class;
+ case 'detailed':
+ return HistoryListItemDetailed::class;
+ default:
+ return HistoryListItem::class;
+ }
+ }
+
+ protected function assemble()
+ {
+ $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..b516a4e
--- /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()
+ {
+ 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..0c90fea
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/HostDetailHeader.php
@@ -0,0 +1,72 @@
+<?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\CheckAttempt;
+use Icinga\Module\Icingadb\Widget\StateChange;
+use ipl\Html\BaseHtmlElement;
+use ipl\Web\Widget\Icon;
+use ipl\Web\Widget\StateBall;
+
+class HostDetailHeader extends HostListItemMinimal
+{
+ protected function getStateBallSize(): string
+ {
+ return '';
+ }
+
+ protected function assembleVisual(BaseHtmlElement $visual)
+ {
+ 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');
+ }
+ }
+
+ if ($this->state->is_handled) {
+ $currentStateBall = $stateChange->ensureAssembled()->getContent()[1];
+ $currentStateBall->addHtml(new Icon($this->getHandledIcon()));
+ $currentStateBall->getAttributes()->add('class', 'handled');
+ }
+
+ $visual->addHtml($stateChange);
+ }
+
+ protected function assemble()
+ {
+ $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..51d218e
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/HostList.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;
+
+/**
+ * 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':
+ return HostListItemDetailed::class;
+ case 'objectHeader':
+ return HostDetailHeader::class;
+ default:
+ return HostListItem::class;
+ }
+ }
+
+ protected function init()
+ {
+ $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..a2f1909
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/HostListItemDetailed.php
@@ -0,0 +1,103 @@
+<?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)
+ {
+ $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));
+ }
+ }
+
+ $footer->addHtml($statusIcons);
+ $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/HostgroupList.php b/library/Icingadb/Widget/ItemList/HostgroupList.php
new file mode 100644
index 0000000..e6c8279
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/HostgroupList.php
@@ -0,0 +1,33 @@
+<?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\ViewMode;
+use Icinga\Module\Icingadb\Common\BaseItemList;
+use ipl\Web\Url;
+
+class HostgroupList extends BaseItemList
+{
+ use NoSubjectLink;
+ use ViewMode;
+
+ protected $defaultAttributes = ['class' => 'hostgroup-list item-table'];
+
+ protected function init()
+ {
+ parent::init();
+
+ $this->getAttributes()->get('class')->removeValue('item-list');
+ $this->setDetailUrl(Url::fromPath('icingadb/hostgroup'));
+ }
+
+ protected function getItemClass(): string
+ {
+ $this->addAttributes(['class' => $this->getViewMode()]);
+
+ return HostgroupListItem::class;
+ }
+}
diff --git a/library/Icingadb/Widget/ItemList/HostgroupListItem.php b/library/Icingadb/Widget/ItemList/HostgroupListItem.php
new file mode 100644
index 0000000..5df0432
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/HostgroupListItem.php
@@ -0,0 +1,84 @@
+<?php
+
+/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\Icingadb\Widget\ItemList;
+
+use Icinga\Module\Icingadb\Common\BaseTableRowItem;
+use Icinga\Module\Icingadb\Common\Links;
+use Icinga\Module\Icingadb\Common\NoSubjectLink;
+use Icinga\Module\Icingadb\Model\Hostgroup;
+use Icinga\Module\Icingadb\Widget\Detail\HostStatistics;
+use Icinga\Module\Icingadb\Widget\Detail\ServiceStatistics;
+use ipl\Html\Attributes;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\HtmlDocument;
+use ipl\Html\HtmlElement;
+use ipl\Html\Text;
+use ipl\Stdlib\Filter;
+use ipl\Web\Widget\Link;
+
+/**
+ * Hostgroup item of a hostgroup list. Represents one database row.
+ *
+ * @property Hostgroup $item
+ * @property HostgroupList $list
+ */
+class HostgroupListItem extends BaseTableRowItem
+{
+ use NoSubjectLink;
+
+ protected function init()
+ {
+ $this->setNoSubjectLink($this->list->getNoSubjectLink());
+ $this->list->addDetailFilterAttribute($this, Filter::equal('name', $this->item->name));
+ }
+
+ protected function assembleColumns(HtmlDocument $columns)
+ {
+ $hostStats = new HostStatistics($this->item);
+
+ $hostStats->setBaseFilter(Filter::equal('hostgroup.name', $this->item->name));
+ if ($this->list->hasBaseFilter()) {
+ $hostStats->setBaseFilter(
+ Filter::all($hostStats->getBaseFilter(), $this->list->getBaseFilter())
+ );
+ }
+
+ $columns->addFrom($hostStats, function (BaseHtmlElement $item) {
+ $item->getAttributes()->add(['class' => 'col']);
+ $item->setTag('div');
+ return $item;
+ });
+
+ $serviceStats = new ServiceStatistics($this->item);
+
+ $serviceStats->setBaseFilter(Filter::equal('hostgroup.name', $this->item->name));
+ if ($this->list->hasBaseFilter()) {
+ $serviceStats->setBaseFilter(
+ Filter::all($serviceStats->getBaseFilter(), $this->list->getBaseFilter())
+ );
+ }
+
+ $columns->addFrom($serviceStats, function (BaseHtmlElement $item) {
+ $item->getAttributes()->add(['class' => 'col']);
+ $item->setTag('div');
+ return $item;
+ });
+ }
+
+ protected function assembleTitle(BaseHtmlElement $title)
+ {
+ $title->addHtml(
+ $this->getNoSubjectLink()
+ ? new HtmlElement(
+ 'span',
+ Attributes::create(['class' => 'subject']),
+ Text::create($this->item->display_name)
+ )
+ : new Link($this->item->display_name, Links::hostgroup($this->item), ['class' => 'subject']),
+ new HtmlElement('br'),
+ Text::create($this->item->name)
+ );
+ }
+}
diff --git a/library/Icingadb/Widget/ItemList/NotificationList.php b/library/Icingadb/Widget/ItemList/NotificationList.php
new file mode 100644
index 0000000..8c95d26
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/NotificationList.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\CaptionDisabled;
+use Icinga\Module\Icingadb\Common\LoadMore;
+use Icinga\Module\Icingadb\Common\NoSubjectLink;
+use Icinga\Module\Icingadb\Common\ViewMode;
+use Icinga\Module\Icingadb\Common\BaseItemList;
+use ipl\Orm\ResultSet;
+use ipl\Web\Url;
+
+class NotificationList extends BaseItemList
+{
+ use CaptionDisabled;
+ use NoSubjectLink;
+ use ViewMode;
+ use LoadMore;
+
+ protected $defaultAttributes = ['class' => 'notification-list'];
+
+ /** @var ResultSet */
+ protected $data;
+
+ public function __construct(ResultSet $data)
+ {
+ parent::__construct($data);
+ }
+
+ protected function init()
+ {
+ $this->data = $this->getIterator($this->data);
+ $this->setDetailUrl(Url::fromPath('icingadb/event'));
+ }
+
+ protected function getItemClass(): string
+ {
+ switch ($this->getViewMode()) {
+ case 'minimal':
+ return NotificationListItemMinimal::class;
+ case 'detailed':
+ return NotificationListItemDetailed::class;
+ default:
+ return NotificationListItem::class;
+ }
+ }
+
+ protected function assemble()
+ {
+ $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..dcda5fd
--- /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()
+ {
+ 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..6036929
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/ServiceDetailHeader.php
@@ -0,0 +1,72 @@
+<?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\CheckAttempt;
+use Icinga\Module\Icingadb\Widget\StateChange;
+use ipl\Html\BaseHtmlElement;
+use ipl\Web\Widget\Icon;
+use ipl\Web\Widget\StateBall;
+
+class ServiceDetailHeader extends ServiceListItemMinimal
+{
+ protected function getStateBallSize(): string
+ {
+ return '';
+ }
+
+ protected function assembleVisual(BaseHtmlElement $visual)
+ {
+ 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');
+ }
+ }
+
+ if ($this->state->is_handled) {
+ $currentStateBall = $stateChange->ensureAssembled()->getContent()[1];
+ $currentStateBall->addHtml(new Icon($this->getHandledIcon()));
+ $currentStateBall->getAttributes()->add('class', 'handled');
+ }
+
+ $visual->addHtml($stateChange);
+ }
+
+ protected function assemble()
+ {
+ $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..11febb0
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/ServiceList.php
@@ -0,0 +1,33 @@
+<?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':
+ return ServiceListItemDetailed::class;
+ case 'objectHeader':
+ return ServiceDetailHeader::class;
+ default:
+ return ServiceListItem::class;
+ }
+ }
+
+ protected function init()
+ {
+ $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..a068beb
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/ServiceListItemDetailed.php
@@ -0,0 +1,107 @@
+<?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)
+ {
+ $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));
+ }
+ }
+
+ $footer->addHtml($statusIcons);
+ $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/ServicegroupList.php b/library/Icingadb/Widget/ItemList/ServicegroupList.php
new file mode 100644
index 0000000..fa612f1
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/ServicegroupList.php
@@ -0,0 +1,33 @@
+<?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\ViewMode;
+use Icinga\Module\Icingadb\Common\BaseItemList;
+use ipl\Web\Url;
+
+class ServicegroupList extends BaseItemList
+{
+ use NoSubjectLink;
+ use ViewMode;
+
+ protected $defaultAttributes = ['class' => 'servicegroup-list item-table'];
+
+ protected function init()
+ {
+ parent::init();
+
+ $this->getAttributes()->get('class')->removeValue('item-list');
+ $this->setDetailUrl(Url::fromPath('icingadb/servicegroup'));
+ }
+
+ protected function getItemClass(): string
+ {
+ $this->addAttributes(['class' => $this->getViewMode()]);
+
+ return ServicegroupListItem::class;
+ }
+}
diff --git a/library/Icingadb/Widget/ItemList/ServicegroupListItem.php b/library/Icingadb/Widget/ItemList/ServicegroupListItem.php
new file mode 100644
index 0000000..6a8320c
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/ServicegroupListItem.php
@@ -0,0 +1,68 @@
+<?php
+
+/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\Icingadb\Widget\ItemList;
+
+use Icinga\Module\Icingadb\Common\BaseTableRowItem;
+use Icinga\Module\Icingadb\Common\Links;
+use Icinga\Module\Icingadb\Common\NoSubjectLink;
+use Icinga\Module\Icingadb\Model\Servicegroup;
+use Icinga\Module\Icingadb\Widget\Detail\ServiceStatistics;
+use ipl\Html\Attributes;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\HtmlDocument;
+use ipl\Html\HtmlElement;
+use ipl\Html\Text;
+use ipl\Stdlib\Filter;
+use ipl\Web\Widget\Link;
+
+/**
+ * Servicegroup item of a servicegroup list. Represents one database row.
+ *
+ * @property Servicegroup $item
+ * @property ServicegroupList $list
+ */
+class ServicegroupListItem extends BaseTableRowItem
+{
+ use NoSubjectLink;
+
+ protected function init()
+ {
+ $this->setNoSubjectLink($this->list->getNoSubjectLink());
+ $this->list->addDetailFilterAttribute($this, Filter::equal('name', $this->item->name));
+ }
+
+ protected function assembleColumns(HtmlDocument $columns)
+ {
+ $serviceStats = new ServiceStatistics($this->item);
+
+ $serviceStats->setBaseFilter(Filter::equal('servicegroup.name', $this->item->name));
+ if ($this->list->hasBaseFilter()) {
+ $serviceStats->setBaseFilter(
+ Filter::all($serviceStats->getBaseFilter(), $this->list->getBaseFilter())
+ );
+ }
+
+ $columns->addFrom($serviceStats, function (BaseHtmlElement $item) {
+ $item->getAttributes()->add(['class' => 'col']);
+ $item->setTag('div');
+ return $item;
+ });
+ }
+
+ protected function assembleTitle(BaseHtmlElement $title)
+ {
+ $title->addHtml(
+ $this->getNoSubjectLink()
+ ? new HtmlElement(
+ 'span',
+ Attributes::create(['class' => 'subject']),
+ Text::create($this->item->display_name)
+ )
+ : new Link($this->item->display_name, Links::servicegroup($this->item), ['class' => 'subject']),
+ new HtmlElement('br'),
+ Text::create($this->item->name)
+ );
+ }
+}
diff --git a/library/Icingadb/Widget/ItemList/StateList.php b/library/Icingadb/Widget/ItemList/StateList.php
new file mode 100644
index 0000000..cf6ec0b
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/StateList.php
@@ -0,0 +1,31 @@
+<?php
+
+/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\Icingadb\Widget\ItemList;
+
+use Icinga\Module\Icingadb\Common\BaseItemList;
+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;
+
+abstract class StateList extends BaseItemList
+{
+ use ViewMode;
+ use NoSubjectLink;
+
+ protected function assemble()
+ {
+ $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..d5eb4fd
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/StateListItem.php
@@ -0,0 +1,129 @@
+<?php
+
+/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\Icingadb\Widget\ItemList;
+
+use Icinga\Module\Icingadb\Common\BaseListItem;
+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\EmptyState;
+use Icinga\Module\Icingadb\Widget\IconImage;
+use Icinga\Module\Icingadb\Widget\PluginOutputContainer;
+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 State The state of the item */
+ protected $state;
+
+ protected function init()
+ {
+ $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;
+
+ protected function assembleCaption(BaseHtmlElement $caption)
+ {
+ 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)
+ {
+ 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)
+ {
+ $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)
+ {
+ $stateBall = new StateBall($this->state->getStateText(), $this->getStateBallSize());
+
+ if ($this->state->is_handled) {
+ $stateBall->addHtml(new Icon($this->getHandledIcon()));
+ $stateBall->getAttributes()->add('class', 'handled');
+ } elseif ($this->state->getStateText() === 'pending' && $this->state->in_downtime) {
+ $stateBall->addHtml(new Icon($this->getHandledIcon()));
+ }
+
+ $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()
+ {
+ if ($this->state->is_overdue) {
+ $since = new TimeSince($this->state->next_update);
+ $since->prepend(t('Overdue') . ' ');
+ $since->prependHtml(new Icon(Icons::WARNING));
+ return $since;
+ } elseif ($this->state->last_state_change > 0) {
+ return new TimeSince($this->state->last_state_change);
+ }
+ }
+
+ protected function getHandledIcon(): string
+ {
+ switch (true) {
+ case $this->state->is_acknowledged:
+ return Icons::IS_ACKNOWLEDGED;
+ case $this->state->in_downtime:
+ return Icons::IN_DOWNTIME;
+ case $this->state->is_flapping:
+ return Icons::IS_FLAPPING;
+ default:
+ return Icons::HOST_DOWN;
+ }
+ }
+
+ protected function assemble()
+ {
+ if ($this->state->is_overdue) {
+ $this->addAttributes(['class' => 'overdue']);
+ }
+
+ parent::assemble();
+ }
+}
diff --git a/library/Icingadb/Widget/ItemList/UserList.php b/library/Icingadb/Widget/ItemList/UserList.php
new file mode 100644
index 0000000..826a467
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/UserList.php
@@ -0,0 +1,29 @@
+<?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\BaseItemList;
+use ipl\Web\Url;
+
+class UserList extends BaseItemList
+{
+ use NoSubjectLink;
+
+ protected $defaultAttributes = ['class' => 'user-list item-table'];
+
+ protected function init()
+ {
+ parent::init();
+
+ $this->getAttributes()->get('class')->removeValue('item-list');
+ $this->setDetailUrl(Url::fromPath('icingadb/user'));
+ }
+
+ protected function getItemClass(): string
+ {
+ return UserListItem::class;
+ }
+}
diff --git a/library/Icingadb/Widget/ItemList/UserListItem.php b/library/Icingadb/Widget/ItemList/UserListItem.php
new file mode 100644
index 0000000..e43692f
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/UserListItem.php
@@ -0,0 +1,62 @@
+<?php
+
+/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\Icingadb\Widget\ItemList;
+
+use Icinga\Module\Icingadb\Common\BaseTableRowItem;
+use Icinga\Module\Icingadb\Common\Links;
+use Icinga\Module\Icingadb\Common\NoSubjectLink;
+use Icinga\Module\Icingadb\Model\User;
+use ipl\Html\Attributes;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\HtmlDocument;
+use ipl\Html\HtmlElement;
+use ipl\Html\Text;
+use ipl\Stdlib\Filter;
+use ipl\Web\Widget\Link;
+
+/**
+ * User item of a user list. Represents one database row.
+ *
+ * @property User $item
+ * @property UserList $list
+ */
+class UserListItem extends BaseTableRowItem
+{
+ use NoSubjectLink;
+
+ protected function init()
+ {
+ $this->setNoSubjectLink($this->list->getNoSubjectLink());
+ $this->list->addDetailFilterAttribute($this, Filter::equal('name', $this->item->name));
+ }
+
+ protected function assembleVisual(BaseHtmlElement $visual)
+ {
+ $visual->addHtml(new HtmlElement(
+ 'div',
+ Attributes::create(['class' => 'user-ball']),
+ Text::create($this->item->display_name[0])
+ ));
+ }
+
+ protected function assembleTitle(BaseHtmlElement $title)
+ {
+ $title->addHtml(
+ $this->getNoSubjectLink()
+ ? new HtmlElement(
+ 'span',
+ Attributes::create(['class' => 'subject']),
+ Text::create($this->item->display_name)
+ )
+ : new Link($this->item->display_name, Links::user($this->item), ['class' => 'subject']),
+ new HtmlElement('br'),
+ Text::create($this->item->name)
+ );
+ }
+
+ protected function assembleColumns(HtmlDocument $columns)
+ {
+ }
+}
diff --git a/library/Icingadb/Widget/ItemList/UsergroupList.php b/library/Icingadb/Widget/ItemList/UsergroupList.php
new file mode 100644
index 0000000..2e95368
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/UsergroupList.php
@@ -0,0 +1,29 @@
+<?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\BaseItemList;
+use ipl\Web\Url;
+
+class UsergroupList extends BaseItemList
+{
+ use NoSubjectLink;
+
+ protected $defaultAttributes = ['class' => 'usergroup-list item-table'];
+
+ protected function init()
+ {
+ parent::init();
+
+ $this->getAttributes()->get('class')->removeValue('item-list');
+ $this->setDetailUrl(Url::fromPath('icingadb/usergroup'));
+ }
+
+ protected function getItemClass(): string
+ {
+ return UsergroupListItem::class;
+ }
+}
diff --git a/library/Icingadb/Widget/ItemList/UsergroupListItem.php b/library/Icingadb/Widget/ItemList/UsergroupListItem.php
new file mode 100644
index 0000000..f8c800d
--- /dev/null
+++ b/library/Icingadb/Widget/ItemList/UsergroupListItem.php
@@ -0,0 +1,62 @@
+<?php
+
+/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */
+
+namespace Icinga\Module\Icingadb\Widget\ItemList;
+
+use Icinga\Module\Icingadb\Common\BaseTableRowItem;
+use Icinga\Module\Icingadb\Common\Links;
+use Icinga\Module\Icingadb\Common\NoSubjectLink;
+use Icinga\Module\Icingadb\Model\Usergroup;
+use ipl\Html\Attributes;
+use ipl\Html\BaseHtmlElement;
+use ipl\Html\HtmlDocument;
+use ipl\Html\HtmlElement;
+use ipl\Html\Text;
+use ipl\Stdlib\Filter;
+use ipl\Web\Widget\Link;
+
+/**
+ * Usergroup item of a usergroup list. Represents one database row.
+ *
+ * @property Usergroup $item
+ * @property UsergroupList $list
+ */
+class UsergroupListItem extends BaseTableRowItem
+{
+ use NoSubjectLink;
+
+ protected function init()
+ {
+ $this->setNoSubjectLink($this->list->getNoSubjectLink());
+ $this->list->addDetailFilterAttribute($this, Filter::equal('name', $this->item->name));
+ }
+
+ protected function assembleVisual(BaseHtmlElement $visual)
+ {
+ $visual->addHtml(new HtmlElement(
+ 'div',
+ Attributes::create(['class' => 'usergroup-ball']),
+ Text::create($this->item->display_name[0])
+ ));
+ }
+
+ protected function assembleTitle(BaseHtmlElement $title)
+ {
+ $title->addHtml(
+ $this->getNoSubjectLink()
+ ? new HtmlElement(
+ 'span',
+ Attributes::create(['class' => 'subject']),
+ Text::create($this->item->display_name)
+ )
+ : new Link($this->item->display_name, Links::usergroup($this->item), ['class' => 'subject']),
+ new HtmlElement('br'),
+ Text::create($this->item->name)
+ );
+ }
+
+ protected function assembleColumns(HtmlDocument $columns)
+ {
+ }
+}