Merge branch 'feature/data-field-categories-1969'

fixes #1969
This commit is contained in:
Thomas Gelf 2019-10-02 09:59:35 +02:00
commit ddcc8953b8
16 changed files with 445 additions and 16 deletions

View File

@ -7,6 +7,7 @@ use Icinga\Module\Director\Forms\DirectorDatalistForm;
use Icinga\Module\Director\Objects\DirectorDatalist;
use Icinga\Module\Director\Web\Controller\ActionController;
use Icinga\Module\Director\Web\Table\CustomvarTable;
use Icinga\Module\Director\Web\Table\DatafieldCategoryTable;
use Icinga\Module\Director\Web\Table\DatafieldTable;
use Icinga\Module\Director\Web\Table\DatalistEntryTable;
use Icinga\Module\Director\Web\Table\DatalistTable;
@ -74,6 +75,24 @@ class DataController extends ActionController
(new DatafieldTable($this->db()))->renderTo($this);
}
public function fieldcategoriesAction()
{
$this->setAutorefreshInterval(10);
$this->tabs(new DataTabs())->activate('datafieldcategory');
$this->addTitle($this->translate('Data Field Categories'));
$this->actions()->add(Link::create(
$this->translate('Add'),
'director/datafieldcategory/add',
null,
[
'class' => 'icon-plus',
'data-base-target' => '_next',
]
));
(new DatafieldCategoryTable($this->db()))->renderTo($this);
}
public function varsAction()
{
$this->tabs(new DataTabs())->activate('customvars');

View File

@ -0,0 +1,46 @@
<?php
namespace Icinga\Module\Director\Controllers;
use Icinga\Module\Director\Forms\DirectorDatafieldCategoryForm;
use Icinga\Module\Director\Web\Controller\ActionController;
class DatafieldcategoryController extends ActionController
{
public function addAction()
{
$this->indexAction();
}
public function editAction()
{
$this->indexAction();
}
public function indexAction()
{
$edit = false;
if ($name = $this->params->get('name')) {
$edit = true;
}
$form = DirectorDatafieldCategoryForm::load()
->setDb($this->db());
if ($edit) {
$form->loadObject($name);
$this->addTitle(
$this->translate('Modify %s'),
$form->getObject()->category_name
);
$this->addSingleTab($this->translate('Edit a Category'));
} else {
$this->addTitle($this->translate('Add a new Data Field Category'));
$this->addSingleTab($this->translate('New Category'));
}
$form->handleRequest();
$this->content()->add($form);
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace Icinga\Module\Director\Forms;
use Icinga\Module\Director\Web\Form\DirectorObjectForm;
class DirectorDatafieldCategoryForm extends DirectorObjectForm
{
protected $objectName = 'Data field category';
protected $listUrl = 'director/data/fieldcategories';
public function setup()
{
$this->addHtmlHint(
$this->translate(
'Data field categories allow to structure Data Fields. Fields with'
. ' a category will be shown grouped by category.'
)
);
$this->addElement('text', 'category_name', [
'label' => $this->translate('Category name'),
'description' => $this->translate(
'The unique name of the category used for grouping your custom Data Fields.'
),
'required' => true,
]);
$this->addElement('text', 'description', [
'label' => $this->translate('Description'),
]);
$this->setButtons();
}
}

View File

@ -164,6 +164,11 @@ class DirectorDatafieldForm extends DirectorObjectForm
'rows' => '3',
));
$this->addElement('select', 'category_id', [
'label' => $this->translate('Data Field Category'),
'multiOptions' => $this->optionalEnum($this->enumCategpories()),
]);
$error = false;
try {
$types = $this->enumDataTypes();
@ -285,4 +290,12 @@ class DirectorDatafieldForm extends DirectorObjectForm
return $enum;
}
protected function enumCategpories()
{
$db = $this->getDb()->getDbAdapter();
return $db->fetchPairs(
$db->select()->from('director_datafield_category', ['id', 'category_name'])
);
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Icinga\Module\Director\Dashboard\Dashlet;
class DatafieldCategoryDashlet extends Dashlet
{
protected $icon = 'th-list';
public function getTitle()
{
return $this->translate('Data Field Categories');
}
public function getSummary()
{
return $this->translate(
'Categories bring structure to your Data Fields'
);
}
public function getUrl()
{
return 'director/data/fieldcategories';
}
public function listRequiredPermissions()
{
return array('director/admin');
}
}

View File

@ -4,11 +4,12 @@ namespace Icinga\Module\Director\Dashboard;
class DataDashboard extends Dashboard
{
protected $dashletNames = array(
protected $dashletNames = [
'Datafield',
'DatafieldCategory',
'Datalist',
'Customvar'
);
];
public function getTitle()
{

View File

@ -11,6 +11,9 @@ class FieldSpec
/** @var string */
protected $varName;
/** @var string */
protected $category;
/** @var string */
protected $caption;
@ -46,13 +49,14 @@ class FieldSpec
{
return DirectorDatafield::create([
'varname' => $this->getVarName(),
'category' => $this->getCategory(),
'caption' => $this->getCaption(),
'description' => $this->getDescription(),
'datatype' => $this->getDataType(),
'format' => $this->getFormat(),
'var_filter' => $this->getVarFilter(),
'icinga_type' => $object->getShortTableName(),
'object_id' => $object->get('id')
'object_id' => $object->get('id'),
]);
}
@ -181,4 +185,22 @@ class FieldSpec
$this->format = $format;
return $this;
}
/**
* @return string
*/
public function getCategory()
{
return $this->category;
}
/**
* @param string $category
* @return FieldSpec
*/
public function setCategory($category)
{
$this->category = $category;
return $this;
}
}

View File

@ -19,19 +19,27 @@ class DirectorDatafield extends DbObjectWithSettings
protected $autoincKeyName = 'id';
protected $defaultProperties = array(
protected $defaultProperties = [
'id' => null,
'category_id' => null,
'varname' => null,
'caption' => null,
'description' => null,
'datatype' => null,
'format' => null,
);
];
protected $relations = [
'category' => 'DirectorDatafieldCategory'
];
protected $settingsTable = 'director_datafield_setting';
protected $settingsRemoteId = 'datafield_id';
/** @var DirectorDatafieldCategory|null */
private $category;
private $object;
public static function fromDbRow($row, Db $connection)
@ -53,6 +61,53 @@ class DirectorDatafield extends DbObjectWithSettings
return $obj;
}
public function hasCategory()
{
return $this->category !== null || $this->get('category_id') !== null;
}
/**
* @return DirectorDatafieldCategory|null
* @throws \Icinga\Exception\NotFoundError
*/
public function getCategory()
{
if ($this->category) {
return $this->category;
} elseif ($id = $this->get('category_id')) {
return DirectorDatafieldCategory::loadWithAutoIncId($id, $this->getConnection());
} else {
return null;
}
}
public function getCategoryName()
{
$category = $this->getCategory();
if ($this->category === null) {
return null;
} else {
return $category->get('category_name');
}
}
public function setCategory($category)
{
if ($category === null) {
$this->category = null;
$this->set('category_id', null);
} elseif ($category instanceof DirectorDatafieldCategory) {
if ($category->hasBeenLoadedFromDb()) {
$this->set('category_id', $category->get('id'));
}
$this->category = $category;
} else {
$this->setCategory(DirectorDatafieldCategory::load($category, $this->getConnection()));
}
return $this;
}
/**
* @return object
* @throws \Icinga\Exception\NotFoundError

View File

@ -0,0 +1,20 @@
<?php
namespace Icinga\Module\Director\Objects;
use Icinga\Module\Director\Data\Db\DbObject;
class DirectorDatafieldCategory extends DbObject
{
protected $table = 'director_datafield_category';
protected $keyName = 'category_name';
protected $autoincKeyName = 'id';
protected $defaultProperties = [
'id' => null,
'category_name' => null,
'description' => null,
];
}

View File

@ -9,6 +9,7 @@ use Icinga\Data\Filter\FilterExpression;
use Icinga\Exception\IcingaException;
use Icinga\Module\Director\Hook\HostFieldHook;
use Icinga\Module\Director\Hook\ServiceFieldHook;
use Icinga\Module\Director\Objects\DirectorDatafieldCategory;
use Icinga\Module\Director\Objects\IcingaCommand;
use Icinga\Module\Director\Objects\IcingaHost;
use Icinga\Module\Director\Objects\IcingaObject;
@ -33,6 +34,7 @@ class IcingaObjectFieldLoader
/** @var \Zend_Db_Adapter_Abstract */
protected $db;
/** @var DirectorDatafield[] */
protected $fields;
protected $elements;
@ -219,13 +221,51 @@ class IcingaObjectFieldLoader
$form->addElement($element);
}
if (! empty($elements)) {
$form->addElementsToGroup(
$elements,
'custom_fields',
50,
$form->translate('Custom properties')
);
$this->attachGroupElements($elements, $form);
}
/**
* @param ZfElement[] $elements
* @param DirectorObjectForm $form
*/
protected function attachGroupElements(array $elements, DirectorObjectForm $form)
{
$categories = [];
$categoriesFetchedById = [];
foreach ($this->fields as $key => $field) {
if ($id = $field->get('category_id')) {
if (isset($categoriesFetchedById[$id])) {
$category = $categoriesFetchedById[$id];
} else {
$category = DirectorDatafieldCategory::loadWithAutoIncId($id, $form->getDb());
$categoriesFetchedById[$id] = $category;
}
} elseif ($field->hasCategory()) {
$category = $field->getCategory();
} else {
continue;
}
$categories[$key] = $category;
}
$prioIdx = \array_flip(\array_keys($categories));
foreach ($elements as $key => $element) {
if (isset($categories[$key])) {
$category = $categories[$key];
$form->addElementsToGroup(
[$element],
'custom_fields:' . $category->get('category_name'),
51 + $prioIdx[$key],
$category->get('category_name')
);
} else {
$form->addElementsToGroup(
[$element],
'custom_fields',
50,
$form->translate('Custom properties')
);
}
}
}
@ -491,6 +531,7 @@ class IcingaObjectFieldLoader
'var_filter' => 'f.var_filter',
'is_required' => 'f.is_required',
'id' => 'df.id',
'category_id' => 'df.category_id',
'varname' => 'df.varname',
'caption' => 'df.caption',
'description' => 'df.description',

View File

@ -0,0 +1,66 @@
<?php
namespace Icinga\Module\Director\Web\Table;
use gipfl\IcingaWeb2\Link;
use gipfl\IcingaWeb2\Table\ZfQueryBasedTable;
use ipl\Html\Html;
use Zend_Db_Adapter_Abstract as ZfDbAdapter;
use Zend_Db_Select as ZfDbSelect;
class DatafieldCategoryTable extends ZfQueryBasedTable
{
protected $searchColumns = array(
'dfc.varname',
'dfc.caption',
);
public function getColumns()
{
return array(
'id' => 'dfc.id',
'category_name' => 'dfc.category_name',
'description' => 'dfc.description',
'assigned_fields' => 'COUNT(df.id)',
);
}
public function renderRow($row)
{
$main = [Link::create(
$row->category_name,
'director/datafieldcategory/edit',
['name' => $row->category_name]
)];
if (strlen($row->description)) {
$main[] = Html::tag('br');
$main[] = Html::tag('small', $row->description);
}
return $this::tr([
$this::td($main),
$this::td($row->assigned_fields)
]);
}
public function getColumnsToBeRendered()
{
return array(
$this->translate('Category Name'),
$this->translate('# Used'),
);
}
public function prepareQuery()
{
$db = $this->db();
return $db->select()->from(
['dfc' => 'director_datafield_category'],
$this->getColumns()
)->joinLeft(
['df' => 'director_datafield'],
'df.category_id = dfc.id',
[]
)->group('dfc.id')->group('dfc.category_name')->order('category_name ASC');
}
}

View File

@ -20,6 +20,9 @@ class DataTabs extends Tabs
$this->add('datafield', [
'label' => $this->translate('Data fields'),
'url' => 'director/data/fields'
])->add('datafieldcategory', [
'label' => $this->translate('Data field categories'),
'url' => 'director/data/fieldcategories'
])->add('datalist', [
'label' => $this->translate('Data lists'),
'url' => 'director/data/lists'

View File

@ -0,0 +1,21 @@
CREATE TABLE director_datafield_category (
id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
category_name VARCHAR(255) NOT NULL,
description TEXT DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY category_name (category_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE director_datafield
ADD COLUMN category_id INT(10) UNSIGNED DEFAULT NULL AFTER id,
ADD CONSTRAINT director_datafield_category
FOREIGN KEY category (category_id)
REFERENCES director_datafield_category (id)
ON DELETE RESTRICT
ON UPDATE CASCADE
;
INSERT INTO director_schema_migration
(schema_version, migration_time)
VALUES (168, NOW());

View File

@ -179,8 +179,17 @@ CREATE TABLE director_datalist_entry (
ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE director_datafield_category (
id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
category_name VARCHAR(255) NOT NULL,
description TEXT DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY category_name (category_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE director_datafield (
id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
category_id INT(10) UNSIGNED DEFAULT NULL,
varname VARCHAR(64) NOT NULL COLLATE utf8_bin,
caption VARCHAR(255) NOT NULL,
description TEXT DEFAULT NULL,
@ -188,7 +197,12 @@ CREATE TABLE director_datafield (
-- datatype_param? multiple ones?
format enum ('string', 'json', 'expression'),
PRIMARY KEY (id),
KEY search_idx (varname)
KEY search_idx (varname),
CONSTRAINT director_datalist_value_datalist
FOREIGN KEY category (category_id)
REFERENCES director_datafield_category (id)
ON DELETE RESTRICT
ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE director_datafield_setting (
@ -1869,4 +1883,4 @@ CREATE TABLE icinga_scheduled_downtime_range (
INSERT INTO director_schema_migration
(schema_version, migration_time)
VALUES (167, NOW());
VALUES (168, NOW());

View File

@ -0,0 +1,25 @@
CREATE TABLE director_datafield_category (
id serial,
category_name character varying(255) NOT NULL,
description text DEFAULT NULL,
PRIMARY KEY (id)
);
CREATE UNIQUE INDEX datafield_category_name ON director_datafield_category (category_name);
ALTER TABLE director_datafield
ADD COLUMN category_id integer DEFAULT NULL,
ADD CONSTRAINT director_datafield_category
FOREIGN KEY (category_id)
REFERENCES director_datafield_category (id)
ON DELETE RESTRICT
ON UPDATE CASCADE;
CREATE INDEX datafield_category ON director_datafield (category_id);
INSERT INTO director_schema_migration
(schema_version, migration_time)
VALUES (168, NOW());

View File

@ -248,18 +248,35 @@ CREATE TABLE director_datalist_entry (
CREATE INDEX datalist_entry_datalist ON director_datalist_entry (list_id);
CREATE TABLE director_datafield_category (
id serial,
category_name character varying(255) NOT NULL,
description text DEFAULT NULL,
PRIMARY KEY (id)
);
CREATE UNIQUE INDEX datafield_category_name ON director_datafield_category (category_name);
CREATE TABLE director_datafield (
id serial,
category_id integer DEFAULT NULL,
varname character varying(64) NOT NULL,
caption character varying(255) NOT NULL,
description text DEFAULT NULL,
datatype character varying(255) NOT NULL,
-- datatype_param? multiple ones?
format enum_property_format,
PRIMARY KEY (id)
PRIMARY KEY (id),
CONSTRAINT director_datafield_category
FOREIGN KEY (category_id)
REFERENCES director_datafield_category (id)
ON DELETE RESTRICT
ON UPDATE CASCADE
);
CREATE INDEX search_idx ON director_datafield (varname);
CREATE INDEX datafield_category ON director_datafield (category_id);
CREATE TABLE director_datafield_setting (
@ -2182,4 +2199,4 @@ COMMENT ON COLUMN icinga_scheduled_downtime_range.merge_behaviour IS 'set -> = {
INSERT INTO director_schema_migration
(schema_version, migration_time)
VALUES (167, NOW());
VALUES (168, NOW());