Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions library/Notifications/Model/Behavior/IcingaCustomVars.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

/* Icinga Notifications Web | (c) 2025 Icinga GmbH | GPLv2 */

namespace Icinga\Module\Notifications\Model\Behavior;

use ipl\I18n\Translation;
use ipl\Orm\ColumnDefinition;
use ipl\Orm\Contract\RewriteColumnBehavior;
use ipl\Stdlib\Filter;

/** @internal Temporary implementation */
class IcingaCustomVars implements RewriteColumnBehavior
{
use Translation;

/** @var string */
public const HOST_PREFIX = 'host.vars.';

/** @var string */
public const SERVICE_PREFIX = 'service.vars.';

public function isSelectableColumn(string $name): bool
{
return str_starts_with($name, self::HOST_PREFIX)
|| str_starts_with($name, self::SERVICE_PREFIX);
}

public function rewriteColumn($column, ?string $relation = null)
{
return null;
}

public function rewriteColumnDefinition(ColumnDefinition $def, string $relation): void
{
if (str_starts_with($def->getName(), self::HOST_PREFIX)) {
$varName = substr($def->getName(), strlen(self::HOST_PREFIX));
} elseif (str_starts_with($def->getName(), self::SERVICE_PREFIX)) {
$varName = substr($def->getName(), strlen(self::SERVICE_PREFIX));
} else {
return;
}

if (str_ends_with($varName, '[*]')) {
$varName = substr($varName, 0, -3);
}

$def->setLabel(sprintf(
$this->translate(
ucfirst(substr($def->getName(), 0, strpos($def->getName(), '.'))) . ' %s',
),
$varName
));
}

public function rewriteCondition(Filter\Condition $condition, $relation = null)
{
if (! $this->isSelectableColumn($condition->metaData()->get('columnName', ''))) {
return null;
}

$class = get_class($condition);

return new $class(
$relation . 'object.extra_tag.' . substr($condition->getColumn(), strlen($relation)),
$condition->getValue()
);
}
}
2 changes: 2 additions & 0 deletions library/Notifications/Model/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use DateTime;
use Icinga\Module\Notifications\Common\Database;
use Icinga\Module\Notifications\Common\Icons;
use Icinga\Module\Notifications\Model\Behavior\IcingaCustomVars;
use ipl\Orm\Behavior\Binary;
use ipl\Orm\Behavior\BoolCast;
use ipl\Orm\Behavior\MillisecondTimestamp;
Expand Down Expand Up @@ -103,6 +104,7 @@ public function createBehaviors(Behaviors $behaviors): void
$behaviors->add(new MillisecondTimestamp(['time']));
$behaviors->add(new Binary(['object_id']));
$behaviors->add(new BoolCast(['mute']));
$behaviors->add(new IcingaCustomVars());
}

public function createRelations(Relations $relations): void
Expand Down
2 changes: 2 additions & 0 deletions library/Notifications/Model/Incident.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use DateTime;
use Icinga\Module\Notifications\Common\Database;
use Icinga\Module\Notifications\Model\Behavior\IcingaCustomVars;
use ipl\Orm\Behavior\Binary;
use ipl\Orm\Behavior\MillisecondTimestamp;
use ipl\Orm\Behaviors;
Expand Down Expand Up @@ -94,6 +95,7 @@ public function createBehaviors(Behaviors $behaviors): void
'started_at',
'recovered_at'
]));
$behaviors->add(new IcingaCustomVars());
}

public function createRelations(Relations $relations): void
Expand Down
53 changes: 49 additions & 4 deletions library/Notifications/Web/Control/SearchBar/ObjectSuggestions.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@

use Icinga\Module\Notifications\Common\Auth;
use Icinga\Module\Notifications\Common\Database;
use Icinga\Module\Notifications\Model\Behavior\IcingaCustomVars;
use Icinga\Module\Notifications\Model\ObjectExtraTag;
use Icinga\Module\Notifications\Model\ObjectIdTag;
use Icinga\Module\Notifications\Util\ObjectSuggestionsCursor;
use ipl\Html\HtmlElement;
use ipl\I18n\Translation;
use ipl\Orm\Exception\InvalidColumnException;
use ipl\Orm\Exception\InvalidRelationException;
use ipl\Orm\Model;
Expand All @@ -27,6 +29,7 @@
class ObjectSuggestions extends Suggestions
{
use Auth;
use Translation;

/** @var Model */
protected $model;
Expand Down Expand Up @@ -67,7 +70,12 @@ public function getModel(): Model

protected function shouldShowRelationFor(string $column): bool
{
if (strpos($column, '.tag.') !== false || strpos($column, '.extra_tag.') !== false) {
if (
str_contains($column, '.tag.')
|| str_contains($column, '.extra_tag.')
|| str_starts_with($column, IcingaCustomVars::HOST_PREFIX)
|| str_starts_with($column, IcingaCustomVars::SERVICE_PREFIX)
) {
return false;
}

Expand Down Expand Up @@ -128,6 +136,16 @@ protected function fetchValueSuggestions($column, $searchTerm, Filter\Chain $sea
} elseif (substr($targetPath, -10) === '.extra_tag') {
$isTag = true;
$targetPath = substr($targetPath, 0, -9) . 'object_extra_tag';
} elseif (
str_starts_with($column, IcingaCustomVars::HOST_PREFIX)
|| str_starts_with($column, IcingaCustomVars::SERVICE_PREFIX)
) {
$isTag = true;
$columnName = $column;
$targetPath = $query->getResolver()->qualifyPath(
'object.object_extra_tag',
$model->getTableName()
);
}

if (strpos($targetPath, '.') !== false) {
Expand Down Expand Up @@ -183,6 +201,8 @@ protected function fetchColumnSuggestions($searchTerm)
yield $columnName => $columnMeta;
}

$parsedArrayVars = [];

// Custom variables only after the columns are exhausted and there's actually a chance the user sees them
foreach ([new ObjectIdTag(), new ObjectExtraTag()] as $model) {
$titleAdded = false;
Expand All @@ -199,9 +219,34 @@ protected function fetchColumnSuggestions($searchTerm)
));
}

$relation = $isIdTag ? 'object.tag' : 'object.extra_tag';

yield $relation . '.' . $tag->tag => ucfirst($tag->tag);
if (
str_starts_with($tag->tag, IcingaCustomVars::HOST_PREFIX)
|| str_starts_with($tag->tag, IcingaCustomVars::SERVICE_PREFIX)
) {
$search = $name = $tag->tag;
if (preg_match('/\w+(?:\[(\d*)])+$/', $name, $matches)) {
$name = substr($name, 0, -(strlen($matches[1]) + 2));
if (isset($parsedArrayVars[$name])) {
continue;
}

$parsedArrayVars[$name] = true;
$search = $name . '[*]';
}

yield $search => sprintf($this->translate(
ucfirst(substr($name, 0, strpos($name, '.'))) . ' %s',
'..<customvar-name>'
), substr($name, strlen(
str_starts_with($name, IcingaCustomVars::HOST_PREFIX)
? IcingaCustomVars::HOST_PREFIX
: IcingaCustomVars::SERVICE_PREFIX
)));
} else {
$relation = $isIdTag ? 'object.tag' : 'object.extra_tag';

yield $relation . '.' . $tag->tag => ucfirst($tag->tag);
}
}
}
}
Expand Down
66 changes: 57 additions & 9 deletions library/Notifications/Widget/Detail/IncidentDetail.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

namespace Icinga\Module\Notifications\Widget\Detail;

use ArrayIterator;
use Icinga\Module\Icingadb\Model\CustomvarFlat;
use Icinga\Module\Icingadb\Widget\Detail\CustomVarTable;
use Icinga\Module\Notifications\Hook\ObjectsRendererHook;
use Icinga\Module\Notifications\Model\Behavior\IcingaCustomVars;
use Icinga\Module\Notifications\Model\Incident;
use Icinga\Module\Notifications\View\IncidentContactRenderer;
use Icinga\Module\Notifications\View\IncidentHistoryRenderer;
Expand All @@ -16,11 +20,14 @@
use ipl\Html\HtmlElement;
use ipl\Html\Table;
use ipl\Html\Text;
use ipl\I18n\Translation;
use ipl\Stdlib\Filter;
use ipl\Web\Layout\MinimalItemLayout;

class IncidentDetail extends BaseHtmlElement
{
use Translation;

/** @var Incident */
protected $incident;

Expand Down Expand Up @@ -106,20 +113,61 @@ protected function createSource()
protected function createObjectTag(): array
{
$tags = [];
$hostCustomvars = [];
$serviceCustomvars = [];
foreach ($this->incident->object->object_extra_tag as $extraTag) {
$tags[] = Table::row([$extraTag->tag, $extraTag->value]);
if (str_starts_with($extraTag->tag, IcingaCustomVars::HOST_PREFIX)) {
$name = substr($extraTag->tag, strlen(IcingaCustomVars::HOST_PREFIX));
$name = preg_replace('/(\[\d*])/', '.\1', $name);

$hostCustomvars[] = (object) [
'flatname' => $name,
'flatvalue' => $extraTag->value
];
} elseif (str_starts_with($extraTag->tag, IcingaCustomVars::SERVICE_PREFIX)) {
$name = substr($extraTag->tag, strlen(IcingaCustomVars::SERVICE_PREFIX));
$name = preg_replace('/(\[\d*])/', '.\1', $name);

$serviceCustomvars[] = (object) [
'flatname' => $name,
'flatvalue' => $extraTag->value
];
} else {
$tags[] = Table::row([$extraTag->tag, $extraTag->value]);
}
}

if (! $tags) {
return $tags;
$result = [];

if (! empty($tags) || ! empty($hostCustomvars) || ! empty($serviceCustomvars)) {
$result[] = new HtmlElement('h2', null, new Text(t('Object Tags')));
}

return [
new HtmlElement('h2', null, new Text(t('Object Tags'))),
(new Table())
->addHtml(...$tags)
->addAttributes(['class' => 'object-tags-table'])
];
if (! empty($tags)) {
$result[] = (new Table())->addHtml(...$tags)->addAttributes(['class' => 'object-tags-table']);
}

// TODO: Drop the following custom variable handling once the final source integration is ready

if (! empty($hostCustomvars)) {
$result[] = new HtmlElement('h3', null, new Text('Host Custom Variables'));
$result[] = new HtmlElement(
'div',
Attributes::create(['class' => ['icinga-module', 'module-icingadb']]),
new CustomVarTable((new CustomvarFlat())->unFlattenVars(new ArrayIterator($hostCustomvars)))
);
}

if (! empty($serviceCustomvars)) {
$result[] = new HtmlElement('h3', null, new Text('Service Custom Variables'));
$result[] = new HtmlElement(
'div',
Attributes::create(['class' => ['icinga-module', 'module-icingadb']]),
new CustomVarTable((new CustomvarFlat())->unFlattenVars(new ArrayIterator($serviceCustomvars)))
);
}

return $result;
}

protected function assemble()
Expand Down
Loading