mirror of
https://github.com/Icinga/icingaweb2-module-director.git
synced 2026-06-04 22:32:55 -04:00
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
135 lines
3.7 KiB
PHP
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;
|
|
}
|
|
}
|