Skip to content

Commit 43dfc97

Browse files
committed
Support host.vars.* and service.vars.* filters
1 parent a637615 commit 43dfc97

File tree

5 files changed

+176
-13
lines changed

5 files changed

+176
-13
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
/* Icinga Notifications Web | (c) 2025 Icinga GmbH | GPLv2 */
4+
5+
namespace Icinga\Module\Notifications\Model\Behavior;
6+
7+
use ipl\I18n\Translation;
8+
use ipl\Orm\ColumnDefinition;
9+
use ipl\Orm\Contract\RewriteColumnBehavior;
10+
use ipl\Stdlib\Filter;
11+
12+
/** @internal Temporary implementation */
13+
class IcingaCustomVars implements RewriteColumnBehavior
14+
{
15+
use Translation;
16+
17+
/** @var string */
18+
public const HOST_PREFIX = 'host.vars.';
19+
20+
/** @var string */
21+
public const SERVICE_PREFIX = 'service.vars.';
22+
23+
public function isSelectableColumn(string $name): bool
24+
{
25+
return str_starts_with($name, self::HOST_PREFIX)
26+
|| str_starts_with($name, self::SERVICE_PREFIX);
27+
}
28+
29+
public function rewriteColumn($column, ?string $relation = null)
30+
{
31+
return null;
32+
}
33+
34+
public function rewriteColumnDefinition(ColumnDefinition $def, string $relation): void
35+
{
36+
if (str_starts_with($def->getName(), self::HOST_PREFIX)) {
37+
$varName = substr($def->getName(), strlen(self::HOST_PREFIX));
38+
} elseif (str_starts_with($def->getName(), self::SERVICE_PREFIX)) {
39+
$varName = substr($def->getName(), strlen(self::SERVICE_PREFIX));
40+
} else {
41+
return;
42+
}
43+
44+
if (str_ends_with($varName, '[*]')) {
45+
$varName = substr($varName, 0, -3);
46+
}
47+
48+
$def->setLabel(sprintf(
49+
$this->translate(
50+
ucfirst(substr($def->getName(), 0, strpos($def->getName(), '.'))) . ' %s',
51+
),
52+
$varName
53+
));
54+
}
55+
56+
public function rewriteCondition(Filter\Condition $condition, $relation = null)
57+
{
58+
if (! $this->isSelectableColumn($condition->metaData()->get('columnName', ''))) {
59+
return null;
60+
}
61+
62+
$class = get_class($condition);
63+
64+
return new $class(
65+
$relation . 'object.extra_tag.' . substr($condition->getColumn(), strlen($relation)),
66+
$condition->getValue()
67+
);
68+
}
69+
}

library/Notifications/Model/Event.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use DateTime;
88
use Icinga\Module\Notifications\Common\Database;
99
use Icinga\Module\Notifications\Common\Icons;
10+
use Icinga\Module\Notifications\Model\Behavior\IcingaCustomVars;
1011
use ipl\Orm\Behavior\Binary;
1112
use ipl\Orm\Behavior\BoolCast;
1213
use ipl\Orm\Behavior\MillisecondTimestamp;
@@ -103,6 +104,7 @@ public function createBehaviors(Behaviors $behaviors): void
103104
$behaviors->add(new MillisecondTimestamp(['time']));
104105
$behaviors->add(new Binary(['object_id']));
105106
$behaviors->add(new BoolCast(['mute']));
107+
$behaviors->add(new IcingaCustomVars());
106108
}
107109

108110
public function createRelations(Relations $relations): void

library/Notifications/Model/Incident.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use DateTime;
88
use Icinga\Module\Notifications\Common\Database;
9+
use Icinga\Module\Notifications\Model\Behavior\IcingaCustomVars;
910
use ipl\Orm\Behavior\Binary;
1011
use ipl\Orm\Behavior\MillisecondTimestamp;
1112
use ipl\Orm\Behaviors;
@@ -94,6 +95,7 @@ public function createBehaviors(Behaviors $behaviors): void
9495
'started_at',
9596
'recovered_at'
9697
]));
98+
$behaviors->add(new IcingaCustomVars());
9799
}
98100

99101
public function createRelations(Relations $relations): void

library/Notifications/Web/Control/SearchBar/ObjectSuggestions.php

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66

77
use Icinga\Module\Notifications\Common\Auth;
88
use Icinga\Module\Notifications\Common\Database;
9+
use Icinga\Module\Notifications\Model\Behavior\IcingaCustomVars;
910
use Icinga\Module\Notifications\Model\ObjectExtraTag;
1011
use Icinga\Module\Notifications\Model\ObjectIdTag;
1112
use Icinga\Module\Notifications\Util\ObjectSuggestionsCursor;
1213
use ipl\Html\HtmlElement;
14+
use ipl\I18n\Translation;
1315
use ipl\Orm\Exception\InvalidColumnException;
1416
use ipl\Orm\Exception\InvalidRelationException;
1517
use ipl\Orm\Model;
@@ -27,6 +29,7 @@
2729
class ObjectSuggestions extends Suggestions
2830
{
2931
use Auth;
32+
use Translation;
3033

3134
/** @var Model */
3235
protected $model;
@@ -67,7 +70,12 @@ public function getModel(): Model
6770

6871
protected function shouldShowRelationFor(string $column): bool
6972
{
70-
if (strpos($column, '.tag.') !== false || strpos($column, '.extra_tag.') !== false) {
73+
if (
74+
str_contains($column, '.tag.')
75+
|| str_contains($column, '.extra_tag.')
76+
|| str_starts_with($column, IcingaCustomVars::HOST_PREFIX)
77+
|| str_starts_with($column, IcingaCustomVars::SERVICE_PREFIX)
78+
) {
7179
return false;
7280
}
7381

@@ -128,6 +136,16 @@ protected function fetchValueSuggestions($column, $searchTerm, Filter\Chain $sea
128136
} elseif (substr($targetPath, -10) === '.extra_tag') {
129137
$isTag = true;
130138
$targetPath = substr($targetPath, 0, -9) . 'object_extra_tag';
139+
} elseif (
140+
str_starts_with($column, IcingaCustomVars::HOST_PREFIX)
141+
|| str_starts_with($column, IcingaCustomVars::SERVICE_PREFIX)
142+
) {
143+
$isTag = true;
144+
$columnName = $column;
145+
$targetPath = $query->getResolver()->qualifyPath(
146+
'object.object_extra_tag',
147+
$model->getTableName()
148+
);
131149
}
132150

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

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

202-
$relation = $isIdTag ? 'object.tag' : 'object.extra_tag';
203-
204-
yield $relation . '.' . $tag->tag => ucfirst($tag->tag);
222+
if (
223+
str_starts_with($tag->tag, IcingaCustomVars::HOST_PREFIX)
224+
|| str_starts_with($tag->tag, IcingaCustomVars::SERVICE_PREFIX)
225+
) {
226+
$search = $name = $tag->tag;
227+
if (preg_match('/\w+(?:\[(\d*)])+$/', $name, $matches)) {
228+
$name = substr($name, 0, -(strlen($matches[1]) + 2));
229+
if (isset($parsedArrayVars[$name])) {
230+
continue;
231+
}
232+
233+
$parsedArrayVars[$name] = true;
234+
$search = $name . '[*]';
235+
}
236+
237+
yield $search => sprintf($this->translate(
238+
ucfirst(substr($name, 0, strpos($name, '.'))) . ' %s',
239+
'..<customvar-name>'
240+
), substr($name, strlen(
241+
str_starts_with($name, IcingaCustomVars::HOST_PREFIX)
242+
? IcingaCustomVars::HOST_PREFIX
243+
: IcingaCustomVars::SERVICE_PREFIX
244+
)));
245+
} else {
246+
$relation = $isIdTag ? 'object.tag' : 'object.extra_tag';
247+
248+
yield $relation . '.' . $tag->tag => ucfirst($tag->tag);
249+
}
205250
}
206251
}
207252
}

library/Notifications/Widget/Detail/IncidentDetail.php

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44

55
namespace Icinga\Module\Notifications\Widget\Detail;
66

7+
use ArrayIterator;
8+
use Icinga\Module\Icingadb\Model\CustomvarFlat;
9+
use Icinga\Module\Icingadb\Widget\Detail\CustomVarTable;
710
use Icinga\Module\Notifications\Hook\ObjectsRendererHook;
11+
use Icinga\Module\Notifications\Model\Behavior\IcingaCustomVars;
812
use Icinga\Module\Notifications\Model\Incident;
913
use Icinga\Module\Notifications\View\IncidentContactRenderer;
1014
use Icinga\Module\Notifications\View\IncidentHistoryRenderer;
@@ -16,11 +20,14 @@
1620
use ipl\Html\HtmlElement;
1721
use ipl\Html\Table;
1822
use ipl\Html\Text;
23+
use ipl\I18n\Translation;
1924
use ipl\Stdlib\Filter;
2025
use ipl\Web\Layout\MinimalItemLayout;
2126

2227
class IncidentDetail extends BaseHtmlElement
2328
{
29+
use Translation;
30+
2431
/** @var Incident */
2532
protected $incident;
2633

@@ -106,20 +113,58 @@ protected function createSource()
106113
protected function createObjectTag(): array
107114
{
108115
$tags = [];
116+
$hostCustomvars = [];
117+
$serviceCustomvars = [];
109118
foreach ($this->incident->object->object_extra_tag as $extraTag) {
110-
$tags[] = Table::row([$extraTag->tag, $extraTag->value]);
119+
if (str_starts_with($extraTag->tag, IcingaCustomVars::HOST_PREFIX)) {
120+
$name = substr($extraTag->tag, strlen(IcingaCustomVars::HOST_PREFIX));
121+
$name = preg_replace('/(\[\d*])/', '.\1', $name);
122+
123+
$hostCustomvars[] = (object) [
124+
'flatname' => $name,
125+
'flatvalue' => $extraTag->value
126+
];
127+
} elseif (str_starts_with($extraTag->tag, IcingaCustomVars::SERVICE_PREFIX)) {
128+
$name = substr($extraTag->tag, strlen(IcingaCustomVars::SERVICE_PREFIX));
129+
$name = preg_replace('/(\[\d*])/', '.\1', $name);
130+
131+
$serviceCustomvars[] = (object) [
132+
'flatname' => $name,
133+
'flatvalue' => $extraTag->value
134+
];
135+
} else {
136+
$tags[] = Table::row([$extraTag->tag, $extraTag->value]);
137+
}
111138
}
112139

113-
if (! $tags) {
114-
return $tags;
140+
$result = [];
141+
142+
if ($tags) {
143+
$result[] = new HtmlElement('h2', null, new Text(t('Object Tags')));
144+
$result[] = (new Table())->addHtml(...$tags)->addAttributes(['class' => 'object-tags-table']);
115145
}
116146

117-
return [
118-
new HtmlElement('h2', null, new Text(t('Object Tags'))),
119-
(new Table())
120-
->addHtml(...$tags)
121-
->addAttributes(['class' => 'object-tags-table'])
122-
];
147+
// TODO: Drop the following custom variable handling once the final source integration is ready
148+
149+
if (! empty($hostCustomvars)) {
150+
$result[] = new HtmlElement('h2', null, new Text('Host Custom Variables'));
151+
$result[] = new HtmlElement(
152+
'div',
153+
Attributes::create(['class' => ['icinga-module', 'module-icingadb']]),
154+
new CustomVarTable((new CustomvarFlat())->unFlattenVars(new ArrayIterator($hostCustomvars)))
155+
);
156+
}
157+
158+
if (! empty($serviceCustomvars)) {
159+
$result[] = new HtmlElement('h2', null, new Text('Service Custom Variables'));
160+
$result[] = new HtmlElement(
161+
'div',
162+
Attributes::create(['class' => ['icinga-module', 'module-icingadb']]),
163+
new CustomVarTable((new CustomvarFlat())->unFlattenVars(new ArrayIterator($serviceCustomvars)))
164+
);
165+
}
166+
167+
return $result;
123168
}
124169

125170
protected function assemble()

0 commit comments

Comments
 (0)