mirror of
https://github.com/Icinga/icingadb-web.git
synced 2026-05-28 04:36:06 -04:00
Introduce class LoadMoreObjectList and NotificationRenderer
- Remove now obsolete ItemList classes - Fix load-more element's css - LoadMore: Replace `list-item` css class with new `item-layout` class, as this class is now responsible for list items
This commit is contained in:
parent
c15f32a43f
commit
3252ff8925
9 changed files with 190 additions and 254 deletions
|
|
@ -8,13 +8,11 @@ use GuzzleHttp\Psr7\ServerRequest;
|
|||
use Icinga\Module\Icingadb\Model\NotificationHistory;
|
||||
use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions;
|
||||
use Icinga\Module\Icingadb\Web\Controller;
|
||||
use Icinga\Module\Icingadb\Widget\ItemList\NotificationList;
|
||||
use Icinga\Module\Icingadb\Widget\ItemList\LoadMoreObjectList;
|
||||
use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher;
|
||||
use ipl\Sql\Sql;
|
||||
use ipl\Stdlib\Filter;
|
||||
use ipl\Web\Control\LimitControl;
|
||||
use ipl\Web\Control\SortControl;
|
||||
use ipl\Web\Filter\QueryString;
|
||||
use ipl\Web\Url;
|
||||
|
||||
class NotificationsController extends Controller
|
||||
|
|
@ -94,7 +92,8 @@ class NotificationsController extends Controller
|
|||
->onlyWith($preserveParams)
|
||||
->setFilter($filter);
|
||||
|
||||
$notificationList = (new NotificationList($notifications->execute()))
|
||||
$notificationList = (new LoadMoreObjectList($notifications->execute()))
|
||||
->setDetailUrl(Url::fromPath('icingadb/event'))
|
||||
->setPageSize($limitControl->getLimit())
|
||||
->setViewMode($viewModeSwitcher->getViewMode())
|
||||
->setLoadMoreUrl($url->setParam('before', $before));
|
||||
|
|
|
|||
|
|
@ -1,43 +1,168 @@
|
|||
<?php
|
||||
|
||||
/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */
|
||||
/* Icinga DB Web | (c) 2025 Icinga GmbH | GPLv2 */
|
||||
|
||||
namespace Icinga\Module\Icingadb\Widget\ItemList;
|
||||
namespace Icinga\Module\Icingadb\View;
|
||||
|
||||
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\Model\NotificationHistory;
|
||||
use Icinga\Module\Icingadb\Util\PluginOutput;
|
||||
use Icinga\Module\Icingadb\Widget\PluginOutputContainer;
|
||||
use Icinga\Module\Icingadb\Widget\StateChange;
|
||||
use ipl\Stdlib\Filter;
|
||||
use ipl\Web\Common\BaseListItem;
|
||||
use ipl\Web\Widget\EmptyState;
|
||||
use ipl\Web\Widget\TimeAgo;
|
||||
use InvalidArgumentException;
|
||||
use ipl\Html\BaseHtmlElement;
|
||||
use ipl\Html\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;
|
||||
|
||||
abstract class BaseNotificationListItem extends BaseListItem
|
||||
/** @implements ItemRenderer<NotificationHistory> */
|
||||
class NotificationRenderer implements ItemRenderer
|
||||
{
|
||||
use Translation;
|
||||
use HostLink;
|
||||
use NoSubjectLink;
|
||||
use ServiceLink;
|
||||
|
||||
/** @var NotificationList */
|
||||
protected $list;
|
||||
|
||||
protected function init(): void
|
||||
public function assembleAttributes($item, Attributes $attributes, string $layout): void
|
||||
{
|
||||
$this->setNoSubjectLink($this->list->getNoSubjectLink());
|
||||
$this->list->addDetailFilterAttribute($this, Filter::equal('id', bin2hex($this->item->history->id)));
|
||||
$attributes->get('class')->addValue('notification');
|
||||
}
|
||||
|
||||
public function assembleVisual($item, HtmlDocument $visual, string $layout): void
|
||||
{
|
||||
$ballSize = StateBall::SIZE_LARGE;
|
||||
if ($layout === 'minimal' || $layout === 'header') {
|
||||
$ballSize = StateBall::SIZE_BIG;
|
||||
}
|
||||
|
||||
switch ($item->type) {
|
||||
case 'acknowledgement':
|
||||
$visual->addHtml(HtmlElement::create(
|
||||
'div',
|
||||
['class' => ['icon-ball', 'ball-size-' . $ballSize]],
|
||||
new Icon(Icons::IS_ACKNOWLEDGED)
|
||||
));
|
||||
|
||||
break;
|
||||
case 'custom':
|
||||
$visual->addHtml(HtmlElement::create(
|
||||
'div',
|
||||
['class' => ['icon-ball', 'ball-size-' . $ballSize]],
|
||||
new Icon(Icons::NOTIFICATION)
|
||||
));
|
||||
|
||||
break;
|
||||
case 'downtime_end':
|
||||
case 'downtime_removed':
|
||||
case 'downtime_start':
|
||||
$visual->addHtml(HtmlElement::create(
|
||||
'div',
|
||||
['class' => ['icon-ball', 'ball-size-' . $ballSize]],
|
||||
new Icon(Icons::IN_DOWNTIME)
|
||||
));
|
||||
|
||||
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 'problem':
|
||||
case 'recovery':
|
||||
if ($item->object_type === 'host') {
|
||||
$state = HostStates::text($item->state);
|
||||
$previousHardState = HostStates::text($item->previous_hard_state);
|
||||
} else {
|
||||
$state = ServiceStates::text($item->state);
|
||||
$previousHardState = ServiceStates::text($item->previous_hard_state);
|
||||
}
|
||||
|
||||
$visual->addHtml(new StateChange($state, $previousHardState));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function assembleTitle($item, HtmlDocument $title, string $layout): void
|
||||
{
|
||||
if ($layout === 'header') {
|
||||
$title->addHtml(HtmlElement::create(
|
||||
'span',
|
||||
['class' => 'subject'],
|
||||
sprintf(self::phraseForType($item->type), ucfirst($item->object_type))
|
||||
));
|
||||
} else {
|
||||
$title->addHtml(new Link(
|
||||
sprintf(self::phraseForType($item->type), ucfirst($item->object_type)),
|
||||
Links::event($item->history),
|
||||
['class' => 'subject']
|
||||
));
|
||||
}
|
||||
|
||||
if ($item->object_type === 'host') {
|
||||
$link = $this->createHostLink($item->host, true);
|
||||
} else {
|
||||
$link = $this->createServiceLink($item->service, $item->host, true);
|
||||
}
|
||||
|
||||
$title->addHtml(Text::create(' '), $link);
|
||||
}
|
||||
|
||||
public function assembleCaption($item, HtmlDocument $caption, string $layout): void
|
||||
{
|
||||
if (in_array($item->type, ['flapping_end', 'flapping_start', 'problem', 'recovery'])) {
|
||||
$commandName = $item->object_type === 'host'
|
||||
? $item->host->checkcommand_name
|
||||
: $item->service->checkcommand_name;
|
||||
if (isset($commandName)) {
|
||||
if (empty($item->text)) {
|
||||
$caption->addHtml(new EmptyState($this->translate('Output unavailable.')));
|
||||
} else {
|
||||
$caption->addHtml(new PluginOutputContainer(
|
||||
(new PluginOutput($item->text))
|
||||
->setCommandName($commandName)
|
||||
));
|
||||
}
|
||||
} else {
|
||||
$caption->addHtml(new EmptyState($this->translate('Waiting for Icinga DB to synchronize the config.')));
|
||||
}
|
||||
} else {
|
||||
$caption->add([
|
||||
new Icon(Icons::USER),
|
||||
$item->author,
|
||||
': ',
|
||||
$item->text
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function assembleExtendedInfo($item, HtmlDocument $info, string $layout): void
|
||||
{
|
||||
$info->addHtml(new TimeAgo($item->send_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
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -72,118 +197,4 @@ abstract class BaseNotificationListItem extends BaseListItem
|
|||
throw new InvalidArgumentException(sprintf('Type %s is not a valid notification type', $type));
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected function getStateBallSize();
|
||||
|
||||
protected function assembleCaption(BaseHtmlElement $caption): void
|
||||
{
|
||||
if (in_array($this->item->type, ['flapping_end', 'flapping_start', 'problem', 'recovery'])) {
|
||||
$commandName = $this->item->object_type === 'host'
|
||||
? $this->item->host->checkcommand_name
|
||||
: $this->item->service->checkcommand_name;
|
||||
if (isset($commandName)) {
|
||||
if (empty($this->item->text)) {
|
||||
$caption->addHtml(new EmptyState(t('Output unavailable.')));
|
||||
} else {
|
||||
$caption->addHtml(new PluginOutputContainer(
|
||||
(new PluginOutput($this->item->text))
|
||||
->setCommandName($commandName)
|
||||
));
|
||||
}
|
||||
} else {
|
||||
$caption->addHtml(new EmptyState(t('Waiting for Icinga DB to synchronize the config.')));
|
||||
}
|
||||
} else {
|
||||
$caption->add([
|
||||
new Icon(Icons::USER),
|
||||
$this->item->author,
|
||||
': ',
|
||||
$this->item->text
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function assembleVisual(BaseHtmlElement $visual): void
|
||||
{
|
||||
switch ($this->item->type) {
|
||||
case 'acknowledgement':
|
||||
$visual->addHtml(HtmlElement::create(
|
||||
'div',
|
||||
['class' => ['icon-ball', 'ball-size-' . $this->getStateBallSize()]],
|
||||
new Icon(Icons::IS_ACKNOWLEDGED)
|
||||
));
|
||||
|
||||
break;
|
||||
case 'custom':
|
||||
$visual->addHtml(HtmlElement::create(
|
||||
'div',
|
||||
['class' => ['icon-ball', 'ball-size-' . $this->getStateBallSize()]],
|
||||
new Icon(Icons::NOTIFICATION)
|
||||
));
|
||||
|
||||
break;
|
||||
case 'downtime_end':
|
||||
case 'downtime_removed':
|
||||
case 'downtime_start':
|
||||
$visual->addHtml(HtmlElement::create(
|
||||
'div',
|
||||
['class' => ['icon-ball', 'ball-size-' . $this->getStateBallSize()]],
|
||||
new Icon(Icons::IN_DOWNTIME)
|
||||
));
|
||||
|
||||
break;
|
||||
case 'flapping_end':
|
||||
case 'flapping_start':
|
||||
$visual->addHtml(HtmlElement::create(
|
||||
'div',
|
||||
['class' => ['icon-ball', 'ball-size-' . $this->getStateBallSize()]],
|
||||
new Icon(Icons::IS_FLAPPING)
|
||||
));
|
||||
|
||||
break;
|
||||
case 'problem':
|
||||
case 'recovery':
|
||||
if ($this->item->object_type === 'host') {
|
||||
$state = HostStates::text($this->item->state);
|
||||
$previousHardState = HostStates::text($this->item->previous_hard_state);
|
||||
} else {
|
||||
$state = ServiceStates::text($this->item->state);
|
||||
$previousHardState = ServiceStates::text($this->item->previous_hard_state);
|
||||
}
|
||||
|
||||
$visual->addHtml(new StateChange($state, $previousHardState));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected function assembleTitle(BaseHtmlElement $title): void
|
||||
{
|
||||
if ($this->getNoSubjectLink()) {
|
||||
$title->addHtml(HtmlElement::create(
|
||||
'span',
|
||||
['class' => 'subject'],
|
||||
sprintf(self::phraseForType($this->item->type), ucfirst($this->item->object_type))
|
||||
));
|
||||
} else {
|
||||
$title->addHtml(new Link(
|
||||
sprintf(self::phraseForType($this->item->type), ucfirst($this->item->object_type)),
|
||||
Links::event($this->item->history),
|
||||
['class' => 'subject']
|
||||
));
|
||||
}
|
||||
|
||||
if ($this->item->object_type === 'host') {
|
||||
$link = $this->createHostLink($this->item->host, true);
|
||||
} else {
|
||||
$link = $this->createServiceLink($this->item->service, $this->item->host, true);
|
||||
}
|
||||
|
||||
$title->addHtml(Text::create(' '), $link);
|
||||
}
|
||||
|
||||
protected function createTimestamp(): ?BaseHtmlElement
|
||||
{
|
||||
return new TimeAgo($this->item->send_time->getTimestamp());
|
||||
}
|
||||
}
|
||||
37
library/Icingadb/Widget/ItemList/LoadMoreObjectList.php
Normal file
37
library/Icingadb/Widget/ItemList/LoadMoreObjectList.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/* Icinga DB Web | (c) 2025 Icinga GmbH | GPLv2 */
|
||||
|
||||
namespace Icinga\Module\Icingadb\Widget\ItemList;
|
||||
|
||||
use Icinga\Exception\NotImplementedError;
|
||||
use Icinga\Module\Icingadb\Common\LoadMore;
|
||||
use Icinga\Module\Icingadb\Model\NotificationHistory;
|
||||
use Icinga\Module\Icingadb\View\NotificationRenderer;
|
||||
use ipl\Orm\Model;
|
||||
use ipl\Web\Widget\ItemList;
|
||||
|
||||
/**
|
||||
* LoadMoreObjectList
|
||||
*
|
||||
* Create a list of icingadb objects with Load more link
|
||||
*
|
||||
* @extends ObjectList //TODO: define object type
|
||||
*/
|
||||
class LoadMoreObjectList extends ObjectList
|
||||
{
|
||||
use LoadMore;
|
||||
|
||||
public function __construct($data)
|
||||
{
|
||||
ItemList::__construct($data, function (Model $item) {
|
||||
if ($item instanceof NotificationHistory) {
|
||||
return new NotificationRenderer();
|
||||
}
|
||||
|
||||
throw new NotImplementedError('Not implemented');
|
||||
});
|
||||
|
||||
$this->data = $this->getIterator($data);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +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\ViewMode;
|
||||
use ipl\Orm\ResultSet;
|
||||
use ipl\Web\Common\BaseItemList;
|
||||
use ipl\Web\Url;
|
||||
|
||||
class NotificationList extends BaseItemList
|
||||
{
|
||||
use CaptionDisabled;
|
||||
use NoSubjectLink;
|
||||
use ViewMode;
|
||||
use LoadMore;
|
||||
use DetailActions;
|
||||
|
||||
protected $defaultAttributes = ['class' => 'notification-list'];
|
||||
|
||||
protected function init(): void
|
||||
{
|
||||
/** @var ResultSet $data */
|
||||
$data = $this->data;
|
||||
$this->data = $this->getIterator($data);
|
||||
$this->initializeDetailActions();
|
||||
$this->setDetailUrl(Url::fromPath('icingadb/event'));
|
||||
}
|
||||
|
||||
protected function getItemClass(): string
|
||||
{
|
||||
switch ($this->getViewMode()) {
|
||||
case 'minimal':
|
||||
return NotificationListItemMinimal::class;
|
||||
case 'detailed':
|
||||
$this->removeAttribute('class', 'default-layout');
|
||||
|
||||
return NotificationListItemDetailed::class;
|
||||
default:
|
||||
return NotificationListItem::class;
|
||||
}
|
||||
}
|
||||
|
||||
protected function assemble(): void
|
||||
{
|
||||
$this->addAttributes(['class' => $this->getViewMode()]);
|
||||
|
||||
parent::assemble();
|
||||
}
|
||||
}
|
||||
|
|
@ -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 NotificationListItem extends BaseNotificationListItem
|
||||
{
|
||||
use ListItemCommonLayout;
|
||||
|
||||
protected function getStateBallSize(): string
|
||||
{
|
||||
return StateBall::SIZE_LARGE;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 NotificationListItemDetailed extends BaseNotificationListItem
|
||||
{
|
||||
use ListItemDetailedLayout;
|
||||
|
||||
protected function getStateBallSize(): string
|
||||
{
|
||||
return StateBall::SIZE_LARGE;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 NotificationListItemMinimal extends BaseNotificationListItem
|
||||
{
|
||||
use ListItemMinimalLayout;
|
||||
|
||||
protected function init(): void
|
||||
{
|
||||
parent::init();
|
||||
|
||||
if ($this->list->isCaptionDisabled()) {
|
||||
$this->setCaptionDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
protected function getStateBallSize(): string
|
||||
{
|
||||
return StateBall::SIZE_BIG;
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ use Icinga\Module\Icingadb\Model\Comment;
|
|||
use Icinga\Module\Icingadb\Model\DependencyNode;
|
||||
use Icinga\Module\Icingadb\Model\Downtime;
|
||||
use Icinga\Module\Icingadb\Model\Host;
|
||||
use Icinga\Module\Icingadb\Model\NotificationHistory;
|
||||
use Icinga\Module\Icingadb\Model\RedundancyGroup;
|
||||
use Icinga\Module\Icingadb\Model\Service;
|
||||
use Icinga\Module\Icingadb\Model\UnreachableParent;
|
||||
|
|
@ -207,6 +208,10 @@ class ObjectList extends ItemList
|
|||
$this->addDetailFilterAttribute($item, Filter::equal('name', $object->name));
|
||||
$this->addMultiSelectFilterAttribute($item, Filter::equal('name', $object->name));
|
||||
|
||||
break;
|
||||
case $object instanceof NotificationHistory:
|
||||
$this->addDetailFilterAttribute($item, Filter::equal('id', bin2hex($object->history->id)));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,8 +43,10 @@
|
|||
|
||||
// Layout
|
||||
|
||||
.item-list .list-item {
|
||||
&.load-more a {
|
||||
.item-list .load-more {
|
||||
display: flex;
|
||||
|
||||
a {
|
||||
flex: 1;
|
||||
margin: 1.5em 0;
|
||||
padding: .5em 0;
|
||||
|
|
|
|||
Loading…
Reference in a new issue