mirror of
https://github.com/Icinga/icingadb-web.git
synced 2026-05-28 04:36:06 -04:00
This was easy because only README.md and doc/01-About.md were redacted manually, everything else via: git ls-files -z |xargs -0 perl -pi -e 's/Icinga GmbH \| GPLv2/Icinga GmbH | GPLv2+/' This is legal because we have only merged PRs with label:cla/signed or made by Icinga staff: https://github.com/Icinga/icingadb-web/pulls?page=1&q=is%3Apr+is%3Aclosed+-label%3Acla%2Fsigned+-author%3Anilmerg This has no risk for us in people distributing their own version under GPLv3 only. After all, we won't take their patches anyway, unless they sign our CLA. This is the cleanest solution for having e.g. these in one address space: * Icinga Web, GPLv2+ * K8s Web, AGPLv3 * Thirdparty, some LGPLv3 and Apache-2.0 Apropos, K8s Web is even v3-licensed on purpose, to have a stronger protection against cloud ops.
427 lines
16 KiB
PHP
427 lines
16 KiB
PHP
<?php
|
|
|
|
/* Icinga DB Web | (c) 2025 Icinga GmbH | GPLv2+ */
|
|
|
|
namespace Icinga\Module\Icingadb\View;
|
|
|
|
use Icinga\Date\DateFormatter;
|
|
use Icinga\Module\Icingadb\Common\HostLink;
|
|
use Icinga\Module\Icingadb\Common\HostStates;
|
|
use Icinga\Module\Icingadb\Common\Icons;
|
|
use Icinga\Module\Icingadb\Common\Links;
|
|
use Icinga\Module\Icingadb\Common\ServiceLink;
|
|
use Icinga\Module\Icingadb\Common\ServiceStates;
|
|
use Icinga\Module\Icingadb\Common\TicketLinks;
|
|
use Icinga\Module\Icingadb\Model\History;
|
|
use Icinga\Module\Icingadb\Util\PluginOutput;
|
|
use Icinga\Module\Icingadb\Widget\CheckAttempt;
|
|
use Icinga\Module\Icingadb\Widget\MarkdownLine;
|
|
use Icinga\Module\Icingadb\Widget\PluginOutputContainer;
|
|
use Icinga\Module\Icingadb\Widget\StateChange;
|
|
use ipl\Html\Attributes;
|
|
use ipl\Html\HtmlDocument;
|
|
use ipl\Html\HtmlElement;
|
|
use ipl\Html\Text;
|
|
use ipl\I18n\Translation;
|
|
use ipl\Web\Common\ItemRenderer;
|
|
use ipl\Web\Widget\EmptyState;
|
|
use ipl\Web\Widget\Icon;
|
|
use ipl\Web\Widget\Link;
|
|
use ipl\Web\Widget\StateBall;
|
|
use ipl\Web\Widget\TimeAgo;
|
|
|
|
/** @implements ItemRenderer<History> */
|
|
class EventRenderer implements ItemRenderer
|
|
{
|
|
use Translation;
|
|
use TicketLinks;
|
|
use HostLink;
|
|
use ServiceLink;
|
|
|
|
/** @var NotificationRenderer To render NotificationHistory event */
|
|
protected $notificationRenderer;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->notificationRenderer = new NotificationRenderer();
|
|
}
|
|
|
|
public function assembleAttributes($item, Attributes $attributes, string $layout): void
|
|
{
|
|
$attributes->get('class')->addValue('history');
|
|
}
|
|
|
|
public function assembleVisual($item, HtmlDocument $visual, string $layout): void
|
|
{
|
|
$ballSize = StateBall::SIZE_LARGE;
|
|
if ($layout === 'minimal' || $layout === 'header') {
|
|
$ballSize = StateBall::SIZE_BIG;
|
|
}
|
|
|
|
switch ($item->event_type) {
|
|
case 'comment_add':
|
|
$visual->addHtml(
|
|
HtmlElement::create(
|
|
'div',
|
|
['class' => ['icon-ball', 'ball-size-' . $ballSize]],
|
|
new Icon(Icons::COMMENT)
|
|
)
|
|
);
|
|
|
|
break;
|
|
case 'comment_remove':
|
|
case 'downtime_end':
|
|
case 'ack_clear':
|
|
$visual->addHtml(
|
|
HtmlElement::create(
|
|
'div',
|
|
['class' => ['icon-ball', 'ball-size-' . $ballSize]],
|
|
new Icon(Icons::REMOVE)
|
|
)
|
|
);
|
|
|
|
break;
|
|
case 'downtime_start':
|
|
$visual->addHtml(
|
|
HtmlElement::create(
|
|
'div',
|
|
['class' => ['icon-ball', 'ball-size-' . $ballSize]],
|
|
new Icon(Icons::IN_DOWNTIME)
|
|
)
|
|
);
|
|
|
|
break;
|
|
case 'ack_set':
|
|
$visual->addHtml(
|
|
HtmlElement::create(
|
|
'div',
|
|
['class' => ['icon-ball', 'ball-size-' . $ballSize]],
|
|
new Icon(Icons::IS_ACKNOWLEDGED)
|
|
)
|
|
);
|
|
|
|
break;
|
|
case 'flapping_end':
|
|
case 'flapping_start':
|
|
$visual->addHtml(
|
|
HtmlElement::create(
|
|
'div',
|
|
['class' => ['icon-ball', 'ball-size-' . $ballSize]],
|
|
new Icon(Icons::IS_FLAPPING)
|
|
)
|
|
);
|
|
|
|
break;
|
|
case 'notification':
|
|
$visual->addHtml(
|
|
HtmlElement::create(
|
|
'div',
|
|
['class' => ['icon-ball', 'ball-size-' . $ballSize]],
|
|
new Icon(Icons::NOTIFICATION)
|
|
)
|
|
);
|
|
|
|
break;
|
|
case 'state_change':
|
|
if ($item->state->state_type === 'soft') {
|
|
$stateType = 'soft_state';
|
|
$previousStateType = 'previous_soft_state';
|
|
|
|
if ($item->state->previous_soft_state === 0) {
|
|
$previousStateType = 'hard_state';
|
|
}
|
|
|
|
if ($layout !== 'minimal' && $layout !== 'header') {
|
|
$visual->addHtml(
|
|
new CheckAttempt(
|
|
(int) $item->state->check_attempt,
|
|
(int) $item->state->max_check_attempts
|
|
)
|
|
);
|
|
}
|
|
} else {
|
|
$stateType = 'hard_state';
|
|
$previousStateType = 'previous_hard_state';
|
|
|
|
if ($item->state->hard_state === $item->state->previous_hard_state) {
|
|
$previousStateType = 'previous_soft_state';
|
|
}
|
|
}
|
|
|
|
if ($item->object_type === 'host') {
|
|
$state = HostStates::text($item->state->$stateType);
|
|
$previousState = HostStates::text($item->state->$previousStateType);
|
|
} else {
|
|
$state = ServiceStates::text($item->state->$stateType);
|
|
$previousState = ServiceStates::text($item->state->$previousStateType);
|
|
}
|
|
|
|
$stateChange = new StateChange($state, $previousState);
|
|
if ($stateType === 'soft_state') {
|
|
$stateChange->setCurrentStateBallSize(StateBall::SIZE_MEDIUM_LARGE);
|
|
}
|
|
|
|
if ($previousStateType === 'previous_soft_state') {
|
|
$stateChange->setPreviousStateBallSize(StateBall::SIZE_MEDIUM_LARGE);
|
|
if ($stateType === 'soft_state') {
|
|
$visual->getAttributes()->add('class', 'small-state-change');
|
|
}
|
|
}
|
|
|
|
$visual->prependHtml($stateChange);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
public function assembleTitle($item, HtmlDocument $title, string $layout): void
|
|
{
|
|
if ($item->event_type === 'notification' && isset($item->notification->id)) {
|
|
$item->notification->history = $item;
|
|
$item->notification->host = $item->host;
|
|
$item->notification->service = $item->service;
|
|
|
|
$this->notificationRenderer->assembleTitle($item->notification, $title, $layout);
|
|
|
|
return;
|
|
}
|
|
|
|
switch ($item->event_type) {
|
|
case 'comment_add':
|
|
$subjectLabel = $this->translate('Comment added');
|
|
|
|
break;
|
|
case 'comment_remove':
|
|
if (! empty($item->comment->removed_by)) {
|
|
if ($item->comment->removed_by !== $item->comment->author) {
|
|
$subjectLabel = sprintf(
|
|
$this->translate('Comment removed by %s', '..<username>'),
|
|
$item->comment->removed_by
|
|
);
|
|
} else {
|
|
$subjectLabel = $this->translate('Comment removed by author');
|
|
}
|
|
} elseif (isset($item->comment->expire_time)) {
|
|
$subjectLabel = $this->translate('Comment expired');
|
|
} else {
|
|
$subjectLabel = $this->translate('Comment removed');
|
|
}
|
|
|
|
break;
|
|
case 'downtime_end':
|
|
if (! empty($item->downtime->cancelled_by)) {
|
|
if ($item->downtime->cancelled_by !== $item->downtime->author) {
|
|
$subjectLabel = sprintf(
|
|
$this->translate('Downtime cancelled by %s', '..<username>'),
|
|
$item->downtime->cancelled_by
|
|
);
|
|
} else {
|
|
$subjectLabel = $this->translate('Downtime cancelled by author');
|
|
}
|
|
} elseif ($item->downtime->has_been_cancelled === 'y') {
|
|
$subjectLabel = $this->translate('Downtime cancelled');
|
|
} else {
|
|
$subjectLabel = $this->translate('Downtime ended');
|
|
}
|
|
|
|
break;
|
|
case 'downtime_start':
|
|
$subjectLabel = $this->translate('Downtime started');
|
|
|
|
break;
|
|
case 'flapping_start':
|
|
$subjectLabel = $this->translate('Flapping started');
|
|
|
|
break;
|
|
case 'flapping_end':
|
|
$subjectLabel = $this->translate('Flapping stopped');
|
|
|
|
break;
|
|
case 'ack_set':
|
|
$subjectLabel = $this->translate('Acknowledgement set');
|
|
|
|
break;
|
|
case 'ack_clear':
|
|
if (! empty($item->acknowledgement->cleared_by)) {
|
|
if ($item->acknowledgement->cleared_by !== $item->acknowledgement->author) {
|
|
$subjectLabel = sprintf(
|
|
$this->translate('Acknowledgement cleared by %s', '..<username>'),
|
|
$item->acknowledgement->cleared_by
|
|
);
|
|
} else {
|
|
$subjectLabel = $this->translate('Acknowledgement cleared by author');
|
|
}
|
|
} elseif (isset($item->acknowledgement->expire_time)) {
|
|
$subjectLabel = $this->translate('Acknowledgement expired');
|
|
} else {
|
|
$subjectLabel = $this->translate('Acknowledgement cleared');
|
|
}
|
|
|
|
break;
|
|
case 'state_change':
|
|
$state = $item->state->state_type === 'hard'
|
|
? $item->state->hard_state
|
|
: $item->state->soft_state;
|
|
if ($state === 0) {
|
|
if ($item->object_type === 'service') {
|
|
$subjectLabel = $this->translate('Service recovered');
|
|
} else {
|
|
$subjectLabel = $this->translate('Host recovered');
|
|
}
|
|
} else {
|
|
if ($item->state->state_type === 'hard') {
|
|
$subjectLabel = $this->translate('Hard state changed');
|
|
} else {
|
|
$subjectLabel = $this->translate('Soft state changed');
|
|
}
|
|
}
|
|
|
|
break;
|
|
default:
|
|
$subjectLabel = $item->event_type;
|
|
|
|
break;
|
|
}
|
|
|
|
if ($layout === 'header') {
|
|
$title->addHtml(HtmlElement::create('span', ['class' => 'subject'], $subjectLabel));
|
|
} else {
|
|
$title->addHtml(new Link($subjectLabel, Links::event($item), ['class' => 'subject']));
|
|
}
|
|
|
|
if ($item->object_type === 'host' && isset($item->host->id)) {
|
|
$link = $this->createHostLink($item->host, true);
|
|
} elseif (isset($item->host->id, $item->service->id)) {
|
|
$link = $this->createServiceLink($item->service, $item->host, true);
|
|
}
|
|
|
|
$title->addHtml(Text::create(' '));
|
|
if (isset($link)) {
|
|
$title->addHtml($link);
|
|
}
|
|
}
|
|
|
|
public function assembleCaption($item, HtmlDocument $caption, string $layout): void
|
|
{
|
|
if ($item->event_type === 'notification') {
|
|
$item->notification->host = $item->host;
|
|
$item->notification->service = $item->service;
|
|
|
|
$this->notificationRenderer->assembleCaption($item->notification, $caption, $layout);
|
|
|
|
return;
|
|
}
|
|
|
|
switch ($item->event_type) {
|
|
case 'comment_add':
|
|
case 'comment_remove':
|
|
$markdownLine = new MarkdownLine($this->createTicketLinks($item->comment->comment));
|
|
$caption->getAttributes()->add($markdownLine->getAttributes());
|
|
$caption->add([
|
|
new Icon(Icons::USER),
|
|
$item->comment->author,
|
|
': '
|
|
])->addFrom($markdownLine);
|
|
|
|
break;
|
|
case 'downtime_end':
|
|
case 'downtime_start':
|
|
$markdownLine = new MarkdownLine($this->createTicketLinks($item->downtime->comment));
|
|
$caption->getAttributes()->add($markdownLine->getAttributes());
|
|
$caption->add([
|
|
new Icon(Icons::USER),
|
|
$item->downtime->author,
|
|
': '
|
|
])->addFrom($markdownLine);
|
|
|
|
break;
|
|
case 'flapping_start':
|
|
$caption
|
|
->add(
|
|
sprintf(
|
|
$this->translate('State Change Rate: %.2f%%; Start Threshold: %.2f%%'),
|
|
$item->flapping->percent_state_change_start,
|
|
$item->flapping->flapping_threshold_high
|
|
)
|
|
)
|
|
->getAttributes()
|
|
->add('class', 'plugin-output');
|
|
|
|
break;
|
|
case 'flapping_end':
|
|
$caption
|
|
->add(
|
|
sprintf(
|
|
$this->translate('State Change Rate: %.2f%%; End Threshold: %.2f%%; Flapping for %s'),
|
|
$item->flapping->percent_state_change_end,
|
|
$item->flapping->flapping_threshold_low,
|
|
isset($item->flapping->end_time)
|
|
? DateFormatter::formatDuration(
|
|
$item->flapping->end_time->getTimestamp()
|
|
- $item->flapping->start_time->getTimestamp()
|
|
)
|
|
: $this->translate('n. a.')
|
|
)
|
|
)
|
|
->getAttributes()
|
|
->add('class', 'plugin-output');
|
|
|
|
break;
|
|
case 'ack_clear':
|
|
case 'ack_set':
|
|
if (! isset($item->acknowledgement->comment) && ! isset($item->acknowledgement->author)) {
|
|
$caption->addHtml(
|
|
new EmptyState(
|
|
$this->translate('This acknowledgement was set before Icinga DB history recording')
|
|
)
|
|
);
|
|
} else {
|
|
$markdownLine = new MarkdownLine($this->createTicketLinks($item->acknowledgement->comment));
|
|
$caption->getAttributes()->add($markdownLine->getAttributes());
|
|
$caption->add([
|
|
new Icon(Icons::USER),
|
|
$item->acknowledgement->author,
|
|
': '
|
|
])->addFrom($markdownLine);
|
|
}
|
|
|
|
break;
|
|
case 'state_change':
|
|
$commandName = $item->object_type === 'host'
|
|
? $item->host->checkcommand_name
|
|
: $item->service->checkcommand_name;
|
|
if (isset($commandName)) {
|
|
if (empty($item->state->output)) {
|
|
$caption->addHtml(new EmptyState($this->translate('Output unavailable.')));
|
|
} else {
|
|
$caption->addHtml(
|
|
new PluginOutputContainer(
|
|
(new PluginOutput($item->state->output))
|
|
->setCommandName($commandName)
|
|
)
|
|
);
|
|
}
|
|
} else {
|
|
$caption->addHtml(
|
|
new EmptyState($this->translate('Waiting for Icinga DB to synchronize the config.'))
|
|
);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
public function assembleExtendedInfo($item, HtmlDocument $info, string $layout): void
|
|
{
|
|
$info->addHtml(new TimeAgo($item->event_time->getTimestamp()));
|
|
}
|
|
|
|
public function assembleFooter($item, HtmlDocument $footer, string $layout): void
|
|
{
|
|
}
|
|
|
|
public function assemble($item, string $name, HtmlDocument $element, string $layout): bool
|
|
{
|
|
return false; // no custom sections
|
|
}
|
|
}
|