mirror of
https://github.com/Icinga/icingadb-web.git
synced 2026-05-28 04:36:06 -04:00
Introduce Host/Service Renderer
- Use these renderers in ObjectHeader and ObjectList class - Remove now superfluous Host/ServiceList and ListItem classes - DetailsAction: Change visibility of url setter to public (These are called in controller now)
This commit is contained in:
parent
1ff6c25ec6
commit
36ce426bbe
27 changed files with 460 additions and 922 deletions
|
|
@ -26,11 +26,10 @@ use Icinga\Module\Icingadb\Web\Controller;
|
|||
use Icinga\Module\Icingadb\Widget\Detail\HostDetail;
|
||||
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\ObjectList;
|
||||
use Icinga\Module\Icingadb\Widget\ItemList\HostList;
|
||||
use Icinga\Module\Icingadb\Widget\ItemList\HistoryList;
|
||||
use Icinga\Module\Icingadb\Widget\ItemList\ServiceList;
|
||||
use ipl\Orm\Query;
|
||||
use ipl\Sql\Expression;
|
||||
use ipl\Sql\Filter\Exists;
|
||||
|
|
@ -69,10 +68,7 @@ class HostController extends Controller
|
|||
$this->host = $host;
|
||||
$this->loadTabsForObject($host);
|
||||
|
||||
$this->addControl((new HostList([$host]))
|
||||
->setViewMode('objectHeader')
|
||||
->setDetailActionsDisabled()
|
||||
->setNoSubjectLink());
|
||||
$this->addControl(new ObjectHeader($host));
|
||||
|
||||
$this->setTitleTab($this->getRequest()->getActionName());
|
||||
$this->setTitle($host->display_name);
|
||||
|
|
@ -223,7 +219,7 @@ class HostController extends Controller
|
|||
|
||||
yield $this->export($services);
|
||||
|
||||
$serviceList = (new ServiceList($services))
|
||||
$serviceList = (new ObjectList($services))
|
||||
->setViewMode($viewModeSwitcher->getViewMode());
|
||||
|
||||
$this->addControl($paginationControl);
|
||||
|
|
@ -537,7 +533,7 @@ class HostController extends Controller
|
|||
|
||||
protected function getDefaultTabControls(): array
|
||||
{
|
||||
return [(new HostList([$this->host]))->setDetailActionsDisabled()->setNoSubjectLink()];
|
||||
return [new ObjectHeader($this->host)];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use Icinga\Module\Icingadb\Redis\VolatileStateResults;
|
|||
use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions;
|
||||
use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher;
|
||||
use Icinga\Module\Icingadb\Web\Controller;
|
||||
use Icinga\Module\Icingadb\Widget\ItemList\HostList;
|
||||
use Icinga\Module\Icingadb\Widget\ItemList\ObjectList;
|
||||
use Icinga\Module\Icingadb\Widget\ItemTable\HostgroupTableRow;
|
||||
use ipl\Html\Html;
|
||||
use ipl\Stdlib\Filter;
|
||||
|
|
@ -109,8 +109,10 @@ class HostgroupController extends Controller
|
|||
|
||||
yield $this->export($hosts);
|
||||
|
||||
$hostList = (new HostList($hosts->execute()))
|
||||
->setViewMode($viewModeSwitcher->getViewMode());
|
||||
$hostList = (new ObjectList($hosts))
|
||||
->setViewMode($viewModeSwitcher->getViewMode())
|
||||
->setMultiselectUrl(Links::hostsDetails())
|
||||
->setDetailUrl(Url::fromPath('icingadb/host'));
|
||||
|
||||
// ICINGAWEB_EXPORT_FORMAT is not set yet and $this->format is inaccessible, yeah...
|
||||
if ($this->getRequest()->getParam('format') === 'pdf') {
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions;
|
|||
use Icinga\Module\Icingadb\Web\Controller;
|
||||
use Icinga\Module\Icingadb\Widget\Detail\MultiselectQuickActions;
|
||||
use Icinga\Module\Icingadb\Widget\Detail\ObjectsDetail;
|
||||
use Icinga\Module\Icingadb\Widget\ItemList\HostList;
|
||||
use Icinga\Module\Icingadb\Widget\HostStatusBar;
|
||||
use Icinga\Module\Icingadb\Widget\ItemList\ObjectList;
|
||||
use Icinga\Module\Icingadb\Widget\ItemTable\HostItemTable;
|
||||
use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher;
|
||||
use Icinga\Module\Icingadb\Widget\ShowMore;
|
||||
|
|
@ -104,8 +104,10 @@ class HostsController extends Controller
|
|||
$hostList = (new HostItemTable($results, HostItemTable::applyColumnMetaData($hosts, $columns)))
|
||||
->setSort($sortControl->getSort());
|
||||
} else {
|
||||
$hostList = (new HostList($results))
|
||||
->setViewMode($viewModeSwitcher->getViewMode());
|
||||
$hostList = (new ObjectList($results))
|
||||
->setViewMode($viewModeSwitcher->getViewMode())
|
||||
->setMultiselectUrl(Links::hostsDetails())
|
||||
->setDetailUrl(Url::fromPath('icingadb/host'));
|
||||
}
|
||||
|
||||
$this->addContent($hostList);
|
||||
|
|
@ -166,9 +168,8 @@ class HostsController extends Controller
|
|||
$summary->comments_total = $comments->count();
|
||||
|
||||
$this->addControl(
|
||||
(new HostList($results))
|
||||
(new ObjectList($results))
|
||||
->setViewMode('minimal')
|
||||
->setDetailActionsDisabled()
|
||||
);
|
||||
$this->addControl(new ShowMore(
|
||||
$results,
|
||||
|
|
|
|||
|
|
@ -20,13 +20,13 @@ use Icinga\Module\Icingadb\Redis\VolatileStateResults;
|
|||
use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions;
|
||||
use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher;
|
||||
use Icinga\Module\Icingadb\Web\Controller;
|
||||
use Icinga\Module\Icingadb\Widget\Detail\ObjectHeader;
|
||||
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\ObjectList;
|
||||
use Icinga\Module\Icingadb\Widget\ItemList\HistoryList;
|
||||
use Icinga\Module\Icingadb\Widget\ItemList\ServiceList;
|
||||
use ipl\Orm\Query;
|
||||
use ipl\Sql\Expression;
|
||||
use ipl\Stdlib\Filter;
|
||||
|
|
@ -79,10 +79,7 @@ class ServiceController extends Controller
|
|||
$this->service = $service;
|
||||
$this->loadTabsForObject($service);
|
||||
|
||||
$this->addControl((new ServiceList([$service]))
|
||||
->setViewMode('objectHeader')
|
||||
->setDetailActionsDisabled()
|
||||
->setNoSubjectLink());
|
||||
$this->addControl(new ObjectHeader($service));
|
||||
|
||||
$this->setTitleTab($this->getRequest()->getActionName());
|
||||
$this->setTitle(
|
||||
|
|
@ -515,6 +512,6 @@ class ServiceController extends Controller
|
|||
|
||||
protected function getDefaultTabControls(): array
|
||||
{
|
||||
return [(new ServiceList([$this->service]))->setDetailActionsDisabled()->setNoSubjectLink()];
|
||||
return [new ObjectHeader($this->service)];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use Icinga\Module\Icingadb\Redis\VolatileStateResults;
|
|||
use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions;
|
||||
use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher;
|
||||
use Icinga\Module\Icingadb\Web\Controller;
|
||||
use Icinga\Module\Icingadb\Widget\ItemList\ServiceList;
|
||||
use Icinga\Module\Icingadb\Widget\ItemList\ObjectList;
|
||||
use Icinga\Module\Icingadb\Widget\ItemTable\ServicegroupTableRow;
|
||||
use ipl\Html\Html;
|
||||
use ipl\Stdlib\Filter;
|
||||
|
|
@ -117,8 +117,10 @@ class ServicegroupController extends Controller
|
|||
|
||||
yield $this->export($services);
|
||||
|
||||
$serviceList = (new ServiceList($services->execute()))
|
||||
->setViewMode($viewModeSwitcher->getViewMode());
|
||||
$serviceList = (new ObjectList($services))
|
||||
->setViewMode($viewModeSwitcher->getViewMode())
|
||||
->setMultiselectUrl(Links::servicesDetails())
|
||||
->setDetailUrl(Url::fromPath('icingadb/service'));
|
||||
|
||||
// ICINGAWEB_EXPORT_FORMAT is not set yet and $this->format is inaccessible, yeah...
|
||||
if ($this->getRequest()->getParam('format') === 'pdf') {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use Icinga\Module\Icingadb\Web\Control\SearchBar\ObjectSuggestions;
|
|||
use Icinga\Module\Icingadb\Web\Controller;
|
||||
use Icinga\Module\Icingadb\Widget\Detail\MultiselectQuickActions;
|
||||
use Icinga\Module\Icingadb\Widget\Detail\ObjectsDetail;
|
||||
use Icinga\Module\Icingadb\Widget\ItemList\ServiceList;
|
||||
use Icinga\Module\Icingadb\Widget\ItemList\ObjectList;
|
||||
use Icinga\Module\Icingadb\Widget\ItemTable\ServiceItemTable;
|
||||
use Icinga\Module\Icingadb\Widget\ServiceStatusBar;
|
||||
use Icinga\Module\Icingadb\Web\Control\ViewModeSwitcher;
|
||||
|
|
@ -115,8 +115,10 @@ class ServicesController extends Controller
|
|||
$serviceList = (new ServiceItemTable($results, ServiceItemTable::applyColumnMetaData($services, $columns)))
|
||||
->setSort($sortControl->getSort());
|
||||
} else {
|
||||
$serviceList = (new ServiceList($results))
|
||||
->setViewMode($viewModeSwitcher->getViewMode());
|
||||
$serviceList = (new ObjectList($results))
|
||||
->setViewMode($viewModeSwitcher->getViewMode())
|
||||
->setMultiselectUrl(Links::servicesDetails())
|
||||
->setDetailUrl(Url::fromPath('icingadb/service'));
|
||||
}
|
||||
|
||||
$this->addContent($serviceList);
|
||||
|
|
@ -182,9 +184,8 @@ class ServicesController extends Controller
|
|||
$summary->comments_total = $comments->count();
|
||||
|
||||
$this->addControl(
|
||||
(new ServiceList($results))
|
||||
(new ObjectList($results))
|
||||
->setViewMode('minimal')
|
||||
->setDetailActionsDisabled()
|
||||
);
|
||||
$this->addControl(new ShowMore(
|
||||
$results,
|
||||
|
|
|
|||
283
library/Icingadb/View/BaseHostAndServiceRenderer.php
Normal file
283
library/Icingadb/View/BaseHostAndServiceRenderer.php
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
<?php
|
||||
|
||||
/* Icinga DB Web | (c) 2025 Icinga GmbH | GPLv2 */
|
||||
|
||||
namespace Icinga\Module\Icingadb\View;
|
||||
|
||||
use Icinga\Module\Icingadb\Common\HostStates;
|
||||
use Icinga\Module\Icingadb\Common\Icons;
|
||||
use Icinga\Module\Icingadb\Common\ServiceStates;
|
||||
use Icinga\Module\Icingadb\Model\Host;
|
||||
use Icinga\Module\Icingadb\Model\Service;
|
||||
use Icinga\Module\Icingadb\Util\PerfDataSet;
|
||||
use Icinga\Module\Icingadb\Util\PluginOutput;
|
||||
use Icinga\Module\Icingadb\Widget\CheckAttempt;
|
||||
use Icinga\Module\Icingadb\Widget\IconImage;
|
||||
use Icinga\Module\Icingadb\Widget\ItemList\CommentList;
|
||||
use Icinga\Module\Icingadb\Widget\PluginOutputContainer;
|
||||
use Icinga\Module\Icingadb\Widget\StateChange;
|
||||
use ipl\Html\Attributes;
|
||||
use ipl\Html\Html;
|
||||
use ipl\Html\HtmlDocument;
|
||||
use ipl\Html\HtmlElement;
|
||||
use ipl\Html\HtmlString;
|
||||
use ipl\Html\Text;
|
||||
use ipl\Html\ValidHtml;
|
||||
use ipl\I18n\Translation;
|
||||
use ipl\Web\Common\ItemRenderer;
|
||||
use ipl\Web\Widget\EmptyState;
|
||||
use ipl\Web\Widget\Icon;
|
||||
use ipl\Web\Widget\StateBall;
|
||||
use ipl\Web\Widget\TimeSince;
|
||||
|
||||
/** @implements ItemRenderer<Host|Service> */
|
||||
abstract class BaseHostAndServiceRenderer implements ItemRenderer
|
||||
{
|
||||
use Translation;
|
||||
|
||||
abstract protected function createSubject($item, string $layout): ValidHtml;
|
||||
|
||||
public function assembleVisual($item, HtmlDocument $visual, string $layout): void
|
||||
{
|
||||
if ($layout === 'header') {
|
||||
if ($item->state->state_type === 'soft') {
|
||||
$stateType = 'soft_state';
|
||||
$previousStateType = 'previous_soft_state';
|
||||
|
||||
if ($item->state->previous_soft_state === 0) {
|
||||
$previousStateType = 'hard_state';
|
||||
}
|
||||
} else {
|
||||
$stateType = 'hard_state';
|
||||
$previousStateType = 'previous_hard_state';
|
||||
|
||||
if ($item->state->hard_state === $item->state->previous_hard_state) {
|
||||
$previousStateType = 'previous_soft_state';
|
||||
}
|
||||
}
|
||||
|
||||
if ($item instanceof 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');
|
||||
}
|
||||
}
|
||||
|
||||
$stateChange->setIcon($item->state->getIcon());
|
||||
$stateChange->setHandled(
|
||||
$item->state->is_problem && ($item->state->is_handled || ! $item->state->is_reachable)
|
||||
);
|
||||
|
||||
$visual->addHtml($stateChange);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$ballSize = $layout === 'minimal' ? StateBall::SIZE_BIG : StateBall::SIZE_LARGE;
|
||||
|
||||
$stateBall = new StateBall($item->state->getStateText(), $ballSize);
|
||||
$stateBall->add($item->state->getIcon());
|
||||
if ($item->state->is_problem && ($item->state->is_handled || ! $item->state->is_reachable)) {
|
||||
$stateBall->getAttributes()->add('class', 'handled');
|
||||
}
|
||||
|
||||
$visual->addHtml($stateBall);
|
||||
if ($layout !== 'minimal' && $item->state->state_type === 'soft') {
|
||||
$visual->addHtml(
|
||||
new CheckAttempt((int) $item->state->check_attempt, (int) $item->max_check_attempts)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function assembleCaption($item, HtmlDocument $caption, string $layout): void
|
||||
{
|
||||
if ($item->state->soft_state === null && $item->state->output === null) {
|
||||
$caption->addHtml(Text::create($this->translate('Waiting for Icinga DB to synchronize the state.')));
|
||||
} else {
|
||||
if (empty($item->state->output)) {
|
||||
$pluginOutput = new EmptyState($this->translate('Output unavailable.'));
|
||||
} else {
|
||||
$pluginOutput = new PluginOutputContainer(PluginOutput::fromObject($item));
|
||||
}
|
||||
|
||||
$caption->addHtml($pluginOutput);
|
||||
}
|
||||
}
|
||||
|
||||
public function assembleTitle($item, HtmlDocument $title, string $layout): void
|
||||
{
|
||||
$title->addHtml(Html::sprintf(
|
||||
$this->translate('%s is %s', '<hostname> is <state-text>'),
|
||||
$this->createSubject($item, $layout),
|
||||
Html::tag('span', ['class' => 'state-text'], $item->state->getStateTextTranslated())
|
||||
));
|
||||
|
||||
if (isset($item->state->affects_children) && $item->state->affects_children) {
|
||||
$total = (int) $item->total_children;
|
||||
|
||||
if ($total > 1000) {
|
||||
$total = '1000+';
|
||||
$tooltip = $this->translate('Up to 1000+ affected objects');
|
||||
} else {
|
||||
$tooltip = sprintf(
|
||||
$this->translatePlural(
|
||||
'%d affected object',
|
||||
'Up to %d affected objects',
|
||||
$total
|
||||
),
|
||||
$total
|
||||
);
|
||||
}
|
||||
|
||||
$icon = new Icon(Icons::UNREACHABLE);
|
||||
|
||||
$title->addHtml(new HtmlElement(
|
||||
'span',
|
||||
Attributes::create([
|
||||
'class' => 'affected-objects',
|
||||
'title' => $tooltip
|
||||
]),
|
||||
$icon,
|
||||
Text::create($total)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public function assembleExtendedInfo($item, HtmlDocument $info, string $layout): void
|
||||
{
|
||||
if ($item->state->is_overdue) {
|
||||
$since = new TimeSince($item->state->next_update->getTimestamp());
|
||||
$since->prepend($this->translate('Overdue') . ' ');
|
||||
$since->prependHtml(new Icon(Icons::WARNING));
|
||||
|
||||
$info->addHtml($since);
|
||||
} elseif ($item->state->last_state_change !== null && $item->state->last_state_change->getTimestamp() > 0) {
|
||||
$info->addHtml(new TimeSince($item->state->last_state_change->getTimestamp()));
|
||||
}
|
||||
}
|
||||
|
||||
public function assembleFooter($item, HtmlDocument $footer, string $layout): void
|
||||
{
|
||||
$pieChartLimit = 5;
|
||||
$statusIcons = new HtmlElement('div', Attributes::create(['class' => 'status-icons']));
|
||||
$isService = $item instanceof Service;
|
||||
|
||||
if (
|
||||
($isService && $item->state->last_comment->service_id === $item->id)
|
||||
|| $item->state->last_comment->host_id === $item->id
|
||||
) {
|
||||
$comment = $item->state->last_comment;
|
||||
|
||||
if ($isService) {
|
||||
$comment->service = $item;
|
||||
} else {
|
||||
$comment->host = $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 ($item->state->is_flapping) {
|
||||
$title = $isService
|
||||
? sprintf(
|
||||
$this->translate('Service "%s" on "%s" is in flapping state'),
|
||||
$item->display_name, $item->host->display_name
|
||||
)
|
||||
: sprintf(
|
||||
$this->translate('Host "%s" is in flapping state'),
|
||||
$item->display_name
|
||||
);
|
||||
|
||||
$statusIcons->addHtml(new Icon('random', ['title' => $title]));
|
||||
}
|
||||
|
||||
if (! $item->notifications_enabled) {
|
||||
$statusIcons->addHtml(
|
||||
new Icon('bell-slash', ['title' => $this->translate('Notifications disabled')])
|
||||
);
|
||||
}
|
||||
|
||||
if (! $item->active_checks_enabled) {
|
||||
$statusIcons->addHtml(
|
||||
new Icon('eye-slash', ['title' => $this->translate('Active checks disabled')])
|
||||
);
|
||||
}
|
||||
|
||||
$performanceData = new HtmlElement('div', Attributes::create(['class' => 'performance-data']));
|
||||
if ($item->state->performance_data) {
|
||||
$pieChartData = PerfDataSet::fromString($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) > $pieChartLimit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$maxVisiblePies = $pieChartLimit - 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 > $pieChartLimit
|
||||
) {
|
||||
$performanceData->addHtml(new HtmlElement('span', null, Text::create('…')));
|
||||
break;
|
||||
}
|
||||
|
||||
$performanceData->addHtml(HtmlString::create($pie));
|
||||
}
|
||||
}
|
||||
|
||||
if (! $statusIcons->isEmpty()) {
|
||||
$footer->addHtml($statusIcons);
|
||||
}
|
||||
|
||||
if (! $performanceData->isEmpty()) {
|
||||
$footer->addHtml($performanceData);
|
||||
}
|
||||
}
|
||||
|
||||
public function assemble($item, string $name, HtmlDocument $element, string $layout): bool
|
||||
{
|
||||
if ($name === 'icon-image') {
|
||||
if (isset($item->icon_image->icon_image)) {
|
||||
$element->addHtml(new IconImage($item->icon_image->icon_image, $item->icon_image_alt));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
29
library/Icingadb/View/HostRenderer.php
Normal file
29
library/Icingadb/View/HostRenderer.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
/* Icinga DB Web | (c) 2025 Icinga GmbH | GPLv2 */
|
||||
|
||||
namespace Icinga\Module\Icingadb\View;
|
||||
|
||||
use Icinga\Module\Icingadb\Common\Links;
|
||||
use ipl\Html\Attributes;
|
||||
use ipl\Html\HtmlElement;
|
||||
use ipl\Html\Text;
|
||||
use ipl\Html\ValidHtml;
|
||||
use ipl\Web\Widget\Link;
|
||||
|
||||
class HostRenderer extends BaseHostAndServiceRenderer
|
||||
{
|
||||
public function assembleAttributes($item, Attributes $attributes, string $layout): void
|
||||
{
|
||||
$attributes->get('class')->addValue('host');
|
||||
}
|
||||
|
||||
protected function createSubject($item, string $layout): ValidHtml
|
||||
{
|
||||
if ($layout === 'header') {
|
||||
return new HtmlElement('span', new Attributes(['class' => 'subject']), new Text($item->display_name));
|
||||
}
|
||||
|
||||
return new Link($item->display_name, Links::host($item), ['class' => 'subject']);
|
||||
}
|
||||
}
|
||||
41
library/Icingadb/View/ServiceRenderer.php
Normal file
41
library/Icingadb/View/ServiceRenderer.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
/* Icinga DB Web | (c) 2025 Icinga GmbH | GPLv2 */
|
||||
|
||||
namespace Icinga\Module\Icingadb\View;
|
||||
|
||||
use Icinga\Module\Icingadb\Common\Links;
|
||||
use ipl\Html\Attributes;
|
||||
use ipl\Html\Html;
|
||||
use ipl\Html\HtmlElement;
|
||||
use ipl\Html\Text;
|
||||
use ipl\Html\ValidHtml;
|
||||
use ipl\Web\Widget\Link;
|
||||
use ipl\Web\Widget\StateBall;
|
||||
|
||||
class ServiceRenderer extends BaseHostAndServiceRenderer
|
||||
{
|
||||
public function assembleAttributes($item, Attributes $attributes, string $layout): void
|
||||
{
|
||||
$attributes->get('class')->addValue('service');
|
||||
}
|
||||
|
||||
protected function createSubject($item, string $layout): ValidHtml
|
||||
{
|
||||
$service = $item->display_name;
|
||||
$host = [
|
||||
new StateBall($item->host->state->getStateText(), StateBall::SIZE_MEDIUM),
|
||||
' ',
|
||||
$item->host->display_name
|
||||
];
|
||||
|
||||
$host = new Link($host, Links::host($item->host), ['class' => 'subject']);
|
||||
if ($layout === 'header') {
|
||||
$service = new HtmlElement('span', new Attributes(['class' => 'subject']), new Text($service));
|
||||
} else {
|
||||
$service = new Link($service, Links::service($item, $item->host), ['class' => 'subject']);
|
||||
}
|
||||
|
||||
return Html::sprintf($this->translate('%s on %s', '<service> on <host>'), $service, $host);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,8 +5,12 @@
|
|||
namespace Icinga\Module\Icingadb\Widget\Detail;
|
||||
|
||||
use Icinga\Exception\NotImplementedError;
|
||||
use Icinga\Module\Icingadb\Model\Host;
|
||||
use Icinga\Module\Icingadb\Model\RedundancyGroup;
|
||||
use Icinga\Module\Icingadb\Model\Service;
|
||||
use Icinga\Module\Icingadb\View\HostRenderer;
|
||||
use Icinga\Module\Icingadb\View\RedundancyGroupRenderer;
|
||||
use Icinga\Module\Icingadb\View\ServiceRenderer;
|
||||
use ipl\Html\BaseHtmlElement;
|
||||
use ipl\Orm\Model;
|
||||
use ipl\Web\Layout\HeaderItemLayout;
|
||||
|
|
@ -28,6 +32,15 @@ class ObjectHeader extends BaseHtmlElement
|
|||
switch (true) {
|
||||
case $this->object instanceof RedundancyGroup:
|
||||
$renderer = new RedundancyGroupRenderer();
|
||||
|
||||
break;
|
||||
case $this->object instanceof Service:
|
||||
$renderer = new ServiceRenderer();
|
||||
|
||||
break;
|
||||
case $this->object instanceof Host:
|
||||
$renderer = new HostRenderer();
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedError('Not implemented');
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
<?php
|
||||
|
||||
/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */
|
||||
|
||||
namespace Icinga\Module\Icingadb\Widget\ItemList;
|
||||
|
||||
use Icinga\Module\Icingadb\Common\Links;
|
||||
use Icinga\Module\Icingadb\Common\NoSubjectLink;
|
||||
use Icinga\Module\Icingadb\Model\Host;
|
||||
use ipl\Html\Attributes;
|
||||
use ipl\Html\BaseHtmlElement;
|
||||
use ipl\Html\HtmlElement;
|
||||
use ipl\Html\Text;
|
||||
use ipl\Stdlib\Filter;
|
||||
use ipl\Web\Widget\Link;
|
||||
|
||||
/**
|
||||
* Host item of a host list. Represents one database row.
|
||||
*
|
||||
* @property Host $item
|
||||
* @property HostList $list
|
||||
*/
|
||||
abstract class BaseHostListItem extends StateListItem
|
||||
{
|
||||
use NoSubjectLink;
|
||||
|
||||
/**
|
||||
* Create new subject link
|
||||
*
|
||||
* @return BaseHtmlElement
|
||||
*/
|
||||
protected function createSubject()
|
||||
{
|
||||
if ($this->getNoSubjectLink()) {
|
||||
return new HtmlElement(
|
||||
'span',
|
||||
Attributes::create(['class' => 'subject']),
|
||||
Text::create($this->item->display_name)
|
||||
);
|
||||
} else {
|
||||
return new Link($this->item->display_name, Links::host($this->item), ['class' => 'subject']);
|
||||
}
|
||||
}
|
||||
|
||||
protected function init(): void
|
||||
{
|
||||
parent::init();
|
||||
|
||||
if ($this->list->getNoSubjectLink()) {
|
||||
$this->setNoSubjectLink();
|
||||
}
|
||||
|
||||
$this->list->addDetailFilterAttribute($this, Filter::equal('name', $this->item->name))
|
||||
->addMultiselectFilterAttribute($this, Filter::equal('host.name', $this->item->name));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
<?php
|
||||
|
||||
/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */
|
||||
|
||||
namespace Icinga\Module\Icingadb\Widget\ItemList;
|
||||
|
||||
use Icinga\Module\Icingadb\Common\Links;
|
||||
use Icinga\Module\Icingadb\Common\NoSubjectLink;
|
||||
use Icinga\Module\Icingadb\Model\Service;
|
||||
use ipl\Html\Attributes;
|
||||
use ipl\Html\Html;
|
||||
use ipl\Html\HtmlElement;
|
||||
use ipl\Html\Text;
|
||||
use ipl\Stdlib\Filter;
|
||||
use ipl\Web\Widget\Link;
|
||||
use ipl\Web\Widget\StateBall;
|
||||
|
||||
/**
|
||||
* Service item of a service list. Represents one database row.
|
||||
*
|
||||
* @property Service $item
|
||||
* @property ServiceList $list
|
||||
*/
|
||||
abstract class BaseServiceListItem extends StateListItem
|
||||
{
|
||||
use NoSubjectLink;
|
||||
|
||||
protected function createSubject()
|
||||
{
|
||||
$service = $this->item->display_name;
|
||||
$host = [
|
||||
new StateBall($this->item->host->state->getStateText(), StateBall::SIZE_MEDIUM),
|
||||
' ',
|
||||
$this->item->host->display_name
|
||||
];
|
||||
|
||||
$host = new Link($host, Links::host($this->item->host), ['class' => 'subject']);
|
||||
if ($this->getNoSubjectLink()) {
|
||||
$service = new HtmlElement('span', Attributes::create(['class' => 'subject']), Text::create($service));
|
||||
} else {
|
||||
$service = new Link($service, Links::service($this->item, $this->item->host), ['class' => 'subject']);
|
||||
}
|
||||
|
||||
return [Html::sprintf(t('%s on %s', '<service> on <host>'), $service, $host)];
|
||||
}
|
||||
|
||||
protected function init(): void
|
||||
{
|
||||
parent::init();
|
||||
|
||||
if ($this->list->getNoSubjectLink()) {
|
||||
$this->setNoSubjectLink();
|
||||
}
|
||||
|
||||
$this->list->addMultiselectFilterAttribute(
|
||||
$this,
|
||||
Filter::all(
|
||||
Filter::equal('service.name', $this->item->name),
|
||||
Filter::equal('host.name', $this->item->host->name)
|
||||
)
|
||||
);
|
||||
$this->list->addDetailFilterAttribute(
|
||||
$this,
|
||||
Filter::all(
|
||||
Filter::equal('name', $this->item->name),
|
||||
Filter::equal('host.name', $this->item->host->name)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
<?php
|
||||
|
||||
/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */
|
||||
|
||||
namespace Icinga\Module\Icingadb\Widget\ItemList;
|
||||
|
||||
use Icinga\Module\Icingadb\Common\HostStates;
|
||||
use Icinga\Module\Icingadb\Widget\StateChange;
|
||||
use ipl\Html\BaseHtmlElement;
|
||||
use ipl\Web\Widget\StateBall;
|
||||
|
||||
class HostDetailHeader extends HostListItemMinimal
|
||||
{
|
||||
protected function getStateBallSize(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
protected function assembleVisual(BaseHtmlElement $visual): void
|
||||
{
|
||||
if ($this->state->state_type === 'soft') {
|
||||
$stateType = 'soft_state';
|
||||
$previousStateType = 'previous_soft_state';
|
||||
|
||||
if ($this->state->previous_soft_state === 0) {
|
||||
$previousStateType = 'hard_state';
|
||||
}
|
||||
} else {
|
||||
$stateType = 'hard_state';
|
||||
$previousStateType = 'previous_hard_state';
|
||||
|
||||
if ($this->state->hard_state === $this->state->previous_hard_state) {
|
||||
$previousStateType = 'previous_soft_state';
|
||||
}
|
||||
}
|
||||
|
||||
$state = HostStates::text($this->state->$stateType);
|
||||
$previousState = HostStates::text($this->state->$previousStateType);
|
||||
|
||||
$stateChange = new StateChange($state, $previousState);
|
||||
if ($stateType === 'soft_state') {
|
||||
$stateChange->setCurrentStateBallSize(StateBall::SIZE_MEDIUM_LARGE);
|
||||
}
|
||||
|
||||
if ($previousStateType === 'previous_soft_state') {
|
||||
$stateChange->setPreviousStateBallSize(StateBall::SIZE_MEDIUM_LARGE);
|
||||
if ($stateType === 'soft_state') {
|
||||
$visual->getAttributes()->add('class', 'small-state-change');
|
||||
}
|
||||
}
|
||||
|
||||
$stateChange->setIcon($this->state->getIcon());
|
||||
$stateChange->setHandled(
|
||||
$this->state->is_problem && ($this->state->is_handled || ! $this->state->is_reachable)
|
||||
);
|
||||
|
||||
$visual->addHtml($stateChange);
|
||||
}
|
||||
|
||||
protected function assemble(): void
|
||||
{
|
||||
$attributes = $this->list->getAttributes();
|
||||
if (! in_array('minimal', $attributes->get('class')->getValue())) {
|
||||
$attributes->add('class', 'minimal');
|
||||
}
|
||||
|
||||
parent::assemble();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
<?php
|
||||
|
||||
/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */
|
||||
|
||||
namespace Icinga\Module\Icingadb\Widget\ItemList;
|
||||
|
||||
use Icinga\Module\Icingadb\Common\Links;
|
||||
use ipl\Web\Url;
|
||||
|
||||
/**
|
||||
* Host list
|
||||
*/
|
||||
class HostList extends StateList
|
||||
{
|
||||
protected $defaultAttributes = ['class' => 'host-list'];
|
||||
|
||||
protected function getItemClass(): string
|
||||
{
|
||||
switch ($this->getViewMode()) {
|
||||
case 'minimal':
|
||||
return HostListItemMinimal::class;
|
||||
case 'detailed':
|
||||
$this->removeAttribute('class', 'default-layout');
|
||||
|
||||
return HostListItemDetailed::class;
|
||||
case 'objectHeader':
|
||||
return HostDetailHeader::class;
|
||||
default:
|
||||
return HostListItem::class;
|
||||
}
|
||||
}
|
||||
|
||||
protected function init(): void
|
||||
{
|
||||
$this->initializeDetailActions();
|
||||
$this->setMultiselectUrl(Links::hostsDetails());
|
||||
$this->setDetailUrl(Url::fromPath('icingadb/host'));
|
||||
}
|
||||
}
|
||||
|
|
@ -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 HostListItem extends BaseHostListItem
|
||||
{
|
||||
use ListItemCommonLayout;
|
||||
|
||||
protected function getStateBallSize(): string
|
||||
{
|
||||
return StateBall::SIZE_LARGE;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
<?php
|
||||
|
||||
/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */
|
||||
|
||||
namespace Icinga\Module\Icingadb\Widget\ItemList;
|
||||
|
||||
use Icinga\Module\Icingadb\Common\ListItemDetailedLayout;
|
||||
use Icinga\Module\Icingadb\Util\PerfDataSet;
|
||||
use Icinga\Module\Icingadb\Widget\ItemList\CommentList;
|
||||
use ipl\Html\Attributes;
|
||||
use ipl\Html\BaseHtmlElement;
|
||||
use ipl\Html\HtmlElement;
|
||||
use ipl\Html\HtmlString;
|
||||
use ipl\Html\Text;
|
||||
use ipl\Web\Widget\Icon;
|
||||
use ipl\Web\Widget\StateBall;
|
||||
|
||||
class HostListItemDetailed extends BaseHostListItem
|
||||
{
|
||||
use ListItemDetailedLayout;
|
||||
|
||||
/** @var int Max pie charts to be shown */
|
||||
const PIE_CHART_LIMIT = 5;
|
||||
|
||||
protected function getStateBallSize(): string
|
||||
{
|
||||
return StateBall::SIZE_LARGE;
|
||||
}
|
||||
|
||||
protected function assembleFooter(BaseHtmlElement $footer): void
|
||||
{
|
||||
$statusIcons = new HtmlElement('div', Attributes::create(['class' => 'status-icons']));
|
||||
|
||||
if ($this->item->state->last_comment->host_id === $this->item->id) {
|
||||
$comment = $this->item->state->last_comment;
|
||||
$comment->host = $this->item;
|
||||
$comment = (new CommentList([$comment]))
|
||||
->setNoSubjectLink()
|
||||
->setObjectLinkDisabled()
|
||||
->setDetailActionsDisabled();
|
||||
|
||||
$statusIcons->addHtml(
|
||||
new HtmlElement(
|
||||
'div',
|
||||
Attributes::create(['class' => 'comment-wrapper']),
|
||||
new HtmlElement('div', Attributes::create(['class' => 'comment-popup']), $comment),
|
||||
(new Icon('comments', ['class' => 'comment-icon']))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->item->state->is_flapping) {
|
||||
$statusIcons->addHtml(new Icon(
|
||||
'random',
|
||||
[
|
||||
'title' => sprintf(t('Host "%s" is in flapping state'), $this->item->display_name),
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
if (! $this->item->notifications_enabled) {
|
||||
$statusIcons->addHtml(new Icon('bell-slash', ['title' => t('Notifications disabled')]));
|
||||
}
|
||||
|
||||
if (! $this->item->active_checks_enabled) {
|
||||
$statusIcons->addHtml(new Icon('eye-slash', ['title' => t('Active checks disabled')]));
|
||||
}
|
||||
|
||||
$performanceData = new HtmlElement('div', Attributes::create(['class' => 'performance-data']));
|
||||
if ($this->item->state->performance_data) {
|
||||
$pieChartData = PerfDataSet::fromString($this->item->state->normalized_performance_data)->asArray();
|
||||
|
||||
$pies = [];
|
||||
foreach ($pieChartData as $i => $perfdata) {
|
||||
if ($perfdata->isVisualizable()) {
|
||||
$pies[] = $perfdata->asInlinePie()->render();
|
||||
}
|
||||
|
||||
// Check if number of visualizable pie charts is larger than PIE_CHART_LIMIT
|
||||
if (count($pies) > HostListItemDetailed::PIE_CHART_LIMIT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$maxVisiblePies = HostListItemDetailed::PIE_CHART_LIMIT - 2;
|
||||
$numOfPies = count($pies);
|
||||
foreach ($pies as $i => $pie) {
|
||||
if (
|
||||
// Show max. 5 elements: if there are more than 5, show 4 + `…`
|
||||
$i > $maxVisiblePies && $numOfPies > HostListItemDetailed::PIE_CHART_LIMIT
|
||||
) {
|
||||
$performanceData->addHtml(new HtmlElement('span', null, Text::create('…')));
|
||||
break;
|
||||
}
|
||||
|
||||
$performanceData->addHtml(HtmlString::create($pie));
|
||||
}
|
||||
}
|
||||
|
||||
if (! $statusIcons->isEmpty()) {
|
||||
$footer->addHtml($statusIcons);
|
||||
}
|
||||
|
||||
if (! $performanceData->isEmpty()) {
|
||||
$footer->addHtml($performanceData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +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 HostListItemMinimal extends BaseHostListItem
|
||||
{
|
||||
use ListItemMinimalLayout;
|
||||
|
||||
protected function getStateBallSize(): string
|
||||
{
|
||||
return StateBall::SIZE_BIG;
|
||||
}
|
||||
}
|
||||
|
|
@ -9,13 +9,17 @@ use Icinga\Module\Icingadb\Common\DetailActions;
|
|||
use Icinga\Module\Icingadb\Model\DependencyNode;
|
||||
use Icinga\Module\Icingadb\Model\Host;
|
||||
use Icinga\Module\Icingadb\Model\RedundancyGroup;
|
||||
use Icinga\Module\Icingadb\Model\Service;
|
||||
use Icinga\Module\Icingadb\Model\UnreachableParent;
|
||||
use Icinga\Module\Icingadb\Redis\VolatileStateResults;
|
||||
use Icinga\Module\Icingadb\View\HostRenderer;
|
||||
use Icinga\Module\Icingadb\View\RedundancyGroupRenderer;
|
||||
use Icinga\Module\Icingadb\View\ServiceRenderer;
|
||||
use Icinga\Module\Icingadb\Widget\Notice;
|
||||
use InvalidArgumentException;
|
||||
use ipl\Html\HtmlDocument;
|
||||
use ipl\Orm\Model;
|
||||
use ipl\Stdlib\Filter;
|
||||
use ipl\Web\Layout\DetailedItemLayout;
|
||||
use ipl\Web\Layout\ItemLayout;
|
||||
use ipl\Web\Layout\MinimalItemLayout;
|
||||
|
|
@ -27,7 +31,7 @@ use ipl\Web\Widget\ListItem;
|
|||
*
|
||||
* Create a list of icingadb objects
|
||||
*
|
||||
* @extends ItemList<RedundancyGroup>
|
||||
* @extends ItemList<RedundancyGroup> // TODO: fix type
|
||||
*/
|
||||
class ObjectList extends ItemList
|
||||
{
|
||||
|
|
@ -41,6 +45,10 @@ class ObjectList extends ItemList
|
|||
parent::__construct($data, function (Model $item) {
|
||||
if ($item instanceof RedundancyGroup) {
|
||||
return new RedundancyGroupRenderer();
|
||||
} elseif ($item instanceof Service) {
|
||||
return new ServiceRenderer();
|
||||
} elseif ($item instanceof Host) {
|
||||
return new HostRenderer();
|
||||
} else {
|
||||
throw new NotImplementedError('Not implemented');
|
||||
}
|
||||
|
|
@ -115,32 +123,64 @@ class ObjectList extends ItemList
|
|||
return $layout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object<UnreachableParent|DependencyNode|RedundancyGroup|Service|Host> $data
|
||||
*
|
||||
* @return ListItem
|
||||
*/
|
||||
protected function createListItem(object $data)
|
||||
{
|
||||
/** @var UnreachableParent|DependencyNode $data */
|
||||
if ($data->redundancy_group_id !== null) {
|
||||
return (new ListItem($data->redundancy_group, $this))
|
||||
->addAttributes(['data-action-item' => true]);
|
||||
if ($data instanceof DependencyNode) {
|
||||
if (isset($data->redundancy_group_id)) {
|
||||
$object = $data->redundancy_group;
|
||||
} else {
|
||||
$object = isset($data->service_id) ? $data->service : $data->host;
|
||||
}
|
||||
} else {
|
||||
$object = $data;
|
||||
}
|
||||
|
||||
// TODO: Adjust the remaining stuff once self::getItemLayout supports host and services
|
||||
|
||||
$object = $data->service_id !== null ? $data->service : $data->host;
|
||||
|
||||
switch (false) {
|
||||
case MinimalItemLayout::class:
|
||||
$class = $object instanceof Host ? HostListItemMinimal::class : ServiceListItemMinimal::class;
|
||||
break;
|
||||
case DetailedItemLayout::class:
|
||||
$this->removeAttribute('class', 'default-layout');
|
||||
|
||||
$class = $object instanceof Host ? HostListItemDetailed::class : ServiceListItemDetailed::class;
|
||||
break;
|
||||
default:
|
||||
$class = $object instanceof Host ? HostListItem::class : ServiceListItem::class;
|
||||
if (isset($object->icon_image->icon_image)) {
|
||||
$this->setHasIconImages(true);
|
||||
}
|
||||
|
||||
return new $class($object, $this);
|
||||
$item = parent::createListItem($object);
|
||||
|
||||
if ($this->getDetailActionsDisabled()) {
|
||||
return $item;
|
||||
}
|
||||
|
||||
switch (true) {
|
||||
case $object instanceof RedundancyGroup:
|
||||
$this->addDetailFilterAttribute($item, Filter::equal('id', bin2hex($object->id)));
|
||||
|
||||
break;
|
||||
case $object instanceof Service:
|
||||
$this->addDetailFilterAttribute(
|
||||
$item,
|
||||
Filter::all(
|
||||
Filter::equal('name', $object->name),
|
||||
Filter::equal('host.name', $object->host->name)
|
||||
)
|
||||
);
|
||||
|
||||
$this->addMultiSelectFilterAttribute(
|
||||
$item,
|
||||
Filter::all(
|
||||
Filter::equal('service.name', $object->name),
|
||||
Filter::equal('host.name', $object->host->name)
|
||||
)
|
||||
);
|
||||
|
||||
break;
|
||||
case $object instanceof Host:
|
||||
$this->addDetailFilterAttribute($item, Filter::equal('name', $object->name));
|
||||
$this->addMultiSelectFilterAttribute($item, Filter::equal('host.name', $object->name));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
protected function assemble(): void
|
||||
|
|
|
|||
|
|
@ -1,69 +0,0 @@
|
|||
<?php
|
||||
|
||||
/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */
|
||||
|
||||
namespace Icinga\Module\Icingadb\Widget\ItemList;
|
||||
|
||||
use Icinga\Module\Icingadb\Common\ServiceStates;
|
||||
use Icinga\Module\Icingadb\Widget\StateChange;
|
||||
use ipl\Html\BaseHtmlElement;
|
||||
use ipl\Web\Widget\StateBall;
|
||||
|
||||
class ServiceDetailHeader extends ServiceListItemMinimal
|
||||
{
|
||||
protected function getStateBallSize(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
protected function assembleVisual(BaseHtmlElement $visual): void
|
||||
{
|
||||
if ($this->state->state_type === 'soft') {
|
||||
$stateType = 'soft_state';
|
||||
$previousStateType = 'previous_soft_state';
|
||||
|
||||
if ($this->state->previous_soft_state === 0) {
|
||||
$previousStateType = 'hard_state';
|
||||
}
|
||||
} else {
|
||||
$stateType = 'hard_state';
|
||||
$previousStateType = 'previous_hard_state';
|
||||
|
||||
if ($this->state->hard_state === $this->state->previous_hard_state) {
|
||||
$previousStateType = 'previous_soft_state';
|
||||
}
|
||||
}
|
||||
|
||||
$state = ServiceStates::text($this->state->$stateType);
|
||||
$previousState = ServiceStates::text($this->state->$previousStateType);
|
||||
|
||||
$stateChange = new StateChange($state, $previousState);
|
||||
if ($stateType === 'soft_state') {
|
||||
$stateChange->setCurrentStateBallSize(StateBall::SIZE_MEDIUM_LARGE);
|
||||
}
|
||||
|
||||
if ($previousStateType === 'previous_soft_state') {
|
||||
$stateChange->setPreviousStateBallSize(StateBall::SIZE_MEDIUM_LARGE);
|
||||
if ($stateType === 'soft_state') {
|
||||
$visual->getAttributes()->add('class', 'small-state-change');
|
||||
}
|
||||
}
|
||||
|
||||
$stateChange->setIcon($this->state->getIcon());
|
||||
$stateChange->setHandled(
|
||||
$this->state->is_problem && ($this->state->is_handled || ! $this->state->is_reachable)
|
||||
);
|
||||
|
||||
$visual->addHtml($stateChange);
|
||||
}
|
||||
|
||||
protected function assemble(): void
|
||||
{
|
||||
$attributes = $this->list->getAttributes();
|
||||
if (! in_array('minimal', $attributes->get('class')->getValue())) {
|
||||
$attributes->add('class', 'minimal');
|
||||
}
|
||||
|
||||
parent::assemble();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
<?php
|
||||
|
||||
/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */
|
||||
|
||||
namespace Icinga\Module\Icingadb\Widget\ItemList;
|
||||
|
||||
use Icinga\Module\Icingadb\Common\Links;
|
||||
use ipl\Web\Url;
|
||||
|
||||
class ServiceList extends StateList
|
||||
{
|
||||
protected $defaultAttributes = ['class' => 'service-list'];
|
||||
|
||||
protected function getItemClass(): string
|
||||
{
|
||||
switch ($this->getViewMode()) {
|
||||
case 'minimal':
|
||||
return ServiceListItemMinimal::class;
|
||||
case 'detailed':
|
||||
$this->removeAttribute('class', 'default-layout');
|
||||
|
||||
return ServiceListItemDetailed::class;
|
||||
case 'objectHeader':
|
||||
return ServiceDetailHeader::class;
|
||||
default:
|
||||
return ServiceListItem::class;
|
||||
}
|
||||
}
|
||||
|
||||
protected function init(): void
|
||||
{
|
||||
$this->initializeDetailActions();
|
||||
$this->setMultiselectUrl(Links::servicesDetails());
|
||||
$this->setDetailUrl(Url::fromPath('icingadb/service'));
|
||||
}
|
||||
}
|
||||
|
|
@ -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 ServiceListItem extends BaseServiceListItem
|
||||
{
|
||||
use ListItemCommonLayout;
|
||||
|
||||
protected function getStateBallSize(): string
|
||||
{
|
||||
return StateBall::SIZE_LARGE;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
<?php
|
||||
|
||||
/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */
|
||||
|
||||
namespace Icinga\Module\Icingadb\Widget\ItemList;
|
||||
|
||||
use Icinga\Module\Icingadb\Common\ListItemDetailedLayout;
|
||||
use Icinga\Module\Icingadb\Util\PerfDataSet;
|
||||
use Icinga\Module\Icingadb\Widget\ItemList\CommentList;
|
||||
use ipl\Html\Attributes;
|
||||
use ipl\Html\BaseHtmlElement;
|
||||
use ipl\Html\HtmlElement;
|
||||
use ipl\Html\HtmlString;
|
||||
use ipl\Html\Text;
|
||||
use ipl\Web\Widget\Icon;
|
||||
use ipl\Web\Widget\StateBall;
|
||||
|
||||
class ServiceListItemDetailed extends BaseServiceListItem
|
||||
{
|
||||
use ListItemDetailedLayout;
|
||||
|
||||
/** @var int Max pie charts to be shown */
|
||||
const PIE_CHART_LIMIT = 5;
|
||||
|
||||
protected function getStateBallSize(): string
|
||||
{
|
||||
return StateBall::SIZE_LARGE;
|
||||
}
|
||||
|
||||
protected function assembleFooter(BaseHtmlElement $footer): void
|
||||
{
|
||||
$statusIcons = new HtmlElement('div', Attributes::create(['class' => 'status-icons']));
|
||||
|
||||
if ($this->item->state->last_comment->service_id === $this->item->id) {
|
||||
$comment = $this->item->state->last_comment;
|
||||
$comment->service = $this->item;
|
||||
$comment = (new CommentList([$comment]))
|
||||
->setNoSubjectLink()
|
||||
->setObjectLinkDisabled()
|
||||
->setDetailActionsDisabled();
|
||||
|
||||
$statusIcons->addHtml(
|
||||
new HtmlElement(
|
||||
'div',
|
||||
Attributes::create(['class' => 'comment-wrapper']),
|
||||
new HtmlElement('div', Attributes::create(['class' => 'comment-popup']), $comment),
|
||||
(new Icon('comments', ['class' => 'comment-icon']))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->item->state->is_flapping) {
|
||||
$statusIcons->addHtml(new Icon(
|
||||
'random',
|
||||
[
|
||||
'title' => sprintf(
|
||||
t('Service "%s" on "%s" is in flapping state'),
|
||||
$this->item->display_name,
|
||||
$this->item->host->display_name
|
||||
),
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
if (! $this->item->notifications_enabled) {
|
||||
$statusIcons->addHtml(new Icon('bell-slash', ['title' => t('Notifications disabled')]));
|
||||
}
|
||||
|
||||
if (! $this->item->active_checks_enabled) {
|
||||
$statusIcons->addHtml(new Icon('eye-slash', ['title' => t('Active checks disabled')]));
|
||||
}
|
||||
|
||||
$performanceData = new HtmlElement('div', Attributes::create(['class' => 'performance-data']));
|
||||
if ($this->item->state->performance_data) {
|
||||
$pieChartData = PerfDataSet::fromString($this->item->state->normalized_performance_data)->asArray();
|
||||
|
||||
$pies = [];
|
||||
foreach ($pieChartData as $i => $perfdata) {
|
||||
if ($perfdata->isVisualizable()) {
|
||||
$pies[] = $perfdata->asInlinePie()->render();
|
||||
}
|
||||
|
||||
// Check if number of visualizable pie charts is larger than PIE_CHART_LIMIT
|
||||
if (count($pies) > ServiceListItemDetailed::PIE_CHART_LIMIT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$maxVisiblePies = ServiceListItemDetailed::PIE_CHART_LIMIT - 2;
|
||||
$numOfPies = count($pies);
|
||||
foreach ($pies as $i => $pie) {
|
||||
if (
|
||||
// Show max. 5 elements: if there are more than 5, show 4 + `…`
|
||||
$i > $maxVisiblePies && $numOfPies > ServiceListItemDetailed::PIE_CHART_LIMIT
|
||||
) {
|
||||
$performanceData->addHtml(new HtmlElement('span', null, Text::create('…')));
|
||||
break;
|
||||
}
|
||||
|
||||
$performanceData->addHtml(HtmlString::create($pie));
|
||||
}
|
||||
}
|
||||
|
||||
if (! $statusIcons->isEmpty()) {
|
||||
$footer->addHtml($statusIcons);
|
||||
}
|
||||
|
||||
if (! $performanceData->isEmpty()) {
|
||||
$footer->addHtml($performanceData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +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 ServiceListItemMinimal extends BaseServiceListItem
|
||||
{
|
||||
use ListItemMinimalLayout;
|
||||
|
||||
protected function getStateBallSize(): string
|
||||
{
|
||||
return StateBall::SIZE_BIG;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
<?php
|
||||
|
||||
/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */
|
||||
|
||||
namespace Icinga\Module\Icingadb\Widget\ItemList;
|
||||
|
||||
use Icinga\Module\Icingadb\Common\DetailActions;
|
||||
use Icinga\Module\Icingadb\Common\NoSubjectLink;
|
||||
use Icinga\Module\Icingadb\Common\ViewMode;
|
||||
use Icinga\Module\Icingadb\Redis\VolatileStateResults;
|
||||
use Icinga\Module\Icingadb\Widget\Notice;
|
||||
use ipl\Html\HtmlDocument;
|
||||
use ipl\Web\Common\BaseItemList;
|
||||
|
||||
abstract class StateList extends BaseItemList
|
||||
{
|
||||
use ViewMode;
|
||||
use NoSubjectLink;
|
||||
use DetailActions;
|
||||
|
||||
/** @var bool Whether the list contains at least one item with an icon_image */
|
||||
protected $hasIconImages = false;
|
||||
|
||||
/**
|
||||
* Get whether the list contains at least one item with an icon_image
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasIconImages(): bool
|
||||
{
|
||||
return $this->hasIconImages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the list contains at least one item with an icon_image
|
||||
*
|
||||
* @param bool $hasIconImages
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setHasIconImages(bool $hasIconImages): self
|
||||
{
|
||||
$this->hasIconImages = $hasIconImages;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function assemble(): void
|
||||
{
|
||||
$this->addAttributes(['class' => $this->getViewMode()]);
|
||||
|
||||
parent::assemble();
|
||||
|
||||
if ($this->data instanceof VolatileStateResults && $this->data->isRedisUnavailable()) {
|
||||
$this->prependWrapper((new HtmlDocument())->addHtml(new Notice(
|
||||
t('Redis is currently unavailable. The shown information might be outdated.')
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,174 +0,0 @@
|
|||
<?php
|
||||
|
||||
/* Icinga DB Web | (c) 2020 Icinga GmbH | GPLv2 */
|
||||
|
||||
namespace Icinga\Module\Icingadb\Widget\ItemList;
|
||||
|
||||
use Icinga\Module\Icingadb\Common\Icons;
|
||||
use Icinga\Module\Icingadb\Model\State;
|
||||
use Icinga\Module\Icingadb\Util\PluginOutput;
|
||||
use Icinga\Module\Icingadb\Widget\CheckAttempt;
|
||||
use Icinga\Module\Icingadb\Widget\IconImage;
|
||||
use Icinga\Module\Icingadb\Widget\PluginOutputContainer;
|
||||
use ipl\Html\Attributes;
|
||||
use ipl\Html\HtmlElement;
|
||||
use ipl\I18n\Translation;
|
||||
use ipl\Web\Common\BaseListItem;
|
||||
use ipl\Web\Widget\EmptyState;
|
||||
use ipl\Web\Widget\TimeSince;
|
||||
use ipl\Html\BaseHtmlElement;
|
||||
use ipl\Html\Html;
|
||||
use ipl\Html\Text;
|
||||
use ipl\Web\Widget\Icon;
|
||||
use ipl\Web\Widget\StateBall;
|
||||
|
||||
/**
|
||||
* Host or service item of a host or service list. Represents one database row.
|
||||
*/
|
||||
abstract class StateListItem extends BaseListItem
|
||||
{
|
||||
use Translation;
|
||||
|
||||
/** @var StateList The list where the item is part of */
|
||||
protected $list;
|
||||
|
||||
/** @var State The state of the item */
|
||||
protected $state;
|
||||
|
||||
protected function init(): void
|
||||
{
|
||||
$this->state = $this->item->state;
|
||||
|
||||
if (isset($this->item->icon_image->icon_image)) {
|
||||
$this->list->setHasIconImages(true);
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected function createSubject();
|
||||
|
||||
abstract protected function getStateBallSize(): string;
|
||||
|
||||
/**
|
||||
* @return ?BaseHtmlElement
|
||||
*/
|
||||
protected function createIconImage(): ?BaseHtmlElement
|
||||
{
|
||||
if (! $this->list->hasIconImages()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$iconImage = HtmlElement::create('div', [
|
||||
'class' => 'icon-image',
|
||||
]);
|
||||
|
||||
$this->assembleIconImage($iconImage);
|
||||
|
||||
return $iconImage;
|
||||
}
|
||||
|
||||
protected function assembleCaption(BaseHtmlElement $caption): void
|
||||
{
|
||||
if ($this->state->soft_state === null && $this->state->output === null) {
|
||||
$caption->addHtml(Text::create($this->translate('Waiting for Icinga DB to synchronize the state.')));
|
||||
} else {
|
||||
if (empty($this->state->output)) {
|
||||
$pluginOutput = new EmptyState($this->translate('Output unavailable.'));
|
||||
} else {
|
||||
$pluginOutput = new PluginOutputContainer(PluginOutput::fromObject($this->item));
|
||||
}
|
||||
|
||||
$caption->addHtml($pluginOutput);
|
||||
}
|
||||
}
|
||||
|
||||
protected function assembleIconImage(BaseHtmlElement $iconImage): void
|
||||
{
|
||||
if (isset($this->item->icon_image->icon_image)) {
|
||||
$iconImage->addHtml(new IconImage($this->item->icon_image->icon_image, $this->item->icon_image_alt));
|
||||
} else {
|
||||
$iconImage->addAttributes(['class' => 'placeholder']);
|
||||
}
|
||||
}
|
||||
|
||||
protected function assembleTitle(BaseHtmlElement $title): void
|
||||
{
|
||||
$title->addHtml(Html::sprintf(
|
||||
$this->translate('%s is %s', '<hostname> is <state-text>'),
|
||||
$this->createSubject(),
|
||||
Html::tag('span', ['class' => 'state-text'], $this->state->getStateTextTranslated())
|
||||
));
|
||||
|
||||
if (isset($this->state->affects_children) && $this->state->affects_children) {
|
||||
$total = (int) $this->item->total_children;
|
||||
|
||||
if ($total > 1000) {
|
||||
$total = '1000+';
|
||||
$tooltip = $this->translate('Up to 1000+ affected objects');
|
||||
} else {
|
||||
$tooltip = sprintf(
|
||||
$this->translatePlural(
|
||||
'%d affected object',
|
||||
'Up to %d affected objects',
|
||||
$total
|
||||
),
|
||||
$total
|
||||
);
|
||||
}
|
||||
|
||||
$icon = new Icon(Icons::UNREACHABLE);
|
||||
|
||||
$title->addHtml(new HtmlElement(
|
||||
'span',
|
||||
Attributes::create([
|
||||
'class' => 'affected-objects',
|
||||
'title' => $tooltip
|
||||
]),
|
||||
$icon,
|
||||
Text::create($total)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
protected function assembleVisual(BaseHtmlElement $visual): void
|
||||
{
|
||||
$stateBall = new StateBall($this->state->getStateText(), $this->getStateBallSize());
|
||||
$stateBall->add($this->state->getIcon());
|
||||
if ($this->state->is_problem && ($this->state->is_handled || ! $this->state->is_reachable)) {
|
||||
$stateBall->getAttributes()->add('class', 'handled');
|
||||
}
|
||||
|
||||
$visual->addHtml($stateBall);
|
||||
if ($this->state->state_type === 'soft') {
|
||||
$visual->addHtml(
|
||||
new CheckAttempt((int) $this->state->check_attempt, (int) $this->item->max_check_attempts)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function createTimestamp(): ?BaseHtmlElement
|
||||
{
|
||||
$since = null;
|
||||
if ($this->state->is_overdue) {
|
||||
$since = new TimeSince($this->state->next_update->getTimestamp());
|
||||
$since->prepend($this->translate('Overdue') . ' ');
|
||||
$since->prependHtml(new Icon(Icons::WARNING));
|
||||
} elseif ($this->state->last_state_change !== null && $this->state->last_state_change->getTimestamp() > 0) {
|
||||
$since = new TimeSince($this->state->last_state_change->getTimestamp());
|
||||
}
|
||||
|
||||
return $since;
|
||||
}
|
||||
|
||||
protected function assemble(): void
|
||||
{
|
||||
if ($this->state->is_overdue) {
|
||||
$this->addAttributes(['class' => 'overdue']);
|
||||
}
|
||||
|
||||
$this->add([
|
||||
$this->createVisual(),
|
||||
$this->createIconImage(),
|
||||
$this->createMain()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -384,7 +384,8 @@ div.show-more {
|
|||
}
|
||||
|
||||
.history-list,
|
||||
.objectHeader {
|
||||
.header-item-layout.host,
|
||||
.header-item-layout.service {
|
||||
.visual.small-state-change .state-change {
|
||||
padding-top: .25em;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,10 +56,6 @@
|
|||
> .empty-state {
|
||||
padding: .25em;
|
||||
}
|
||||
|
||||
.check-attempt {
|
||||
display: none; // TODO: The new renderer shouldn't render it in the first place
|
||||
}
|
||||
}
|
||||
|
||||
.item-list.minimal {
|
||||
|
|
@ -143,7 +139,8 @@
|
|||
|
||||
// TODO: Can be simplified (`.controls .list-item`) once the only list-items in controls are those in the multi select views
|
||||
.controls .item-list:not(.detailed):not(.minimal) .list-item {
|
||||
.plugin-output {
|
||||
.plugin-output { //TODO: why ?? multiselect items has bigger gap now
|
||||
//seems dead code, remove it when cleaning up
|
||||
line-height: 1.5
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue