icingaweb2-module-director/application/forms/DictionaryElements/NestedDictionaryItem.php
Ravi Srinivasa dc4b9cb80f
feat(ui): Add forms, controllers, and REST API handler for custom variable management
Introduce the full UI layer for creating, editing, and assigning custom
variables (DirectorProperties) to Icinga objects:

- CustomvarController: CRUD for global custom variable definitions
- VariablesController: per-object variable assignment
- HostController: host-specific dictionary member management
- SuggestionsController: datalist suggestions with PostgreSQL support
- CustomVariableForm / CustomVariablesForm / DeleteCustomVariableForm:
  forms for managing variables on objects with multipart update support
- DictionaryElements (Dictionary, DictionaryItem, NestedDictionary,
  NestedDictionaryItem): composable form elements for structured types
- ArrayElement, IplBoolean: new reusable form elements
- DatalistEntryValidator: validates datalist-constrained variable values
- ObjectController: fetchNestedDictionaryKeys, multipart reload handling
- IcingaObjectHandler (REST API): expose and accept structured custom
  variables in PUT/POST requests; support PostgreSQL UUID binaries
- ObjectTabs: add Variables tab to all object types
- CSS / JS: styles for item lists, action lists, custom variable forms,
  and host-service deactivation; JS fix for multipart form reloads
- configuration.php: register new routes and the Custom Variables dashlet
2026-06-01 12:32:15 +02:00

137 lines
3.8 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
]);
$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']))
->setIsChild()
);
}
/**
* 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;
}
}