mirror of
https://github.com/Icinga/icingadb-web.git
synced 2026-05-28 04:36:06 -04:00
Show root problem list for objects with problem and are part of dependency
This commit is contained in:
parent
0e67df510a
commit
e22bd1bde8
14 changed files with 538 additions and 51 deletions
|
|
@ -38,13 +38,15 @@ class ServiceController extends Controller
|
|||
$name = $this->params->getRequired('name');
|
||||
$hostName = $this->params->getRequired('host.name');
|
||||
|
||||
$query = Service::on($this->getDb())->with([
|
||||
'state',
|
||||
'icon_image',
|
||||
'host',
|
||||
'host.state',
|
||||
'timeperiod'
|
||||
]);
|
||||
$query = Service::on($this->getDb())
|
||||
->withColumns(['has_problematic_parent'])
|
||||
->with([
|
||||
'state',
|
||||
'icon_image',
|
||||
'host',
|
||||
'host.state',
|
||||
'timeperiod'
|
||||
]);
|
||||
$query
|
||||
->setResultSetClass(VolatileStateResults::class)
|
||||
->filter(Filter::all(
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@
|
|||
|
||||
namespace Icinga\Module\Icingadb\Common;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use ipl\Html\BaseHtmlElement;
|
||||
use ipl\Html\Html;
|
||||
use ipl\Html\HtmlElement;
|
||||
use ipl\Stdlib\BaseFilter;
|
||||
use ipl\Stdlib\Filter;
|
||||
use ipl\Web\Filter\QueryString;
|
||||
use ipl\Web\Url;
|
||||
use ipl\Web\Widget\Link;
|
||||
use ipl\Web\Widget\StateBadge;
|
||||
|
|
@ -26,7 +27,7 @@ abstract class StateBadges extends BaseHtmlElement
|
|||
/** @var string Prefix */
|
||||
protected $prefix;
|
||||
|
||||
/** @var Url Badge link */
|
||||
/** @var ?Url Badge link */
|
||||
protected $url;
|
||||
|
||||
protected $tag = 'ul';
|
||||
|
|
@ -46,13 +47,6 @@ abstract class StateBadges extends BaseHtmlElement
|
|||
$this->url = $this->getBaseUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the badge base URL
|
||||
*
|
||||
* @return Url
|
||||
*/
|
||||
abstract protected function getBaseUrl(): Url;
|
||||
|
||||
/**
|
||||
* Get the type of the items
|
||||
*
|
||||
|
|
@ -67,21 +61,36 @@ abstract class StateBadges extends BaseHtmlElement
|
|||
*/
|
||||
abstract protected function getPrefix(): string;
|
||||
|
||||
/**
|
||||
* Get the badge base URL
|
||||
*
|
||||
* @return ?Url
|
||||
*/
|
||||
protected function getBaseUrl(): ?Url
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the integer of the given state text
|
||||
*
|
||||
* @param string $state
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
* @throws InvalidArgumentException if the given state is not valid
|
||||
*/
|
||||
abstract protected function getStateInt(string $state): int;
|
||||
protected function getStateInt(string $state): int
|
||||
{
|
||||
throw new InvalidArgumentException(sprintf('%s is not a valid state', $state));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the badge URL
|
||||
*
|
||||
* @return Url
|
||||
* @return ?Url
|
||||
*/
|
||||
public function getUrl(): Url
|
||||
public function getUrl(): ?Url
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
|
|
@ -108,7 +117,7 @@ abstract class StateBadges extends BaseHtmlElement
|
|||
*
|
||||
* @return Link
|
||||
*/
|
||||
public function createLink($content, Filter\Rule $filter = null): Link
|
||||
protected function createLink($content, Filter\Rule $filter = null): Link
|
||||
{
|
||||
$url = clone $this->getUrl();
|
||||
|
||||
|
|
@ -135,18 +144,23 @@ abstract class StateBadges extends BaseHtmlElement
|
|||
*
|
||||
* @return ?BaseHtmlElement
|
||||
*/
|
||||
protected function createBadge(string $state)
|
||||
protected function createBadge(string $state): ?BaseHtmlElement
|
||||
{
|
||||
$key = $this->prefix . "_{$state}";
|
||||
|
||||
if (isset($this->item->$key) && $this->item->$key) {
|
||||
return Html::tag('li', $this->createLink(
|
||||
new StateBadge($this->item->$key, $state),
|
||||
Filter::equal($this->type . '.state.soft_state', $this->getStateInt($state))
|
||||
));
|
||||
if (empty($this->item->$key)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
$stateBadge = new StateBadge($this->item->$key, $state);
|
||||
|
||||
if ($this->url !== null) {
|
||||
$this->createLink(
|
||||
$stateBadge,
|
||||
Filter::equal($this->type . '.state.soft_state', $this->getStateInt($state))
|
||||
);
|
||||
}
|
||||
|
||||
return new HtmlElement('li', null, $stateBadge);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -156,34 +170,46 @@ abstract class StateBadges extends BaseHtmlElement
|
|||
*
|
||||
* @return ?BaseHtmlElement
|
||||
*/
|
||||
protected function createGroup(string $state)
|
||||
protected function createGroup(string $state): ?BaseHtmlElement
|
||||
{
|
||||
$content = [];
|
||||
$handledKey = $this->prefix . "_{$state}_handled";
|
||||
$unhandledKey = $this->prefix . "_{$state}_unhandled";
|
||||
|
||||
if (isset($this->item->$unhandledKey) && $this->item->$unhandledKey) {
|
||||
$content[] = Html::tag('li', $this->createLink(
|
||||
new StateBadge($this->item->$unhandledKey, $state),
|
||||
Filter::all(
|
||||
Filter::equal($this->type . '.state.soft_state', $this->getStateInt($state)),
|
||||
Filter::equal($this->type . '.state.is_handled', 'n'),
|
||||
Filter::equal($this->type . '.state.is_reachable', 'y')
|
||||
)
|
||||
));
|
||||
$unhandledStateBadge = new StateBadge($this->item->$unhandledKey, $state);
|
||||
|
||||
if ($this->url !== null) {
|
||||
$unhandledStateBadge = $this->createLink(
|
||||
$unhandledStateBadge,
|
||||
Filter::all(
|
||||
Filter::equal($this->type . '.state.soft_state', $this->getStateInt($state)),
|
||||
Filter::equal($this->type . '.state.is_handled', 'n'),
|
||||
Filter::equal($this->type . '.state.is_reachable', 'y')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$content[] = new HtmlElement('li', null, $unhandledStateBadge);
|
||||
}
|
||||
|
||||
if (isset($this->item->$handledKey) && $this->item->$handledKey) {
|
||||
$content[] = Html::tag('li', $this->createLink(
|
||||
new StateBadge($this->item->$handledKey, $state, true),
|
||||
Filter::all(
|
||||
Filter::equal($this->type . '.state.soft_state', $this->getStateInt($state)),
|
||||
Filter::any(
|
||||
Filter::equal($this->type . '.state.is_handled', 'y'),
|
||||
Filter::equal($this->type . '.state.is_reachable', 'n')
|
||||
$handledStateBadge = new StateBadge($this->item->$handledKey, $state, true);
|
||||
|
||||
if ($this->url !== null) {
|
||||
$handledStateBadge = $this->createLink(
|
||||
$handledStateBadge,
|
||||
Filter::all(
|
||||
Filter::equal($this->type . '.state.soft_state', $this->getStateInt($state)),
|
||||
Filter::any(
|
||||
Filter::equal($this->type . '.state.is_handled', 'y'),
|
||||
Filter::equal($this->type . '.state.is_reachable', 'n')
|
||||
)
|
||||
)
|
||||
)
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
$content[] = new HtmlElement('li', null, $handledStateBadge);
|
||||
}
|
||||
|
||||
if (empty($content)) {
|
||||
|
|
|
|||
159
library/Icingadb/Model/RedundancyGroupSummary.php
Normal file
159
library/Icingadb/Model/RedundancyGroupSummary.php
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
|
||||
/* Icinga DB Web | (c) 2024 Icinga GmbH | GPLv2 */
|
||||
|
||||
namespace Icinga\Module\Icingadb\Model;
|
||||
|
||||
use ipl\Orm\Query;
|
||||
use ipl\Sql\Connection;
|
||||
use ipl\Sql\Expression;
|
||||
use ipl\Sql\Select;
|
||||
|
||||
/**
|
||||
* Redundancy group's summary
|
||||
*
|
||||
* @property int $nodes_total
|
||||
* @property int $nodes_ok
|
||||
* @property int $nodes_problem_handled
|
||||
* @property int $nodes_problem_unhandled
|
||||
* @property int $nodes_pending
|
||||
* @property int $nodes_unknown_handled
|
||||
* @property int $nodes_unknown_unhandled
|
||||
* @property int $nodes_warning_handled
|
||||
* @property int $nodes_warning_unhandled
|
||||
*/
|
||||
class RedundancyGroupSummary extends RedundancyGroup
|
||||
{
|
||||
public function getSummaryColumns(): array
|
||||
{
|
||||
return [
|
||||
'nodes_total' => new Expression('COUNT(*)'),
|
||||
'nodes_ok' => new Expression(
|
||||
'SUM(CASE'
|
||||
. ' WHEN %s IS NOT NULL THEN (CASE WHEN %s = 0 THEN 1 ELSE 0 END)'
|
||||
. ' WHEN %s = 0 THEN 1'
|
||||
. ' ELSE 0'
|
||||
. ' END)',
|
||||
[
|
||||
'from.to.service_id',
|
||||
'from.to.service.state.soft_state',
|
||||
'from.to.host.state.soft_state',
|
||||
]
|
||||
),
|
||||
'nodes_problem_handled' => new Expression(
|
||||
'SUM(CASE'
|
||||
. " WHEN %s IS NOT NULL THEN (CASE WHEN %s = 2 AND (%s = 'y' OR %s = 'n') THEN 1 ELSE 0 END)"
|
||||
. " WHEN %s = 1 AND (%s = 'y' OR %s = 'n') THEN 1"
|
||||
. ' ELSE 0'
|
||||
. ' END)',
|
||||
[
|
||||
'from.to.service_id',
|
||||
'from.to.service.state.soft_state',
|
||||
'from.to.service.state.is_handled',
|
||||
'from.to.service.state.is_reachable',
|
||||
'from.to.host.state.soft_state',
|
||||
'from.to.host.state.is_handled',
|
||||
'from.to.host.state.is_reachable',
|
||||
]
|
||||
),
|
||||
'nodes_problem_unhandled' => new Expression(
|
||||
'SUM(CASE'
|
||||
. " WHEN %s IS NOT NULL THEN (CASE WHEN %s = 2 AND (%s = 'n' AND %s = 'y') THEN 1 ELSE 0 END)"
|
||||
. " WHEN %s = 1 AND (%s = 'n' AND %s = 'y') THEN 1"
|
||||
. ' ELSE 0'
|
||||
. ' END)',
|
||||
[
|
||||
'from.to.service_id',
|
||||
'from.to.service.state.soft_state',
|
||||
'from.to.service.state.is_handled',
|
||||
'from.to.service.state.is_reachable',
|
||||
'from.to.host.state.soft_state',
|
||||
'from.to.host.state.is_handled',
|
||||
'from.to.host.state.is_reachable',
|
||||
]
|
||||
),
|
||||
'nodes_pending' => new Expression(
|
||||
'SUM(CASE'
|
||||
. ' WHEN %s IS NOT NULL THEN (CASE WHEN %s = 99 THEN 1 ELSE 0 END)'
|
||||
. ' WHEN %s = 99 THEN 1'
|
||||
. ' ELSE 0'
|
||||
. ' END)',
|
||||
[
|
||||
'from.to.service_id',
|
||||
'from.to.service.state.soft_state',
|
||||
'from.to.host.state.soft_state',
|
||||
]
|
||||
),
|
||||
'nodes_unknown_handled' => new Expression(
|
||||
'SUM(CASE'
|
||||
. " WHEN %s IS NOT NULL THEN (CASE WHEN %s = 3 AND (%s = 'y' OR %s = 'n') THEN 1 ELSE 0 END)"
|
||||
. ' ELSE 0'
|
||||
. ' END)',
|
||||
[
|
||||
'from.to.service_id',
|
||||
'from.to.service.state.soft_state',
|
||||
'from.to.service.state.is_handled',
|
||||
'from.to.service.state.is_reachable'
|
||||
]
|
||||
),
|
||||
'nodes_unknown_unhandled' => new Expression(
|
||||
'SUM(CASE'
|
||||
. " WHEN %s IS NOT NULL THEN (CASE WHEN %s = 3 AND (%s = 'n' AND %s = 'y') THEN 1 ELSE 0 END)"
|
||||
. ' ELSE 0'
|
||||
. ' END)',
|
||||
[
|
||||
'from.to.service_id',
|
||||
'from.to.service.state.soft_state',
|
||||
'from.to.service.state.is_handled',
|
||||
'from.to.service.state.is_reachable'
|
||||
]
|
||||
),
|
||||
'nodes_warning_handled' => new Expression(
|
||||
'SUM(CASE'
|
||||
. " WHEN %s IS NOT NULL THEN (CASE WHEN %s = 1 AND (%s = 'y' OR %s = 'n') THEN 1 ELSE 0 END)"
|
||||
. ' ELSE 0'
|
||||
. ' END)',
|
||||
[
|
||||
'from.to.service_id',
|
||||
'from.to.service.state.soft_state',
|
||||
'from.to.service.state.is_handled',
|
||||
'from.to.service.state.is_reachable'
|
||||
]
|
||||
),
|
||||
'nodes_warning_unhandled' => new Expression(
|
||||
'SUM(CASE'
|
||||
. " WHEN %s IS NOT NULL THEN (CASE WHEN %s = 1 AND (%s = 'n' AND %s = 'y') THEN 1 ELSE 0 END)"
|
||||
. ' ELSE 0'
|
||||
. ' END)',
|
||||
[
|
||||
'from.to.service_id',
|
||||
'from.to.service.state.soft_state',
|
||||
'from.to.service.state.is_handled',
|
||||
'from.to.service.state.is_reachable'
|
||||
]
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
public static function on(Connection $db): Query
|
||||
{
|
||||
$q = parent::on($db);
|
||||
|
||||
/** @var static $m */
|
||||
$m = $q->getModel();
|
||||
$q->columns($m->getSummaryColumns());
|
||||
|
||||
$q->on($q::ON_SELECT_ASSEMBLED, function (Select $select) use ($q) {
|
||||
$model = $q->getModel();
|
||||
$groupBy = $q->getResolver()->qualifyColumnsAndAliases((array) $model->getKeyName(), $model, false);
|
||||
$select->groupBy($groupBy);
|
||||
});
|
||||
|
||||
return $q;
|
||||
}
|
||||
|
||||
public function getColumns(): array
|
||||
{
|
||||
return array_merge(parent::getColumns(), $this->getSummaryColumns());
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ namespace Icinga\Module\Icingadb\Model;
|
|||
|
||||
use Icinga\Module\Icingadb\Common\Auth;
|
||||
use Icinga\Module\Icingadb\Model\Behavior\BoolCast;
|
||||
use Icinga\Module\Icingadb\Model\Behavior\HasProblematicParent;
|
||||
use Icinga\Module\Icingadb\Model\Behavior\ReRoute;
|
||||
use ipl\Orm\Behavior\Binary;
|
||||
use ipl\Orm\Behaviors;
|
||||
|
|
@ -194,6 +195,8 @@ class Service extends Model
|
|||
'zone_id',
|
||||
'command_endpoint_id'
|
||||
]));
|
||||
|
||||
$behaviors->add(new HasProblematicParent());
|
||||
}
|
||||
|
||||
public function createDefaults(Defaults $defaults)
|
||||
|
|
|
|||
36
library/Icingadb/Widget/DependencyNodeStateBadges.php
Normal file
36
library/Icingadb/Widget/DependencyNodeStateBadges.php
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
/* Icinga DB Web | (c) 2024 Icinga GmbH | GPLv2 */
|
||||
|
||||
namespace Icinga\Module\Icingadb\Widget;
|
||||
|
||||
use Icinga\Module\Icingadb\Common\StateBadges;
|
||||
|
||||
/**
|
||||
* State badges for the dependency nodes
|
||||
*/
|
||||
class DependencyNodeStateBadges extends StateBadges
|
||||
{
|
||||
protected function getType(): string
|
||||
{
|
||||
return 'nodes';
|
||||
}
|
||||
|
||||
protected function getPrefix(): string
|
||||
{
|
||||
return 'nodes';
|
||||
}
|
||||
|
||||
protected function assemble(): void
|
||||
{
|
||||
$this->addAttributes(['class' => 'dependency-node-state-badges']);
|
||||
|
||||
$this->add(array_filter([
|
||||
$this->createGroup('problem'),
|
||||
$this->createGroup('warning'),
|
||||
$this->createGroup('unknown'),
|
||||
$this->createBadge('ok'),
|
||||
$this->createBadge('pending')
|
||||
]));
|
||||
}
|
||||
}
|
||||
49
library/Icingadb/Widget/DependencyNodeStatistics.php
Normal file
49
library/Icingadb/Widget/DependencyNodeStatistics.php
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
/* Icinga DB Web | (c) 2024 Icinga GmbH | GPLv2 */
|
||||
|
||||
namespace Icinga\Module\Icingadb\Widget;
|
||||
|
||||
use Icinga\Chart\Donut;
|
||||
use Icinga\Module\Icingadb\Widget\Detail\ObjectStatistics;
|
||||
use ipl\Html\Text;
|
||||
use ipl\Html\ValidHtml;
|
||||
use ipl\Html\HtmlString;
|
||||
|
||||
/**
|
||||
* Dependency node statistics
|
||||
*/
|
||||
class DependencyNodeStatistics extends ObjectStatistics
|
||||
{
|
||||
protected $summary;
|
||||
|
||||
public function __construct($summary)
|
||||
{
|
||||
$this->summary = $summary;
|
||||
}
|
||||
|
||||
protected function createDonut(): ValidHtml
|
||||
{
|
||||
$donut = (new Donut())
|
||||
->addSlice($this->summary->nodes_ok, ['class' => 'slice-state-ok'])
|
||||
->addSlice($this->summary->nodes_warning_handled, ['class' => 'slice-state-warning-handled'])
|
||||
->addSlice($this->summary->nodes_warning_unhandled, ['class' => 'slice-state-warning'])
|
||||
->addSlice($this->summary->nodes_problem_handled, ['class' => 'slice-state-critical-handled'])
|
||||
->addSlice($this->summary->nodes_problem_unhandled, ['class' => 'slice-state-critical'])
|
||||
->addSlice($this->summary->nodes_unknown_handled, ['class' => 'slice-state-unknown-handled'])
|
||||
->addSlice($this->summary->nodes_unknown_unhandled, ['class' => 'slice-state-unknown'])
|
||||
->addSlice($this->summary->nodes_pending, ['class' => 'slice-state-pending']);
|
||||
|
||||
return HtmlString::create($donut->render());
|
||||
}
|
||||
|
||||
protected function createTotal(): ValidHtml
|
||||
{
|
||||
return Text::create($this->shortenAmount($this->summary->nodes_total));
|
||||
}
|
||||
|
||||
protected function createBadges(): ValidHtml
|
||||
{
|
||||
return new DependencyNodeStateBadges($this->summary);
|
||||
}
|
||||
}
|
||||
|
|
@ -41,7 +41,8 @@ class HostDetail extends ObjectDetail
|
|||
}
|
||||
|
||||
$this->add(ObjectDetailExtensionHook::injectExtensions([
|
||||
0 => $this->createPluginOutput(),
|
||||
0 => $this->createRootProblems(),
|
||||
1 => $this->createPluginOutput(),
|
||||
190 => $this->createServiceStatistics(),
|
||||
300 => $this->createActions(),
|
||||
301 => $this->createNotes(),
|
||||
|
|
|
|||
|
|
@ -20,9 +20,12 @@ use Icinga\Module\Icingadb\Common\Icons;
|
|||
use Icinga\Module\Icingadb\Common\Links;
|
||||
use Icinga\Module\Icingadb\Common\Macros;
|
||||
use Icinga\Module\Icingadb\Compat\CompatHost;
|
||||
use Icinga\Module\Icingadb\Compat\CompatService;
|
||||
use Icinga\Module\Icingadb\Model\CustomvarFlat;
|
||||
use Icinga\Module\Icingadb\Model\Service;
|
||||
use Icinga\Module\Icingadb\Model\UnreachableParent;
|
||||
use Icinga\Module\Icingadb\Redis\VolatileStateResults;
|
||||
use Icinga\Module\Icingadb\Web\Navigation\Action;
|
||||
use Icinga\Module\Icingadb\Widget\ItemList\DependencyNodeList;
|
||||
use Icinga\Module\Icingadb\Widget\MarkdownText;
|
||||
use Icinga\Module\Icingadb\Common\ServiceLinks;
|
||||
use Icinga\Module\Icingadb\Forms\Command\Object\ToggleObjectFeaturesForm;
|
||||
|
|
@ -373,7 +376,7 @@ class ObjectDetail extends BaseHtmlElement
|
|||
|
||||
protected function createNotifications(): array
|
||||
{
|
||||
list($users, $usergroups) = $this->getUsersAndUsergroups();
|
||||
[$users, $usergroups] = $this->getUsersAndUsergroups();
|
||||
|
||||
$userList = new TagList();
|
||||
$usergroupList = new TagList();
|
||||
|
|
@ -602,4 +605,56 @@ class ObjectDetail extends BaseHtmlElement
|
|||
$this->object->customvar_flat = $customvarFlat->execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a list of root problems of the object that is unreachable because of dependency failure
|
||||
*
|
||||
* @return ?BaseHtmlElement[]
|
||||
*/
|
||||
protected function createRootProblems(): ?array
|
||||
{
|
||||
// If a dependency has failed, then the children are not reachable. Hence, the root problems should not be shown
|
||||
// if the object is reachable. And in case of a service, since, it may be also be unreachable because of its
|
||||
// host being down, only show its root problems if it's really caused by a dependency failure.
|
||||
if (
|
||||
$this->object->state->is_reachable
|
||||
|| ($this->object instanceof Service && ! $this->object->has_problematic_parent)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$rootProblems = UnreachableParent::on($this->getDb(), $this->object)
|
||||
->with([
|
||||
'redundancy_group',
|
||||
'redundancy_group.state',
|
||||
'host',
|
||||
'host.state',
|
||||
'host.icon_image',
|
||||
'host.state.last_comment',
|
||||
'service',
|
||||
'service.state',
|
||||
'service.icon_image',
|
||||
'service.state.last_comment',
|
||||
'service.host',
|
||||
'service.host.state'
|
||||
])
|
||||
->setResultSetClass(VolatileStateResults::class)
|
||||
->orderBy([
|
||||
'host.state.severity',
|
||||
'host.state.last_state_change',
|
||||
'service.state.severity',
|
||||
'service.state.last_state_change',
|
||||
'redundancy_group.state.failed',
|
||||
'redundancy_group.state.last_state_change'
|
||||
], SORT_DESC);
|
||||
|
||||
$this->applyRestrictions($rootProblems);
|
||||
|
||||
return [
|
||||
HtmlElement::create('h2', null, Text::create(t('Root Problems'))),
|
||||
(new DependencyNodeList($rootProblems))->setEmptyStateMessage(
|
||||
t('You are not authorized to view these objects.')
|
||||
)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ class ServiceDetail extends ObjectDetail
|
|||
}
|
||||
|
||||
$this->add(ObjectDetailExtensionHook::injectExtensions([
|
||||
0 => $this->createPluginOutput(),
|
||||
0 => $this->createRootProblems(),
|
||||
1 => $this->createPluginOutput(),
|
||||
300 => $this->createActions(),
|
||||
301 => $this->createNotes(),
|
||||
400 => $this->createComments(),
|
||||
|
|
|
|||
39
library/Icingadb/Widget/ItemList/DependencyNodeList.php
Normal file
39
library/Icingadb/Widget/ItemList/DependencyNodeList.php
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
/* Icinga DB Web | (c) 2024 Icinga GmbH | GPLv2 */
|
||||
|
||||
namespace Icinga\Module\Icingadb\Widget\ItemList;
|
||||
|
||||
use Icinga\Module\Icingadb\Model\DependencyNode;
|
||||
use Icinga\Module\Icingadb\Model\UnreachableParent;
|
||||
use ipl\Web\Common\BaseListItem;
|
||||
|
||||
/**
|
||||
* Dependency node list
|
||||
*/
|
||||
class DependencyNodeList extends StateList
|
||||
{
|
||||
protected $defaultAttributes = ['class' => ['dependency-node-list']];
|
||||
|
||||
protected function init(): void
|
||||
{
|
||||
$this->initializeDetailActions();
|
||||
}
|
||||
|
||||
protected function getItemClass(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
protected function createListItem(object $data): BaseListItem
|
||||
{
|
||||
/** @var UnreachableParent|DependencyNode $data */
|
||||
if ($data->redundancy_group_id !== null) {
|
||||
return new RedundancyGroupListItem($data->redundancy_group, $this);
|
||||
} elseif ($data->service_id !== null) {
|
||||
return new ServiceListItem($data->service, $this);
|
||||
} else {
|
||||
return new HostListItem($data->host, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
91
library/Icingadb/Widget/ItemList/RedundancyGroupListItem.php
Normal file
91
library/Icingadb/Widget/ItemList/RedundancyGroupListItem.php
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
|
||||
/* Icinga DB Web | (c) 2024 Icinga GmbH | GPLv2 */
|
||||
|
||||
namespace Icinga\Module\Icingadb\Widget\ItemList;
|
||||
|
||||
use Icinga\Module\Icingadb\Common\Database;
|
||||
use Icinga\Module\Icingadb\Common\ListItemCommonLayout;
|
||||
use Icinga\Module\Icingadb\Model\RedundancyGroup;
|
||||
use Icinga\Module\Icingadb\Model\RedundancyGroupSummary;
|
||||
use Icinga\Module\Icingadb\Model\RedundancyGroupState;
|
||||
use Icinga\Module\Icingadb\Widget\DependencyNodeStatistics;
|
||||
use ipl\Html\BaseHtmlElement;
|
||||
use ipl\I18n\Translation;
|
||||
use ipl\Stdlib\Filter;
|
||||
use ipl\Web\Widget\StateBall;
|
||||
use ipl\Html\HtmlElement;
|
||||
use ipl\Html\Attributes;
|
||||
use ipl\Html\Text;
|
||||
use ipl\Web\Widget\TimeSince;
|
||||
|
||||
/**
|
||||
* Redundancy group list item. Represents one database row.
|
||||
*
|
||||
* @property RedundancyGroup $item
|
||||
*/
|
||||
class RedundancyGroupListItem extends StateListItem
|
||||
{
|
||||
use ListItemCommonLayout;
|
||||
use Database;
|
||||
use Translation;
|
||||
|
||||
protected $defaultAttributes = ['class' => ['redundancy-group-list-item']];
|
||||
|
||||
/** @var RedundancyGroupState */
|
||||
protected $state;
|
||||
|
||||
protected function getStateBallSize(): string
|
||||
{
|
||||
return StateBall::SIZE_LARGE;
|
||||
}
|
||||
|
||||
protected function createTimestamp(): BaseHtmlElement
|
||||
{
|
||||
return new TimeSince($this->state->last_state_change->getTimestamp());
|
||||
}
|
||||
|
||||
protected function createSubject(): BaseHtmlElement
|
||||
{
|
||||
return new HtmlElement(
|
||||
'span',
|
||||
Attributes::create(['class' => 'subject']),
|
||||
Text::create($this->item->display_name)
|
||||
);
|
||||
}
|
||||
|
||||
protected function assembleVisual(BaseHtmlElement $visual): void
|
||||
{
|
||||
$visual->addHtml(new StateBall($this->state->getStateText(), $this->getStateBallSize()));
|
||||
}
|
||||
|
||||
protected function assembleCaption(BaseHtmlElement $caption): void
|
||||
{
|
||||
$caption->addHtml(new DependencyNodeStatistics(
|
||||
RedundancyGroupSummary::on($this->getDb())
|
||||
->filter(Filter::equal('id', $this->item->id))
|
||||
->first()
|
||||
));
|
||||
}
|
||||
|
||||
protected function assembleTitle(BaseHtmlElement $title): void
|
||||
{
|
||||
$title->addHtml($this->createSubject());
|
||||
if ($this->state->failed) {
|
||||
$text = $this->translate('has no working objects');
|
||||
} else {
|
||||
$text = $this->translate('has working objects');
|
||||
}
|
||||
|
||||
$title->addHtml(HtmlElement::create('span', null, Text::create($text)));
|
||||
}
|
||||
|
||||
protected function assemble(): void
|
||||
{
|
||||
$this->add([
|
||||
$this->createVisual(),
|
||||
$this->createIconImage(),
|
||||
$this->createMain()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -197,7 +197,7 @@ div.show-more {
|
|||
margin-left: 1em / 1.333em; // 1em / h2 font size
|
||||
}
|
||||
|
||||
.object-detail .plugin-output {
|
||||
.object-detail :not(.caption) > .plugin-output {
|
||||
.rounded-corners(.25em);
|
||||
background-color: @gray-lighter;
|
||||
padding: .5em;
|
||||
|
|
@ -412,3 +412,13 @@ form[name="form_confirm_removal"] {
|
|||
padding: 0 0.25em;
|
||||
.rounded-corners();
|
||||
}
|
||||
|
||||
.state-ball {
|
||||
&.state-unreachable {
|
||||
background-color: @color-critical;
|
||||
}
|
||||
|
||||
&.state-reachable {
|
||||
background-color: @color-ok;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
6
public/css/list/redundancy-group-list-item.less
Normal file
6
public/css/list/redundancy-group-list-item.less
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
.redundancy-group-list-item {
|
||||
.caption {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
}
|
||||
}
|
||||
9
public/css/widget/dependency-node-state-badges.less
Normal file
9
public/css/widget/dependency-node-state-badges.less
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
.dependency-node-state-badges {
|
||||
.state-badges();
|
||||
|
||||
.state-badge {
|
||||
&.state-problem {
|
||||
background-color: @color-critical;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue