Introduce HistoryRenderer

- Remove now obsolete ItemList classes
This commit is contained in:
Sukhwinder Dhillon 2025-03-24 13:34:08 +01:00 committed by Johannes Meyer
parent 3252ff8925
commit 183d5ee7ba
14 changed files with 470 additions and 545 deletions

View file

@ -4,12 +4,10 @@
namespace Icinga\Module\Icingadb\Controllers;
use ArrayObject;
use Icinga\Module\Icingadb\Model\History;
use Icinga\Module\Icingadb\Web\Controller;
use Icinga\Module\Icingadb\Widget\Detail\EventDetail;
use Icinga\Module\Icingadb\Widget\ItemList\HistoryList;
use ipl\Orm\ResultSet;
use Icinga\Module\Icingadb\Widget\Detail\ObjectHeader;
use ipl\Stdlib\Filter;
class EventController extends Controller
@ -60,12 +58,7 @@ class EventController extends Controller
public function indexAction()
{
$this->addControl((new HistoryList(new ResultSet(new ArrayObject([$this->event]))))
->setViewMode('minimal')
->setPageSize(1)
->setCaptionDisabled()
->setNoSubjectLink()
->setDetailActionsDisabled());
$this->addContent((new EventDetail($this->event))->setTicketLinkEnabled());
$this->addControl(new ObjectHeader($this->event));
$this->addContent(new EventDetail($this->event));
}
}

View file

@ -8,8 +8,8 @@ use GuzzleHttp\Psr7\ServerRequest;
use Icinga\Module\Icingadb\Model\History;
use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions;
use Icinga\Module\Icingadb\Web\Controller;
use Icinga\Module\Icingadb\Widget\ItemList\HistoryList;
use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher;
use Icinga\Module\Icingadb\Widget\ItemList\LoadMoreObjectList;
use ipl\Stdlib\Filter;
use ipl\Web\Control\LimitControl;
use ipl\Web\Control\SortControl;
@ -98,7 +98,8 @@ class HistoryController extends Controller
->onlyWith($preserveParams)
->setFilter($filter);
$historyList = (new HistoryList($history->execute()))
$historyList = (new LoadMoreObjectList($history->execute()))
->setDetailUrl(Url::fromPath('icingadb/event'))
->setPageSize($limitControl->getLimit())
->setViewMode($viewModeSwitcher->getViewMode())
->setLoadMoreUrl($url->setParam('before', $before));

View file

@ -28,8 +28,8 @@ use Icinga\Module\Icingadb\Widget\Detail\HostInspectionDetail;
use Icinga\Module\Icingadb\Widget\Detail\HostMetaInfo;
use Icinga\Module\Icingadb\Widget\Detail\ObjectHeader;
use Icinga\Module\Icingadb\Widget\Detail\QuickActions;
use Icinga\Module\Icingadb\Widget\ItemList\LoadMoreObjectList;
use Icinga\Module\Icingadb\Widget\ItemList\ObjectList;
use Icinga\Module\Icingadb\Widget\ItemList\HistoryList;
use ipl\Orm\Query;
use ipl\Sql\Expression;
use ipl\Sql\Filter\Exists;
@ -167,7 +167,8 @@ class HostController extends Controller
$this->addControl($limitControl);
$this->addControl($viewModeSwitcher);
$historyList = (new HistoryList($history->execute()))
$historyList = (new LoadMoreObjectList($history->execute()))
->setDetailUrl(Url::fromPath('icingadb/event'))
->setViewMode($viewModeSwitcher->getViewMode())
->setPageSize($limitControl->getLimit())
->setLoadMoreUrl($url->setParam('before', $before));

View file

@ -25,8 +25,8 @@ use Icinga\Module\Icingadb\Widget\Detail\QuickActions;
use Icinga\Module\Icingadb\Widget\Detail\ServiceDetail;
use Icinga\Module\Icingadb\Widget\Detail\ServiceInspectionDetail;
use Icinga\Module\Icingadb\Widget\Detail\ServiceMetaInfo;
use Icinga\Module\Icingadb\Widget\ItemList\LoadMoreObjectList;
use Icinga\Module\Icingadb\Widget\ItemList\ObjectList;
use Icinga\Module\Icingadb\Widget\ItemList\HistoryList;
use ipl\Orm\Query;
use ipl\Sql\Expression;
use ipl\Stdlib\Filter;
@ -313,7 +313,8 @@ class ServiceController extends Controller
$this->addControl($limitControl);
$this->addControl($viewModeSwitcher);
$historyList = (new HistoryList($history->execute()))
$historyList = (new LoadMoreObjectList($history->execute()))
->setDetailUrl(Url::fromPath('icingadb/event'))
->setViewMode($viewModeSwitcher->getViewMode())
->setPageSize($limitControl->getLimit())
->setLoadMoreUrl($url->setParam('before', $before));

View file

@ -0,0 +1,440 @@
<?php
/* Icinga DB Web | (c) 2025 Icinga GmbH | GPLv2 */
namespace Icinga\Module\Icingadb\View;
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\ServiceLink;
use Icinga\Module\Icingadb\Common\ServiceStates;
use Icinga\Module\Icingadb\Common\TicketLinks;
use Icinga\Module\Icingadb\Model\History;
use Icinga\Module\Icingadb\Util\PluginOutput;
use Icinga\Module\Icingadb\Widget\CheckAttempt;
use Icinga\Module\Icingadb\Widget\MarkdownLine;
use Icinga\Module\Icingadb\Widget\PluginOutputContainer;
use Icinga\Module\Icingadb\Widget\StateChange;
use ipl\Html\Attributes;
use ipl\Html\HtmlDocument;
use ipl\Html\HtmlElement;
use ipl\Html\Text;
use ipl\I18n\Translation;
use ipl\Web\Common\ItemRenderer;
use ipl\Web\Widget\EmptyState;
use ipl\Web\Widget\Icon;
use ipl\Web\Widget\Link;
use ipl\Web\Widget\StateBall;
use ipl\Web\Widget\TimeAgo;
/** @implements ItemRenderer<History> */
class HistoryRenderer implements ItemRenderer
{
use Translation;
use TicketLinks;
use HostLink;
use ServiceLink;
public function assembleAttributes($item, Attributes $attributes, string $layout): void
{
$attributes->get('class')->addValue('history');
}
public function assembleVisual($item, HtmlDocument $visual, string $layout): void
{
$ballSize = StateBall::SIZE_LARGE;
if ($layout === 'minimal' || $layout === 'header') {
$ballSize = StateBall::SIZE_BIG;
}
switch ($item->event_type) {
case 'comment_add':
$visual->addHtml(
HtmlElement::create(
'div',
['class' => ['icon-ball', 'ball-size-' . $ballSize]],
new Icon(Icons::COMMENT)
)
);
break;
case 'comment_remove':
case 'downtime_end':
case 'ack_clear':
$visual->addHtml(
HtmlElement::create(
'div',
['class' => ['icon-ball', 'ball-size-' . $ballSize]],
new Icon(Icons::REMOVE)
)
);
break;
case 'downtime_start':
$visual->addHtml(
HtmlElement::create(
'div',
['class' => ['icon-ball', 'ball-size-' . $ballSize]],
new Icon(Icons::IN_DOWNTIME)
)
);
break;
case 'ack_set':
$visual->addHtml(
HtmlElement::create(
'div',
['class' => ['icon-ball', 'ball-size-' . $ballSize]],
new Icon(Icons::IS_ACKNOWLEDGED)
)
);
break;
case 'flapping_end':
case 'flapping_start':
$visual->addHtml(
HtmlElement::create(
'div',
['class' => ['icon-ball', 'ball-size-' . $ballSize]],
new Icon(Icons::IS_FLAPPING)
)
);
break;
case 'notification':
$visual->addHtml(
HtmlElement::create(
'div',
['class' => ['icon-ball', 'ball-size-' . $ballSize]],
new Icon(Icons::NOTIFICATION)
)
);
break;
case 'state_change':
if ($item->state->state_type === 'soft') {
$stateType = 'soft_state';
$previousStateType = 'previous_soft_state';
if ($item->state->previous_soft_state === 0) {
$previousStateType = 'hard_state';
}
$visual->addHtml(
new CheckAttempt(
(int) $item->state->check_attempt,
(int) $item->state->max_check_attempts
)
);
} else {
$stateType = 'hard_state';
$previousStateType = 'previous_hard_state';
if ($item->state->hard_state === $item->state->previous_hard_state) {
$previousStateType = 'previous_soft_state';
}
}
if ($item->object_type === 'host') {
$state = HostStates::text($item->state->$stateType);
$previousState = HostStates::text($item->state->$previousStateType);
} else {
$state = ServiceStates::text($item->state->$stateType);
$previousState = ServiceStates::text($item->state->$previousStateType);
}
$stateChange = new StateChange($state, $previousState);
if ($stateType === 'soft_state') {
$stateChange->setCurrentStateBallSize(StateBall::SIZE_MEDIUM_LARGE);
}
if ($previousStateType === 'previous_soft_state') {
$stateChange->setPreviousStateBallSize(StateBall::SIZE_MEDIUM_LARGE);
if ($stateType === 'soft_state') {
$visual->getAttributes()->add('class', 'small-state-change');
}
}
$visual->prependHtml($stateChange);
break;
}
}
public function assembleTitle($item, HtmlDocument $title, string $layout): void
{
switch ($item->event_type) {
case 'comment_add':
$subjectLabel = $this->translate('Comment added');
break;
case 'comment_remove':
if (! empty($item->comment->removed_by)) {
if ($item->comment->removed_by !== $item->comment->author) {
$subjectLabel = sprintf(
$this->translate('Comment removed by %s', '..<username>'),
$item->comment->removed_by
);
} else {
$subjectLabel = $this->translate('Comment removed by author');
}
} elseif (isset($item->comment->expire_time)) {
$subjectLabel = $this->translate('Comment expired');
} else {
$subjectLabel = $this->translate('Comment removed');
}
break;
case 'downtime_end':
if (! empty($item->downtime->cancelled_by)) {
if ($item->downtime->cancelled_by !== $item->downtime->author) {
$subjectLabel = sprintf(
$this->translate('Downtime cancelled by %s', '..<username>'),
$item->downtime->cancelled_by
);
} else {
$subjectLabel = $this->translate('Downtime cancelled by author');
}
} elseif ($item->downtime->has_been_cancelled === 'y') {
$subjectLabel = $this->translate('Downtime cancelled');
} else {
$subjectLabel = $this->translate('Downtime ended');
}
break;
case 'downtime_start':
$subjectLabel = $this->translate('Downtime started');
break;
case 'flapping_start':
$subjectLabel = $this->translate('Flapping started');
break;
case 'flapping_end':
$subjectLabel = $this->translate('Flapping stopped');
break;
case 'ack_set':
$subjectLabel = $this->translate('Acknowledgement set');
break;
case 'ack_clear':
if (! empty($item->acknowledgement->cleared_by)) {
if ($item->acknowledgement->cleared_by !== $item->acknowledgement->author) {
$subjectLabel = sprintf(
$this->translate('Acknowledgement cleared by %s', '..<username>'),
$item->acknowledgement->cleared_by
);
} else {
$subjectLabel = $this->translate('Acknowledgement cleared by author');
}
} elseif (isset($item->acknowledgement->expire_time)) {
$subjectLabel = $this->translate('Acknowledgement expired');
} else {
$subjectLabel = $this->translate('Acknowledgement cleared');
}
break;
case 'notification':
$subjectLabel = isset($item->notification->type) ? sprintf(
NotificationRenderer::phraseForType($item->notification->type),
ucfirst($item->object_type)
) : $item->event_type;
break;
case 'state_change':
$state = $item->state->state_type === 'hard'
? $item->state->hard_state
: $item->state->soft_state;
if ($state === 0) {
if ($item->object_type === 'service') {
$subjectLabel = $this->translate('Service recovered');
} else {
$subjectLabel = $this->translate('Host recovered');
}
} else {
if ($item->state->state_type === 'hard') {
$subjectLabel = $this->translate('Hard state changed');
} else {
$subjectLabel = $this->translate('Soft state changed');
}
}
break;
default:
$subjectLabel = $item->event_type;
break;
}
if ($layout === 'header') {
$title->addHtml(HtmlElement::create('span', ['class' => 'subject'], $subjectLabel));
} else {
$title->addHtml(new Link($subjectLabel, Links::event($item), ['class' => 'subject']));
}
if ($item->object_type === 'host') {
if (isset($item->host->id)) {
$link = $this->createHostLink($item->host, true);
}
} else {
if (isset($item->host->id, $item->service->id)) {
$link = $this->createServiceLink($item->service, $item->host, true);
}
}
$title->addHtml(Text::create(' '));
if (isset($link)) {
$title->addHtml($link);
}
}
public function assembleCaption($item, HtmlDocument $caption, string $layout): void
{
switch ($item->event_type) {
case 'comment_add':
case 'comment_remove':
$markdownLine = new MarkdownLine($this->createTicketLinks($item->comment->comment));
$caption->getAttributes()->add($markdownLine->getAttributes());
$caption->add([
new Icon(Icons::USER),
$item->comment->author,
': '
])->addFrom($markdownLine);
break;
case 'downtime_end':
case 'downtime_start':
$markdownLine = new MarkdownLine($this->createTicketLinks($item->downtime->comment));
$caption->getAttributes()->add($markdownLine->getAttributes());
$caption->add([
new Icon(Icons::USER),
$item->downtime->author,
': '
])->addFrom($markdownLine);
break;
case 'flapping_start':
$caption
->add(
sprintf(
$this->translate('State Change Rate: %.2f%%; Start Threshold: %.2f%%'),
$item->flapping->percent_state_change_start,
$item->flapping->flapping_threshold_high
)
)
->getAttributes()
->add('class', 'plugin-output');
break;
case 'flapping_end':
$caption
->add(
sprintf(
$this->translate('State Change Rate: %.2f%%; End Threshold: %.2f%%; Flapping for %s'),
$item->flapping->percent_state_change_end,
$item->flapping->flapping_threshold_low,
isset($item->flapping->end_time)
? DateFormatter::formatDuration(
$item->flapping->end_time->getTimestamp()
- $item->flapping->start_time->getTimestamp()
)
: $this->translate('n. a.')
)
)
->getAttributes()
->add('class', 'plugin-output');
break;
case 'ack_clear':
case 'ack_set':
if (! isset($item->acknowledgement->comment) && ! isset($item->acknowledgement->author)) {
$caption->addHtml(
new EmptyState(
$this->translate('This acknowledgement was set before Icinga DB history recording')
)
);
} else {
$markdownLine = new MarkdownLine($this->createTicketLinks($item->acknowledgement->comment));
$caption->getAttributes()->add($markdownLine->getAttributes());
$caption->add([
new Icon(Icons::USER),
$item->acknowledgement->author,
': '
])->addFrom($markdownLine);
}
break;
case 'notification':
if (! empty($item->notification->author)) {
$caption->add([
new Icon(Icons::USER),
$item->notification->author,
': ',
$item->notification->text
]);
} else {
$commandName = $item->object_type === 'host'
? $item->host->checkcommand_name
: $item->service->checkcommand_name;
if (isset($commandName)) {
if (empty($item->notification->text)) {
$caption->addHtml(new EmptyState(t('Output unavailable.')));
} else {
$caption->addHtml(
new PluginOutputContainer(
(new PluginOutput($item->notification->text))
->setCommandName($commandName)
)
);
}
} else {
$caption->addHtml(
new EmptyState($this->translate('Waiting for Icinga DB to synchronize the config.'))
);
}
}
break;
case 'state_change':
$commandName = $item->object_type === 'host'
? $item->host->checkcommand_name
: $item->service->checkcommand_name;
if (isset($commandName)) {
if (empty($item->state->output)) {
$caption->addHtml(new EmptyState($this->translate('Output unavailable.')));
} else {
$caption->addHtml(
new PluginOutputContainer(
(new PluginOutput($item->state->output))
->setCommandName($commandName)
)
);
}
} else {
$caption->addHtml(
new EmptyState($this->translate('Waiting for Icinga DB to synchronize the config.'))
);
}
break;
}
}
public function assembleExtendedInfo($item, HtmlDocument $info, string $layout): void
{
$info->addHtml(new TimeAgo($item->event_time->getTimestamp()));
}
public function assembleFooter($item, HtmlDocument $footer, string $layout): void
{
}
public function assemble($item, string $name, HtmlDocument $element, string $layout): bool
{
return false; // no custom sections
}
}

View file

@ -7,6 +7,7 @@ namespace Icinga\Module\Icingadb\Widget\Detail;
use Icinga\Exception\NotImplementedError;
use Icinga\Module\Icingadb\Model\Comment;
use Icinga\Module\Icingadb\Model\Downtime;
use Icinga\Module\Icingadb\Model\History;
use Icinga\Module\Icingadb\Model\Host;
use Icinga\Module\Icingadb\Model\RedundancyGroup;
use Icinga\Module\Icingadb\Model\Service;
@ -14,6 +15,7 @@ use Icinga\Module\Icingadb\Model\User;
use Icinga\Module\Icingadb\Model\Usergroup;
use Icinga\Module\Icingadb\View\CommentRenderer;
use Icinga\Module\Icingadb\View\DowntimeRenderer;
use Icinga\Module\Icingadb\View\HistoryRenderer;
use Icinga\Module\Icingadb\View\HostRenderer;
use Icinga\Module\Icingadb\View\RedundancyGroupRenderer;
use Icinga\Module\Icingadb\View\ServiceRenderer;
@ -65,6 +67,10 @@ class ObjectHeader extends BaseHtmlElement
case $this->object instanceof Downtime:
$renderer = new DowntimeRenderer();
break;
case $this->object instanceof History:
$renderer = new HistoryRenderer();
break;
default:
throw new NotImplementedError('Not implemented');

View file

@ -1,407 +0,0 @@
<?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,
isset($this->item->flapping->end_time)
? DateFormatter::formatDuration(
$this->item->flapping->end_time->getTimestamp()
- $this->item->flapping->start_time->getTimestamp()
)
: t('n. a.')
))
->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 = isset($this->item->notification->type) ? sprintf(
NotificationListItem::phraseForType($this->item->notification->type),
ucfirst($this->item->object_type)
) : $this->item->event_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());
}
}

View file

@ -1,57 +0,0 @@
<?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();
}
}

View file

@ -1,18 +0,0 @@
<?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;
}
}

View file

@ -1,18 +0,0 @@
<?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;
}
}

View file

@ -1,27 +0,0 @@
<?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;
}
}

View file

@ -6,7 +6,9 @@ namespace Icinga\Module\Icingadb\Widget\ItemList;
use Icinga\Exception\NotImplementedError;
use Icinga\Module\Icingadb\Common\LoadMore;
use Icinga\Module\Icingadb\Model\History;
use Icinga\Module\Icingadb\Model\NotificationHistory;
use Icinga\Module\Icingadb\View\HistoryRenderer;
use Icinga\Module\Icingadb\View\NotificationRenderer;
use ipl\Orm\Model;
use ipl\Web\Widget\ItemList;
@ -27,6 +29,8 @@ class LoadMoreObjectList extends ObjectList
ItemList::__construct($data, function (Model $item) {
if ($item instanceof NotificationHistory) {
return new NotificationRenderer();
} elseif ($item instanceof History) {
return new HistoryRenderer();
}
throw new NotImplementedError('Not implemented');

View file

@ -9,6 +9,7 @@ use Icinga\Module\Icingadb\Common\DetailActions;
use Icinga\Module\Icingadb\Model\Comment;
use Icinga\Module\Icingadb\Model\DependencyNode;
use Icinga\Module\Icingadb\Model\Downtime;
use Icinga\Module\Icingadb\Model\History;
use Icinga\Module\Icingadb\Model\Host;
use Icinga\Module\Icingadb\Model\NotificationHistory;
use Icinga\Module\Icingadb\Model\RedundancyGroup;
@ -212,6 +213,11 @@ class ObjectList extends ItemList
case $object instanceof NotificationHistory:
$this->addDetailFilterAttribute($item, Filter::equal('id', bin2hex($object->history->id)));
break;
case $object instanceof History:
$this->addDetailFilterAttribute($item, Filter::equal('id', bin2hex($object->id)));
break;
}

View file

@ -374,9 +374,9 @@ div.show-more {
}
}
.history-list,
.header-item-layout.host,
.header-item-layout.service {
.header-item-layout.service,
.item-layout.history {
.visual.small-state-change .state-change {
padding-top: .25em;
}