diff --git a/library/Notifications/Model/Behavior/IcingaCustomVars.php b/library/Notifications/Model/Behavior/IcingaCustomVars.php new file mode 100644 index 000000000..42c1d8e4e --- /dev/null +++ b/library/Notifications/Model/Behavior/IcingaCustomVars.php @@ -0,0 +1,69 @@ +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() + ); + } +} diff --git a/library/Notifications/Model/Event.php b/library/Notifications/Model/Event.php index 6aea53bc8..368568a51 100644 --- a/library/Notifications/Model/Event.php +++ b/library/Notifications/Model/Event.php @@ -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; @@ -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 diff --git a/library/Notifications/Model/Incident.php b/library/Notifications/Model/Incident.php index 4f86e1d7b..22b697234 100644 --- a/library/Notifications/Model/Incident.php +++ b/library/Notifications/Model/Incident.php @@ -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; @@ -94,6 +95,7 @@ public function createBehaviors(Behaviors $behaviors): void 'started_at', 'recovered_at' ])); + $behaviors->add(new IcingaCustomVars()); } public function createRelations(Relations $relations): void diff --git a/library/Notifications/Web/Control/SearchBar/ObjectSuggestions.php b/library/Notifications/Web/Control/SearchBar/ObjectSuggestions.php index 2a4d674ff..f87f04ba5 100644 --- a/library/Notifications/Web/Control/SearchBar/ObjectSuggestions.php +++ b/library/Notifications/Web/Control/SearchBar/ObjectSuggestions.php @@ -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; @@ -27,6 +29,7 @@ class ObjectSuggestions extends Suggestions { use Auth; + use Translation; /** @var Model */ protected $model; @@ -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; } @@ -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) { @@ -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; @@ -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', + '..' + ), 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); + } } } } diff --git a/library/Notifications/Widget/Detail/IncidentDetail.php b/library/Notifications/Widget/Detail/IncidentDetail.php index 6742ac337..57814a945 100644 --- a/library/Notifications/Widget/Detail/IncidentDetail.php +++ b/library/Notifications/Widget/Detail/IncidentDetail.php @@ -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; @@ -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; @@ -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()