icingaweb2-module-director/application/forms/DictionaryElements/NestedDictionaryItem.php
Ravi Srinivasa 6198cd8d9b
Alternative custom property support with dictionary handling
Introduce a first-class DirectorProperty concept that extends the existing
data-fields model with rich, structured variable types. Custom variables can
now be defined globally under a new "Custom Variables" section and assigned
to Icinga objects (hosts, services, commands, etc.).

Supported types: string, number, boolean, fixed-array, dynamic-array,
datalist-strict, datalist-non-strict, fixed-dictionary, and
dynamic-dictionary (one level of nesting allowed).

Key changes:
  - DirectorProperty object with CRUD, inheritance, and apply-for rule support
  - New form elements: Dictionary, DictionaryItem, NestedDictionary,
    NestedDictionaryItem, ArrayElement, IplBoolean
  - CustomvarController for managing global property definitions
  - VariablesController for per-object variable assignment
  - BasketSnapshotCustomVariableResolver for basket import/export of properties
  - REST API: IcingaObjectHandler extended to expose and accept structured vars
  - IcingaConfigHelper: renders dictionaries/arrays to valid Icinga 2 DSL
  - CustomVarRenderer updated for icingadb hook display
  - DB migration upgrade_192.sql: new director_property and related tables
  - CLI: MigrateCommand for migrating legacy vars to properties;
    HostsCommand for bulk host custom variable management
2026-05-15 13:41:00 +02:00

135 lines
3.7 KiB
PHP

<?php
namespace Icinga\Module\Director\Forms\DictionaryElements;
use ipl\Html\Contract\FormElement;
use ipl\Html\FormElement\FieldsetElement;
use ipl\Html\FormElement\SubmitButtonElement;
use ipl\Html\HtmlElement;
use ipl\Html\Text;
use ipl\Web\Widget\Icon;
/**
* @phpstan-import-type DictionaryDataType from Dictionary
* @phpstan-type NestedDictionaryItemDataType array{
* key: string,
* value: DictionaryDataType
* }
*/
class NestedDictionaryItem extends FieldsetElement
{
protected $defaultAttributes = ['class' => ['nested-dictionary-item', 'collapsible']];
/** @var array Items in the nested dictionary property */
protected array $items = [];
/** @var ?SubmitButtonElement Remove button for the nested dictionary property*/
private ?SubmitButtonElement $removeButton = null;
public function __construct(string $name, array $items, $attributes = null)
{
$this->items = $items;
$this->getAttributes()->add([
'data-toggle-element' => 'legend',
'data-visible-height' => 0
]);
parent::__construct($name, $attributes);
}
protected function assemble(): void
{
$this->addElement('text', 'key', [
'label' => $this->translate('Key'),
'required' => true,
'class' => 'autosubmit'
]);
$id = $this->getPopulatedValue('id');
if ($id === null) {
$id = uniqid('id-');
}
$this->addElement('hidden', 'id', ['value' => $id]);
$this->getAttributes()->set('id', $id);
$label = $this->getElement('key')->getValue();
if ($label === null) {
$label = $this->translate('New Item');
}
$this->setLabel($label);
if ($this->removeButton !== null) {
$this->addHtml(new HtmlElement(
'div',
null,
$this->removeButton->setLabel(new Icon('trash'))
->setAttribute('formnovalidate', true)
->setAttribute('class', ['remove-button'])
->add(Text::create(' ' . $this->translate('Remove')))
));
}
$this->addElement(new Dictionary('var', $this->items, ['class' => 'no-border']));
}
/**
* Set the remove button.
*
* @param ?FormElement $removeButton
*
* @return $this
*/
public function setRemoveButton(?FormElement $removeButton): static
{
$this->removeButton = $removeButton;
return $this;
}
/**
* Prepare the nested dictionary item value for display
*
* @param array $nestedItems
* @param array $property
*
* @return array
*/
public static function prepare(array $nestedItems, array $property): array
{
$nestedValues = [];
foreach ($nestedItems as $nestedItem) {
if (isset($property[$nestedItem['key_name']]) && ! empty($property[$nestedItem['key_name']])) {
$nestedItem['value'] = $property[$nestedItem['key_name']];
}
$nestedValues[] = $nestedItem;
}
if (isset($property['key']) && str_starts_with($property['key'], NestedDictionary::UNDEFINED_KEY)) {
$property['key'] = null;
}
return [
'key' => $property['key'],
'var' => Dictionary::prepare($nestedValues)
];
}
/**
* Get the nested dictionary item value
*
* @return NestedDictionaryItemDataType
*/
public function getItem(): array
{
$this->ensureAssembled();
$key = $this->getElement('key')->getValue();
$values = [];
$values['key'] = $key;
$values['value'] = $this->getElement('var')->getDictionary();
return $values;
}
}