From 91295c44352106d4fe058daf12ffad9a781ff4a4 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Wed, 20 Apr 2016 22:38:28 +0200 Subject: [PATCH 01/74] DirectorDatafieldForm: provide a delete button fixes #11641 --- application/forms/DirectorDatafieldForm.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/application/forms/DirectorDatafieldForm.php b/application/forms/DirectorDatafieldForm.php index e9cd485f..f813a719 100644 --- a/application/forms/DirectorDatafieldForm.php +++ b/application/forms/DirectorDatafieldForm.php @@ -74,6 +74,8 @@ class DirectorDatafieldForm extends DirectorObjectForm $el->setValue($val); } } + + $this->setButtons(); } protected function addSettings($class = null) From c76d3b27bd33f891137af9a324510b2e68a69f48 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Wed, 20 Apr 2016 13:18:18 +0200 Subject: [PATCH 02/74] SyncruleController: clean up tab logic refs #11626 --- .../controllers/SyncruleController.php | 90 +++++++------------ 1 file changed, 32 insertions(+), 58 deletions(-) diff --git a/application/controllers/SyncruleController.php b/application/controllers/SyncruleController.php index b233d87a..40a33e47 100644 --- a/application/controllers/SyncruleController.php +++ b/application/controllers/SyncruleController.php @@ -32,41 +32,23 @@ class SyncruleController extends ActionController public function indexAction() { - $edit = false; - - if ($id = $this->params->get('id')) { - $edit = true; - } - - if ($edit) { - $this->view->title = $this->translate('Edit sync rule'); - $this->getTabs()->add('edit', array( - 'url' => 'director/syncrule/edit', - 'urlParams' => array('id' => $id), - 'label' => $this->view->title, - ))->add('property', array( - 'label' => $this->translate('Properties'), - 'url' => 'director/syncrule/property', - 'urlParams' => array('rule_id' => $id) - ))->activate('edit'); - } else { - $this->view->title = $this->translate('Add sync rule'); - $this->getTabs()->add('add', array( - 'url' => 'director/syncrule/add', - 'label' => $this->view->title, - ))->activate('add'); - } - $form = $this->view->form = $this->loadForm('syncRule') ->setSuccessUrl('director/list/syncrule') ->setDb($this->db()); - if ($edit) { + if ($id = $this->params->get('id')) { + $this->prepareRuleTabs($id)->activate('edit'); $form->loadObject($id); + $this->view->title = sprintf( + $this->translate('Sync rule: %s'), + $form->getObject()->rule_name + ); + } else { + $this->view->title = $this->translate('Add sync rule'); + $this->prepareRuleTabs()->activate('add'); } $form->handleRequest(); - $this->render('object/form', null, true); } @@ -74,6 +56,7 @@ class SyncruleController extends ActionController { $this->view->stayHere = true; $id = $this->params->get('rule_id'); + $this->prepareRuleTabs($id)->activate('property'); $this->view->addLink = $this->view->icon('plus') . ' ' @@ -82,17 +65,8 @@ class SyncruleController extends ActionController 'director/syncrule/addproperty', array('rule_id' => $id) ); - $this->getTabs()->add('edit', array( - 'url' => 'director/syncrule/edit', - 'urlParams' => array('id' => $id), - 'label' => $this->translate('Edit sync rule'), - ))->add('property', array( - 'label' => $this->translate('Properties'), - 'url' => 'director/syncrule/property', - 'urlParams' => array('rule_id' => $id) - ))->activate('property'); - $this->view->title = $this->translate('Sync properties: '); + $this->view->title = $this->translate('Sync properties'); $this->view->table = $this->loadTable('syncproperty') ->enforceFilter(Filter::where('rule_id', $id)) ->setConnection($this->db()); @@ -126,27 +100,7 @@ class SyncruleController extends ActionController $form->handleRequest(); - $tabs = $this->getTabs()->add('edit', array( - 'url' => 'director/syncrule/edit', - 'urlParams' => array('id' => $rule_id), - 'label' => $this->translate('Edit sync rule'), - )); - - if ($edit) { - $tabs->add('property', array( - 'label' => $this->translate('Properties'), - 'url' => 'director/syncrule/property', - 'urlParams' => array('rule_id' => $rule_id) - )); - } else { - $tabs->add('property', array( - 'label' => $this->translate('Properties'), - 'url' => 'director/syncrule/property', - 'urlParams' => array('rule_id' => $rule_id) - )); - } - - $tabs->activate('property'); + $this->prepareRuleTabs($rule_id)->activate('property'); $this->view->title = $this->translate('Sync property'); // add/edit $this->view->table = $this->loadTable('syncproperty') @@ -154,4 +108,24 @@ class SyncruleController extends ActionController ->setConnection($this->db()); $this->render('list/table', null, true); } + + protected function prepareRuleTabs($ruleId = null) + { + if ($ruleId) { + return $this->getTabs()->add('edit', array( + 'url' => 'director/syncrule/edit', + 'urlParams' => array('id' => $ruleId), + 'label' => $this->translate('Sync rule'), + ))->add('property', array( + 'label' => $this->translate('Properties'), + 'url' => 'director/syncrule/property', + 'urlParams' => array('rule_id' => $ruleId) + )); + } else { + return $this->getTabs()->add('add', array( + 'url' => 'director/syncrule/add', + 'label' => $this->translate('Sync rule'), + )); + } + } } From 5776a90a6a6b6eccbb63f811f2227bd59defb313 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Wed, 20 Apr 2016 14:04:45 +0200 Subject: [PATCH 03/74] SyncruleController: do not directly call render() --- application/controllers/SyncruleController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/application/controllers/SyncruleController.php b/application/controllers/SyncruleController.php index 40a33e47..9e47c314 100644 --- a/application/controllers/SyncruleController.php +++ b/application/controllers/SyncruleController.php @@ -49,7 +49,7 @@ class SyncruleController extends ActionController } $form->handleRequest(); - $this->render('object/form', null, true); + $this->setViewScript('object/form'); } public function propertyAction() @@ -70,7 +70,7 @@ class SyncruleController extends ActionController $this->view->table = $this->loadTable('syncproperty') ->enforceFilter(Filter::where('rule_id', $id)) ->setConnection($this->db()); - $this->render('list/table', null, true); + $this->setViewScript('list/table'); } public function editpropertyAction() @@ -106,7 +106,7 @@ class SyncruleController extends ActionController $this->view->table = $this->loadTable('syncproperty') ->enforceFilter(Filter::where('rule_id', $rule_id)) ->setConnection($this->db()); - $this->render('list/table', null, true); + $this->setViewScript('list/table'); } protected function prepareRuleTabs($ruleId = null) From 65b28fc2f6d191cd9dbc04dd0e30a968fbba78a1 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Wed, 20 Apr 2016 14:06:22 +0200 Subject: [PATCH 04/74] SyncRunTable: add new table, link and show it --- .../controllers/SyncruleController.php | 18 +++++ application/tables/SyncRunTable.php | 78 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 application/tables/SyncRunTable.php diff --git a/application/controllers/SyncruleController.php b/application/controllers/SyncruleController.php index 9e47c314..a8994c04 100644 --- a/application/controllers/SyncruleController.php +++ b/application/controllers/SyncruleController.php @@ -109,6 +109,20 @@ class SyncruleController extends ActionController $this->setViewScript('list/table'); } + public function historyAction() + { + $this->view->stayHere = true; + + $id = $this->params->get('id'); + $this->prepareRuleTabs($id)->activate('history'); + $this->view->title = $this->translate('Sync history'); + $this->view->table = $this->loadTable('syncRun') + ->enforceFilter(Filter::where('rule_id', $id)) + ->setConnection($this->db()); + + $this->setViewScript('list/table'); + } + protected function prepareRuleTabs($ruleId = null) { if ($ruleId) { @@ -120,6 +134,10 @@ class SyncruleController extends ActionController 'label' => $this->translate('Properties'), 'url' => 'director/syncrule/property', 'urlParams' => array('rule_id' => $ruleId) + ))->add('history', array( + 'label' => $this->translate('History'), + 'url' => 'director/syncrule/history', + 'urlParams' => array('id' => $ruleId) )); } else { return $this->getTabs()->add('add', array( diff --git a/application/tables/SyncRunTable.php b/application/tables/SyncRunTable.php new file mode 100644 index 00000000..1ba51910 --- /dev/null +++ b/application/tables/SyncRunTable.php @@ -0,0 +1,78 @@ + 'sr.id', + 'rule_id' => 'sr.rule_id', + 'rule_name' => 'sr.rule_name', + 'start_time' => 'sr.start_time', + 'duration_ms' => 'sr.duration_ms', + 'objects_deleted' => 'sr.objects_deleted', + 'objects_created' => 'sr.objects_created', + 'objects_modified' => 'sr.objects_modified', + 'last_former_activity' => 'sr.last_former_activity', + 'last_related_activity' => 'sr.last_related_activity', + ); + } + + protected function getActionUrl($row) + { + return $this->url( + 'director/syncrule/history', + array( + 'id' => $row->rule_id, + 'run_id' => $row->id, + ) + ); + } + + public function getTitles() + { + $singleRule = false; + + foreach ($this->enforcedFilters as $filter) { + if (in_array('rule_id', $filter->listFilteredColumns())) { + $singleRule = true; + break; + } + } + + $view = $this->view(); + + if ($singleRule) { + return array( + 'start_time' => $view->translate('Start time'), + 'objects_created' => $view->translate('Created'), + 'objects_modified' => $view->translate('Modified'), + 'objects_deleted' => $view->translate('Deleted'), + ); + } else { + return array( + 'rule_name' => $view->translate('Rule name'), + 'start_time' => $view->translate('Start time'), + ); + } + } + + public function getBaseQuery() + { + $db = $this->connection()->getConnection(); + + $query = $db->select()->from( + array('sr' => 'sync_run'), + array() + )->order('rule_name'); + + return $query; + } +} From 68215da96c8e28eb8de883b4f09afefc3cad2010 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Wed, 20 Apr 2016 14:34:08 +0200 Subject: [PATCH 05/74] syncrule/history: dedicated view, show/link actions --- .../controllers/SyncruleController.php | 13 ++- .../views/scripts/syncrule/history.phtml | 84 +++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 application/views/scripts/syncrule/history.phtml diff --git a/application/controllers/SyncruleController.php b/application/controllers/SyncruleController.php index a8994c04..9530c3ec 100644 --- a/application/controllers/SyncruleController.php +++ b/application/controllers/SyncruleController.php @@ -4,6 +4,7 @@ namespace Icinga\Module\Director\Controllers; use Icinga\Module\Director\Web\Controller\ActionController; use Icinga\Module\Director\Objects\SyncRule; +use Icinga\Module\Director\Objects\SyncRun; use Icinga\Module\Director\Import\Sync; use Icinga\Data\Filter\Filter; use Icinga\Web\Notification; @@ -120,7 +121,17 @@ class SyncruleController extends ActionController ->enforceFilter(Filter::where('rule_id', $id)) ->setConnection($this->db()); - $this->setViewScript('list/table'); + if ($runId = $this->params->get('run_id')) { + $db = $this->db(); + $this->view->run = SyncRun::load($runId, $db); + $this->view->formerId = $db->fetchActivityLogIdByChecksum( + $this->view->run->last_former_activity + ); + + $this->view->lastId = $db->fetchActivityLogIdByChecksum( + $this->view->run->last_related_activity + ); + } } protected function prepareRuleTabs($ruleId = null) diff --git a/application/views/scripts/syncrule/history.phtml b/application/views/scripts/syncrule/history.phtml new file mode 100644 index 00000000..a225d77c --- /dev/null +++ b/application/views/scripts/syncrule/history.phtml @@ -0,0 +1,84 @@ +
+tabs ?> +

escape($this->title) ?>

+stayHere): ?> data-base-target="_next"> +addLink ?> + +filterEditor ?> +table->getPaginator() ?> +
+ +
stayHere): ?> data-base-target="_next"> +run): ?> +

translate('Sync run details') ?>

+ + + + + + + + + + + + + +
translate('Start time') ?>escape($run->start_time) ?>
translate('Duration') ?>duration_ms / 1000) ?>
translate('Activity') ?>objects_deleted + $run->objects_created + $run->objects_modified; + if ($total === 0) { + echo $this->translate('No changes have been made'); + } else { + if ($total === 1) { + echo $this->translate('One object has been modified'); + } else { + printf( + $this->translate('%s objects have been modified'), + $total + ); + } + + $activityUrl = sprintf( + 'director/config/activities?id>%d&id<=%d', + $formerId, + $lastId + ); + + $links = array(); + if ($run->objects_created > 0) { + $links[] = $this->qlink( + sprintf('%d created', $run->objects_created), + $activityUrl, + array('action_name' => 'create') + ); + } + if ($run->objects_modified > 0) { + $links[] = $this->qlink( + sprintf('%d modified', $run->objects_modified), + $activityUrl, + array('action_name' => 'modify') + ); + } + if ($run->objects_deleted > 0) { + $links[] = $this->qlink( + sprintf('%d deleted', $run->objects_deleted), + $activityUrl, + array('action_name' => 'delete') + ); + } + + if (count($links) > 1) { + $links[] = $this->qlink( + 'Show all actions', + $activityUrl + ); + } + + if (! empty($links)) { + echo ': ' . implode(', ', $links); + } + } + ?>
+ +table->render() ?> +
From c1c9c849ba3489ab29c536cf7d727940cde2b2dc Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Wed, 20 Apr 2016 15:20:49 +0200 Subject: [PATCH 06/74] syncrule/property: improve usability --- .../controllers/SyncruleController.php | 58 ++++++++++++++----- application/forms/SyncPropertyForm.php | 1 - 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/application/controllers/SyncruleController.php b/application/controllers/SyncruleController.php index 9530c3ec..24b60214 100644 --- a/application/controllers/SyncruleController.php +++ b/application/controllers/SyncruleController.php @@ -56,18 +56,21 @@ class SyncruleController extends ActionController public function propertyAction() { $this->view->stayHere = true; + + $db = $this->db(); $id = $this->params->get('rule_id'); + $rule = SyncRule::load($id, $db); + $this->prepareRuleTabs($id)->activate('property'); - $this->view->addLink = $this->view->icon('plus') - . ' ' - . $this->view->qlink( - $this->translate('Add sync property rule'), - 'director/syncrule/addproperty', - array('rule_id' => $id) - ); + $this->view->addLink = $this->view->qlink( + $this->translate('Add sync property rule'), + 'director/syncrule/addproperty', + array('rule_id' => $id), + array('class' => 'icon-plus') + ); - $this->view->title = $this->translate('Sync properties'); + $this->view->title = $this->translate('Sync properties') . ': ' . $rule->rule_name; $this->view->table = $this->loadTable('syncproperty') ->enforceFilter(Filter::where('rule_id', $id)) ->setConnection($this->db()); @@ -84,26 +87,49 @@ class SyncruleController extends ActionController $this->view->stayHere = true; $edit = false; + $db = $this->db(); + $ruleId = $this->params->get('rule_id'); + $rule = SyncRule::load($ruleId, $db); + if ($id = $this->params->get('id')) { $edit = true; } - $form = $this->view->form = $this->loadForm('syncProperty')->setDb($this->db()); + $this->view->addLink = $this->view->qlink( + $this->translate('back'), + 'director/syncrule/property', + array('rule_id' => $ruleId), + array('class' => 'icon-left-big') + ); + + $form = $this->view->form = $this->loadForm('syncProperty')->setDb($db); if ($edit) { $form->loadObject($id); $rule_id = $form->getObject()->rule_id; - $form->setRule(SyncRule::load($rule_id, $this->db())); + $form->setRule(SyncRule::load($rule_id, $db)); } elseif ($rule_id = $this->params->get('rule_id')) { - $form->setRule(SyncRule::load($rule_id, $this->db())); + $form->setRule(SyncRule::load($rule_id, $db)); } - $form->setSuccessUrl('director/syncrule/property', array('rule_id' => $rule_id)); + $form->setSuccessUrl('director/syncrule/property', array('rule_id' => $rule_id)); $form->handleRequest(); $this->prepareRuleTabs($rule_id)->activate('property'); - $this->view->title = $this->translate('Sync property'); // add/edit + if ($edit) { + $this->view->title = sprintf( + $this->translate('Sync "%s": %s'), + $form->getObject()->destination_field, + $rule->rule_name + ); + } else { + $this->view->title = sprintf( + $this->translate('Add sync property: %s'), + $rule->rule_name + ); + } + $this->view->table = $this->loadTable('syncproperty') ->enforceFilter(Filter::where('rule_id', $rule_id)) ->setConnection($this->db()); @@ -114,15 +140,17 @@ class SyncruleController extends ActionController { $this->view->stayHere = true; + $db = $this->db(); $id = $this->params->get('id'); + $rule = SyncRule::load($id, $db); + $this->prepareRuleTabs($id)->activate('history'); - $this->view->title = $this->translate('Sync history'); + $this->view->title = $this->translate('Sync history') . ': ' . $rule->rule_name; $this->view->table = $this->loadTable('syncRun') ->enforceFilter(Filter::where('rule_id', $id)) ->setConnection($this->db()); if ($runId = $this->params->get('run_id')) { - $db = $this->db(); $this->view->run = SyncRun::load($runId, $db); $this->view->formerId = $db->fetchActivityLogIdByChecksum( $this->view->run->last_former_activity diff --git a/application/forms/SyncPropertyForm.php b/application/forms/SyncPropertyForm.php index 77d40d89..7ffa7b23 100644 --- a/application/forms/SyncPropertyForm.php +++ b/application/forms/SyncPropertyForm.php @@ -25,7 +25,6 @@ class SyncPropertyForm extends DirectorObjectForm public function setup() { - $this->addHtml(sprintf('

%s

', $this->getView()->escape($this->rule->rule_name))); $this->addHidden('rule_id', $this->rule_id); $this->addElement('select', 'source_id', array( From de544110b20a6c465286a46734fa77b62d92b554 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Wed, 20 Apr 2016 15:33:03 +0200 Subject: [PATCH 07/74] Sync: redirect to history on sync run --- application/controllers/SyncruleController.php | 14 ++++++++++++-- library/Director/Import/Sync.php | 4 ---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/application/controllers/SyncruleController.php b/application/controllers/SyncruleController.php index 24b60214..9e1405c5 100644 --- a/application/controllers/SyncruleController.php +++ b/application/controllers/SyncruleController.php @@ -8,6 +8,7 @@ use Icinga\Module\Director\Objects\SyncRun; use Icinga\Module\Director\Import\Sync; use Icinga\Data\Filter\Filter; use Icinga\Web\Notification; +use Icinga\Web\Url; class SyncruleController extends ActionController { @@ -23,10 +24,19 @@ class SyncruleController extends ActionController public function runAction() { - $sync = new Sync(SyncRule::load($this->params->get('id'), $this->db())); + $id = $this->params->get('id'); + $sync = new Sync(SyncRule::load($id, $this->db())); if ($runId = $sync->apply()) { Notification::success('Source has successfully been synchronized'); - $this->redirectNow('director/list/syncrule'); + $this->redirectNow( + Url::fromPath( + 'director/syncrule/history', + array( + 'id' => $id, + 'run_id' => $runId + ) + ) + ); } else { } } diff --git a/library/Director/Import/Sync.php b/library/Director/Import/Sync.php index 9eeebf14..1a59cb0f 100644 --- a/library/Director/Import/Sync.php +++ b/library/Director/Import/Sync.php @@ -671,9 +671,6 @@ class Sync /** * Runs a SyncRule and applies all resulting changes * - * TODO: Should return the id of the related sync_history table entry. - * Such a table does not yet exist, so 42 is the answer right now. - * * @return int */ public function apply() @@ -739,7 +736,6 @@ class Sync (microtime(true) - $this->runStartTime) * 1000 ))->store(); - return $this->run->id; } } From fcf62123ee2934c0c715ba3050b3602490ebb8c7 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Wed, 20 Apr 2016 15:34:58 +0200 Subject: [PATCH 08/74] SyncRunTable: order by start time --- application/tables/SyncRunTable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/tables/SyncRunTable.php b/application/tables/SyncRunTable.php index 1ba51910..73530323 100644 --- a/application/tables/SyncRunTable.php +++ b/application/tables/SyncRunTable.php @@ -71,7 +71,7 @@ class SyncRunTable extends QuickTable $query = $db->select()->from( array('sr' => 'sync_run'), array() - )->order('rule_name'); + )->order('start_time DESC'); return $query; } From 8c02b1e6c50bb060700a0884843fbdbb0d4de99b Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Wed, 20 Apr 2016 16:46:59 +0200 Subject: [PATCH 09/74] IcingaObject: fix multi-relation rendering order --- library/Director/Objects/IcingaObject.php | 1 + 1 file changed, 1 insertion(+) diff --git a/library/Director/Objects/IcingaObject.php b/library/Director/Objects/IcingaObject.php index c375936a..31d5eeea 100644 --- a/library/Director/Objects/IcingaObject.php +++ b/library/Director/Objects/IcingaObject.php @@ -155,6 +155,7 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer } } + ksort($this->loadedMultiRelations); return $this->loadedMultiRelations; } From 9c283e8bb6346176f3ad75f6215971f3e09f9f1c Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Thu, 21 Apr 2016 13:15:18 +0200 Subject: [PATCH 10/74] Housekeeping: add getPendingTasks helper method --- library/Director/Db/Housekeeping.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/Director/Db/Housekeeping.php b/library/Director/Db/Housekeeping.php index bdbc9ac2..7fa672f1 100644 --- a/library/Director/Db/Housekeeping.php +++ b/library/Director/Db/Housekeeping.php @@ -64,6 +64,11 @@ class Housekeeping ); } + public function hasPendingTasks() + { + return count($this->getPendingTaskSummary()) > 0 + } + public function runAllTasks() { $result = array(); From cf1093ef543c7bfd09ded2c038406ef895746f6c Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Thu, 21 Apr 2016 13:22:49 +0200 Subject: [PATCH 11/74] JobHook: provide a new hook for hookable jobs --- library/Director/Hook/JobHook.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 library/Director/Hook/JobHook.php diff --git a/library/Director/Hook/JobHook.php b/library/Director/Hook/JobHook.php new file mode 100644 index 00000000..290365ba --- /dev/null +++ b/library/Director/Hook/JobHook.php @@ -0,0 +1,27 @@ + Date: Thu, 21 Apr 2016 13:24:48 +0200 Subject: [PATCH 12/74] JobHook: provide Db --- library/Director/Hook/JobHook.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/library/Director/Hook/JobHook.php b/library/Director/Hook/JobHook.php index 290365ba..b7111eb5 100644 --- a/library/Director/Hook/JobHook.php +++ b/library/Director/Hook/JobHook.php @@ -2,9 +2,11 @@ namespace Icinga\Module\Director\Hook; +use Icinga\Module\Director\Db; + abstract class JobHook { - protected $settings = array(); + private $db; public function getName() { @@ -21,6 +23,17 @@ abstract class JobHook return $class; } + public function setDb(Db $db) + { + $this->db = $db; + return $this; + } + + protected function db() + { + return $this->db; + } + abstract public run(); abstract public function isPending(); From d292def6e72f4f182e7b7d0728074237e5a7e414 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Thu, 21 Apr 2016 13:27:39 +0200 Subject: [PATCH 13/74] HousekeepingJob: provide first simple job --- library/Director/Job/HouskeepingJob.php | 30 +++++++++++++++++++++++++ run.php | 2 ++ 2 files changed, 32 insertions(+) create mode 100644 library/Director/Job/HouskeepingJob.php diff --git a/library/Director/Job/HouskeepingJob.php b/library/Director/Job/HouskeepingJob.php new file mode 100644 index 00000000..318a22f4 --- /dev/null +++ b/library/Director/Job/HouskeepingJob.php @@ -0,0 +1,30 @@ +housekeeping()->runAllTasks(); + } + + public function isPending() + { + return $this->housekeeping()->hasPendingTasks(); + } + + protected function housekeeping() + { + if ($this->housekeeping === null) { + $this->housekeeping = new Housekeeping($this->db()); + } + + return $this->housekeeping; + } +} diff --git a/run.php b/run.php index c9814721..00326809 100644 --- a/run.php +++ b/run.php @@ -32,6 +32,8 @@ $this->provideHook('director/PropertyModifier', $prefix . 'PropertyModifier\\Pro $this->provideHook('director/PropertyModifier', $prefix . 'PropertyModifier\\PropertyModifierFromAdSid'); $this->provideHook('director/PropertyModifier', $prefix . 'PropertyModifier\\PropertyModifierFromLatin1'); +$this->provideHook('director/Job', $prefix . 'Job\\HousekeepingJob'); + if (Icinga::app()->isCli()) { return; } From 71b7a5b430ab0b7d0b2703204c8296dd6e67f783 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 09:28:57 +0200 Subject: [PATCH 14/74] HousekeepingJob: fix file name --- library/Director/Job/{HouskeepingJob.php => HousekeepingJob.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename library/Director/Job/{HouskeepingJob.php => HousekeepingJob.php} (100%) diff --git a/library/Director/Job/HouskeepingJob.php b/library/Director/Job/HousekeepingJob.php similarity index 100% rename from library/Director/Job/HouskeepingJob.php rename to library/Director/Job/HousekeepingJob.php From 7efbbe5bd116fee2c1308751278a8eb5d1133dca Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 09:31:52 +0200 Subject: [PATCH 15/74] JobHook: require a description --- library/Director/Hook/JobHook.php | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/library/Director/Hook/JobHook.php b/library/Director/Hook/JobHook.php index b7111eb5..346c021b 100644 --- a/library/Director/Hook/JobHook.php +++ b/library/Director/Hook/JobHook.php @@ -3,11 +3,21 @@ namespace Icinga\Module\Director\Hook; use Icinga\Module\Director\Db; +use Icinga\Module\Director\Web\Form\QuickForm; abstract class JobHook { private $db; + public static function getDescription(QuickForm $form) + { + return false; + } + + abstract public function run(); + + abstract public function isPending(); + public function getName() { $parts = explode('\\', get_class($this)); @@ -23,6 +33,17 @@ abstract class JobHook return $class; } + /** + * Override this method if you want to extend the settings form + * + * @param QuickForm $form QuickForm that should be extended + * @return QuickForm + */ + public static function addSettingsFormFields(QuickForm $form) + { + return $form; + } + public function setDb(Db $db) { $this->db = $db; @@ -34,7 +55,5 @@ abstract class JobHook return $this->db; } - abstract public run(); - abstract public function isPending(); } From bd553e65ec535c6040dd321e65913c06e5306977 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 09:53:59 +0200 Subject: [PATCH 16/74] Job: add and register a few more jobs --- library/Director/Job/ConfigJob.php | 107 +++++++++++++++++++++++++++++ library/Director/Job/ImportJob.php | 24 +++++++ library/Director/Job/JobRunner.php | 51 ++++++++++++++ library/Director/Job/SyncJob.php | 24 +++++++ run.php | 3 + 5 files changed, 209 insertions(+) create mode 100644 library/Director/Job/ConfigJob.php create mode 100644 library/Director/Job/ImportJob.php create mode 100644 library/Director/Job/JobRunner.php create mode 100644 library/Director/Job/SyncJob.php diff --git a/library/Director/Job/ConfigJob.php b/library/Director/Job/ConfigJob.php new file mode 100644 index 00000000..da80a985 --- /dev/null +++ b/library/Director/Job/ConfigJob.php @@ -0,0 +1,107 @@ +housekeeping()->runAllTasks(); + } + + public function isPending() + { + return $this->housekeeping()->hasPendingTasks(); + } + + public static function getDescription(QuickForm $form) + { + return $form->translate( + 'The Housekeeping job provides various task that keep your Director' + . ' database fast and clean' + ); + } + + protected function housekeeping() + { + if ($this->housekeeping === null) { + $this->housekeeping = new Housekeeping($this->db()); + } + + return $this->housekeeping; + } + + /** + * Re-render the current configuration + */ + public function renderAction() + { + $config = new IcingaConfig($this->db()); + Benchmark::measure('Rendering config'); + if ($config->hasBeenModified()) { + Benchmark::measure('Config rendered, storing to db'); + $config->store(); + Benchmark::measure('All done'); + $checksum = $config->getHexChecksum(); + $this->printf( + "New config with checksum %s has been generated\n", + $checksum + ); + } else { + $checksum = $config->getHexChecksum(); + $this->printf( + "Config with checksum %s already exists\n", + $checksum + ); + } + } + + /** + * Deploy the current configuration + * + * Does nothing if config didn't change unless you provide + * the --force parameter + */ + public function deployAction() + { + $api = $this->api(); + $db = $this->db(); + + $checksum = $this->params->get('checksum'); + if ($checksum) { + $config = IcingaConfig::load(Util::hex2binary($checksum), $db); + } else { + $config = IcingaConfig::generate($db); + $checksum = $config->getHexChecksum(); + } + + $api->wipeInactiveStages($db); + $current = $api->getActiveChecksum($db); + if ($current === $checksum) { + if ($this->params->get('force')) { + echo "Config matches active stage, deploying anyway\n"; + } else { + echo "Config matches active stage, nothing to do\n"; + + return; + } + + } else { + if ($api->dumpConfig($config, $db)) { + $this->printf("Config '%s' has been deployed\n", $checksum); + } else { + $this->fail( + sprintf("Failed to deploy config '%s'\n", $checksum) + ); + } + } + } +} diff --git a/library/Director/Job/ImportJob.php b/library/Director/Job/ImportJob.php new file mode 100644 index 00000000..2e480a54 --- /dev/null +++ b/library/Director/Job/ImportJob.php @@ -0,0 +1,24 @@ +translate( + 'The "Import" job allows to run import actions at regular intervals' + ); + } + + public function isPending() + { + } +} diff --git a/library/Director/Job/JobRunner.php b/library/Director/Job/JobRunner.php new file mode 100644 index 00000000..90751b0c --- /dev/null +++ b/library/Director/Job/JobRunner.php @@ -0,0 +1,51 @@ +db = $db; + } + + public function runPendingJobs() + { + foreach ($this->getConfiguredJobs() as $job) { + if ($job->isPending()) { + $this->run($job); + } + } + } + + protected function run(Job $job) + { + if ($this->shouldFork()) { + $this->fork($job); + } else { + $this->run($job); + } + } + + protected function run(Job $job) + { + $job->run(); + } + + protected function fork(Job $job) + { + $cmd = 'icingacli director job run ' . $job->id; + $output = `$cmd`; + } + + protected function shouldFork() + { + return true; + } + + protected getRegisteredJobs() + { + } +} diff --git a/library/Director/Job/SyncJob.php b/library/Director/Job/SyncJob.php new file mode 100644 index 00000000..21bfe761 --- /dev/null +++ b/library/Director/Job/SyncJob.php @@ -0,0 +1,24 @@ +translate( + 'The "Sync" job allows to run sync actions at regular intervals' + ); + } + + public function isPending() + { + } +} diff --git a/run.php b/run.php index 00326809..01170726 100644 --- a/run.php +++ b/run.php @@ -33,6 +33,9 @@ $this->provideHook('director/PropertyModifier', $prefix . 'PropertyModifier\\Pro $this->provideHook('director/PropertyModifier', $prefix . 'PropertyModifier\\PropertyModifierFromLatin1'); $this->provideHook('director/Job', $prefix . 'Job\\HousekeepingJob'); +$this->provideHook('director/Job', $prefix . 'Job\\ConfigJob'); +$this->provideHook('director/Job', $prefix . 'Job\\ImportJob'); +$this->provideHook('director/Job', $prefix . 'Job\\SyncJob'); if (Icinga::app()->isCli()) { return; From ee041b5aacca8c784840d5ee3655c6a8f312e21b Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 09:55:09 +0200 Subject: [PATCH 17/74] DirectorJob: add job object --- library/Director/Objects/DirectorJob.php | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 library/Director/Objects/DirectorJob.php diff --git a/library/Director/Objects/DirectorJob.php b/library/Director/Objects/DirectorJob.php new file mode 100644 index 00000000..289627f3 --- /dev/null +++ b/library/Director/Objects/DirectorJob.php @@ -0,0 +1,28 @@ + null, + 'job_name' => null, + 'job_class' => null, + 'disabled' => null, + 'run_interval' => null, + 'last_attempt_succeeded' => null, + 'ts_last_attempt' => null, + 'ts_last_error' => null, + 'last_error_message' => null, + ); + + protected $settingsTable = 'director_job_setting'; +} From 68d7f9098c51a65e5477c1f73a64e6dd240c910c Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 10:04:48 +0200 Subject: [PATCH 18/74] HousekeepingJob: add description --- library/Director/Job/HousekeepingJob.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/library/Director/Job/HousekeepingJob.php b/library/Director/Job/HousekeepingJob.php index 318a22f4..9f3f596e 100644 --- a/library/Director/Job/HousekeepingJob.php +++ b/library/Director/Job/HousekeepingJob.php @@ -4,6 +4,7 @@ namespace Icinga\Module\Director\Job; use Icinga\Module\Director\Db\Housekeeping; use Icinga\Module\Director\Hook\JobHook; +use Icinga\Module\Director\Web\Form\QuickForm; class HousekeepingJob extends JobHook { @@ -14,6 +15,14 @@ class HousekeepingJob extends JobHook $this->housekeeping()->runAllTasks(); } + public static function getDescription(QuickForm $form) + { + return $form->translate( + 'The Housekeeping job provides various task that keep your Director' + . ' database fast and clean' + ); + } + public function isPending() { return $this->housekeeping()->hasPendingTasks(); From e01cfeabef188b69449fca051a70425398611c86 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 11:07:57 +0200 Subject: [PATCH 19/74] schema/mysql: extend sync rule --- schema/mysql-migrations/upgrade_93.sql | 22 ++++++++++++++++++++++ schema/mysql.sql | 10 +++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 schema/mysql-migrations/upgrade_93.sql diff --git a/schema/mysql-migrations/upgrade_93.sql b/schema/mysql-migrations/upgrade_93.sql new file mode 100644 index 00000000..845d4bfd --- /dev/null +++ b/schema/mysql-migrations/upgrade_93.sql @@ -0,0 +1,22 @@ +ALTER TABLE sync_rule + ADD COLUMN sync_state ENUM( + 'unknown', + 'in-sync', + 'pending-changes', + 'failing' + ) NOT NULL DEFAULT 'unknown', + ADD COLUMN last_error_message VARCHAR(255) DEFAULT NULL, + ADD COLUMN last_attempt DATETIME DEFAULT NULL +; + +UPDATE sync_rule r + JOIN ( + SELECT rule_id, MAX(start_time) AS start_time + FROM sync_run + GROUP BY rule_id + ) lr ON r.id = lr.rule_id + SET r.last_attempt = lr.start_time; + +INSERT INTO director_schema_migration + (schema_version, migration_time) + VALUES (93, NOW()); diff --git a/schema/mysql.sql b/schema/mysql.sql index 8f06b0eb..52d81419 100644 --- a/schema/mysql.sql +++ b/schema/mysql.sql @@ -1214,6 +1214,14 @@ CREATE TABLE sync_rule ( update_policy ENUM('merge', 'override', 'ignore') NOT NULL, purge_existing ENUM('y', 'n') NOT NULL DEFAULT 'n', filter_expression TEXT DEFAULT NULL, + sync_state ENUM( + 'unknown', + 'in-sync', + 'pending-changes', + 'failing' + ) NOT NULL DEFAULT 'unknown', + last_error_message VARCHAR(255) DEFAULT NULL, + last_attempt DATETIME DEFAULT NULL, PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -1260,4 +1268,4 @@ CREATE TABLE sync_run ( INSERT INTO director_schema_migration SET migration_time = NOW(), - schema_version = 92; + schema_version = 93; From c92d1caeb4a316f42316af6527d56e9c2f3fcef7 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 11:09:54 +0200 Subject: [PATCH 20/74] SyncRule: adjust to fit latest schema changes --- library/Director/Objects/SyncRule.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/library/Director/Objects/SyncRule.php b/library/Director/Objects/SyncRule.php index f26bd043..e6d1b094 100644 --- a/library/Director/Objects/SyncRule.php +++ b/library/Director/Objects/SyncRule.php @@ -14,12 +14,15 @@ class SyncRule extends DbObject protected $autoincKeyName = 'id'; protected $defaultProperties = array( - 'id' => null, - 'rule_name' => null, - 'object_type' => null, - 'update_policy' => null, - 'purge_existing' => null, - 'filter_expression' => null, + 'id' => null, + 'rule_name' => null, + 'object_type' => null, + 'update_policy' => null, + 'purge_existing' => null, + 'filter_expression' => null, + 'sync_state' => 'unknown', + 'last_error_message' => null, + 'last_attempt' => null, ); private $filter; From b37716cabe488adf130dc5d00a7b92a96be7309a Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 11:19:54 +0200 Subject: [PATCH 21/74] Index: show sync state on dashboard --- application/controllers/IndexController.php | 26 +++++++++++++++++++++ application/views/scripts/index/index.phtml | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/application/controllers/IndexController.php b/application/controllers/IndexController.php index 391b26eb..6a7c5770 100644 --- a/application/controllers/IndexController.php +++ b/application/controllers/IndexController.php @@ -3,6 +3,7 @@ namespace Icinga\Module\Director\Controllers; use Exception; +use Icinga\Module\Director\Objects\SyncRule; use Icinga\Module\Director\Web\Controller\ActionController; class IndexController extends ActionController @@ -29,9 +30,34 @@ class IndexController extends ActionController 'url' => $this->getRequest()->getUrl(), 'label' => $this->translate('Overview') ))->activate('overview'); + + $this->fetchSyncState(); } } + protected function fetchSyncState() + { + $syncs = SyncRule::loadAll($this->db()); + if (count($syncs) > 0) { + $state = 'ok'; + } else { + $state = null; + } + + foreach ($syncs as $sync) { + if ($sync->sync_state !== 'in-sync') { + if ($sync->sync_state === 'failing') { + $state = 'critical'; + break; + } else { + $state = 'warning'; + } + } + } + + $this->view->syncState = $state; + } + protected function hasDeploymentEndpoint() { try { diff --git a/application/views/scripts/index/index.phtml b/application/views/scripts/index/index.phtml index 7cf930fb..afec2dd1 100644 --- a/application/views/scripts/index/index.phtml +++ b/application/views/scripts/index/index.phtml @@ -102,7 +102,7 @@ $all = array( ), $this->translate('Do more with your data') => array( array('database', $this->translate('Import data sources'), 'director/list/importsource', $this->translate('Define and manage imports from various data sources')), - array('flapping', $this->translate('Synchronize'), 'director/list/importsource', $this->translate('Define how imported data should be synchronized with Icinga')), + array('flapping', $this->translate('Synchronize'), 'director/list/syncrule', $this->translate('Define how imported data should be synchronized with Icinga'), $this->syncState), array('sort-name-up', $this->translate('Provide data lists'), 'director/data/lists', $this->translate('Provide data lists to make life easier for your users')), array('edit', $this->translate('Define data fields'), 'director/data/fields', $this->translate('Data fields make sure that configuration fits your rules')), ) From 86bc4fa457de765e06d104912e783e51d7055d92 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 11:21:01 +0200 Subject: [PATCH 22/74] SyncruleTable: use pre-checked sync-state --- application/tables/SyncruleTable.php | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/application/tables/SyncruleTable.php b/application/tables/SyncruleTable.php index a47c4d0e..ac2f6cdb 100644 --- a/application/tables/SyncruleTable.php +++ b/application/tables/SyncruleTable.php @@ -9,13 +9,12 @@ use Exception; class SyncruleTable extends QuickTable { - protected $revalidate = false; - public function getColumns() { return array( 'id' => 's.id', 'rule_name' => 's.rule_name', + 'sync_state' => 's.sync_state', 'object_type' => 's.object_type', 'update_policy' => 's.update_policy', 'purge_existing' => 's.purge_existing', @@ -25,7 +24,7 @@ class SyncruleTable extends QuickTable protected function getActionUrl($row) { - return $this->url('director/syncrule/edit', array('id' => $row->id)); + return $this->url('director/syncrule', array('id' => $row->id)); } protected function listTableClasses() @@ -45,25 +44,7 @@ class SyncruleTable extends QuickTable protected function getRowClasses($row) { - if (! $this->revalidate) { - return array(); - } - - try { - // $mod = Sync::hasModifications( - $sync = new Sync(SyncRule::load($row->id, $this->connection())); - $mod = $sync->getExpectedModifications(); - - if (count($mod) > 0) { - $row->rule_name = $row->rule_name . ' (' . count($mod) . ')'; - return 'pending-changes'; - } else { - return 'in-sync'; - } - } catch (Exception $e) { - $row->rule_name = $row->rule_name . ' (' . $e->getMessage() . ')'; - return 'failing'; - } + return $row->sync_state; } public function getTitles() From 6298659c32244bfd4e4733b5552fc635b25a115e Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 11:21:55 +0200 Subject: [PATCH 23/74] ConfigCommand: add missing Util class --- application/clicommands/ConfigCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/clicommands/ConfigCommand.php b/application/clicommands/ConfigCommand.php index ca44d853..85408e1c 100644 --- a/application/clicommands/ConfigCommand.php +++ b/application/clicommands/ConfigCommand.php @@ -5,7 +5,7 @@ namespace Icinga\Module\Director\Clicommands; use Icinga\Application\Benchmark; use Icinga\Module\Director\Cli\Command; use Icinga\Module\Director\IcingaConfig\IcingaConfig; -use Icinga\Module\Director\Data\Db\DbObject; +use Icinga\Module\Director\Util; /** * Generate, show and deploy Icinga 2 configuration From 51ebf79632c7d5132753faa60498c48c26621502 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 11:28:51 +0200 Subject: [PATCH 24/74] JobRunner: fix PHP errors --- library/Director/Job/JobRunner.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/library/Director/Job/JobRunner.php b/library/Director/Job/JobRunner.php index 90751b0c..f3ea7bd9 100644 --- a/library/Director/Job/JobRunner.php +++ b/library/Director/Job/JobRunner.php @@ -25,15 +25,10 @@ class JobRunner if ($this->shouldFork()) { $this->fork($job); } else { - $this->run($job); + $job->run(); } } - protected function run(Job $job) - { - $job->run(); - } - protected function fork(Job $job) { $cmd = 'icingacli director job run ' . $job->id; @@ -45,7 +40,7 @@ class JobRunner return true; } - protected getRegisteredJobs() + protected function getRegisteredJobs() { } } From f5f4bb1dfa09f4875795a6a68b8d40dc09f15c87 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 11:29:09 +0200 Subject: [PATCH 25/74] Housekeeping: semicolon --- library/Director/Db/Housekeeping.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Director/Db/Housekeeping.php b/library/Director/Db/Housekeeping.php index 7fa672f1..366fe976 100644 --- a/library/Director/Db/Housekeeping.php +++ b/library/Director/Db/Housekeeping.php @@ -66,7 +66,7 @@ class Housekeeping public function hasPendingTasks() { - return count($this->getPendingTaskSummary()) > 0 + return count($this->getPendingTaskSummary()) > 0; } public function runAllTasks() From c494c808ca5acdbaca8be8bad44e54331222b8d1 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 11:40:15 +0200 Subject: [PATCH 26/74] schema/mysql: add job tables --- schema/mysql-migrations/upgrade_94.sql | 29 ++++++++++++++++++++++++++ schema/mysql.sql | 28 ++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 schema/mysql-migrations/upgrade_94.sql diff --git a/schema/mysql-migrations/upgrade_94.sql b/schema/mysql-migrations/upgrade_94.sql new file mode 100644 index 00000000..5b55b37b --- /dev/null +++ b/schema/mysql-migrations/upgrade_94.sql @@ -0,0 +1,29 @@ +CREATE TABLE director_job ( + id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL, + job_name VARCHAR(64) NOT NULL, + job_class VARCHAR(72) NOT NULL, + disabled ENUM('y', 'n') NOT NULL DEFAULT 'n', + run_interval INT(10) UNSIGNED NOT NULL, -- seconds + last_attempt_succeeded ENUM('y', 'n') DEFAULT NULL, + ts_last_attempt DATETIME DEFAULT NULL, + ts_last_error DATETIME DEFAULT NULL, + last_error_message TEXT, + PRIMARY KEY (id), + UNIQUE KEY (job_name) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE director_job_setting ( + job_id INT UNSIGNED NOT NULL, + setting_name VARCHAR(64) NOT NULL, + setting_value TEXT DEFAULT NULL, + PRIMARY KEY (job_id, setting_name), + CONSTRAINT job_settings + FOREIGN KEY director_job (job_id) + REFERENCES director_job (id) + ON DELETE CASCADE + ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT INTO director_schema_migration + (schema_version, migration_time) + VALUES (94, NOW()); diff --git a/schema/mysql.sql b/schema/mysql.sql index 52d81419..e6663e70 100644 --- a/schema/mysql.sql +++ b/schema/mysql.sql @@ -140,6 +140,32 @@ CREATE TABLE director_datafield_setting ( ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +CREATE TABLE director_job ( + id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL, + job_name VARCHAR(64) NOT NULL, + job_class VARCHAR(72) NOT NULL, + disabled ENUM('y', 'n') NOT NULL DEFAULT 'n', + run_interval INT(10) UNSIGNED NOT NULL, -- seconds + last_attempt_succeeded ENUM('y', 'n') DEFAULT NULL, + ts_last_attempt DATETIME DEFAULT NULL, + ts_last_error DATETIME DEFAULT NULL, + last_error_message TEXT, + PRIMARY KEY (id), + UNIQUE KEY (job_name) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE director_job_setting ( + job_id INT UNSIGNED NOT NULL, + setting_name VARCHAR(64) NOT NULL, + setting_value TEXT DEFAULT NULL, + PRIMARY KEY (job_id, setting_name), + CONSTRAINT job_settings + FOREIGN KEY director_job (job_id) + REFERENCES director_job (id) + ON DELETE CASCADE + ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + CREATE TABLE director_schema_migration ( schema_version SMALLINT UNSIGNED NOT NULL, migration_time DATETIME NOT NULL, @@ -1268,4 +1294,4 @@ CREATE TABLE sync_run ( INSERT INTO director_schema_migration SET migration_time = NOW(), - schema_version = 93; + schema_version = 94; From 7265a5796ba0f3d4a9f2d05a9ced93ce96388c05 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 12:10:48 +0200 Subject: [PATCH 27/74] SyncRule: add checkForChanges helper --- library/Director/Objects/SyncRule.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/library/Director/Objects/SyncRule.php b/library/Director/Objects/SyncRule.php index e6d1b094..142e5037 100644 --- a/library/Director/Objects/SyncRule.php +++ b/library/Director/Objects/SyncRule.php @@ -2,8 +2,11 @@ namespace Icinga\Module\Director\Objects; +use Icinga\Application\Benchmark; use Icinga\Data\Filter\Filter; use Icinga\Module\Director\Data\Db\DbObject; +use Icinga\Module\Director\Import\Sync; +use Exception; class SyncRule extends DbObject { @@ -70,6 +73,30 @@ class SyncRule extends DbObject return $this->filter()->matches($row); } + public function checkForChanges() + { + Benchmark::measure('Checking sync rule ' . $this->rule_name); + try { + $sync = new Sync($this); + if ($sync->hasModifications()) { + Benchmark::measure('Got modifications for sync rule ' . $this->rule_name); + $this->sync_state = 'pending-changes'; + } else { + Benchmark::measure('No modifications for sync rule ' . $this->rule_name); + $this->sync_state = 'in-sync'; + } + + $this->last_error_message = null; + } catch (Exception $e) { + $this->sync_state = 'failing'; + $this->last_error_message = $e->getMessage(); + } + + if ($this->hasBeenModified()) { + $this->store(); + } + } + protected function filter() { if ($this->filter === null) { From 4cc70311f2b45235dd6ad8c777724219f2fe4c29 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 13:38:36 +0200 Subject: [PATCH 28/74] JobHook: add getSuggestedRunInterval() --- library/Director/Hook/JobHook.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/library/Director/Hook/JobHook.php b/library/Director/Hook/JobHook.php index 346c021b..f2366cfa 100644 --- a/library/Director/Hook/JobHook.php +++ b/library/Director/Hook/JobHook.php @@ -33,6 +33,11 @@ abstract class JobHook return $class; } + public static function getSuggestedRunInterval(QuickForm $form) + { + return 900; + } + /** * Override this method if you want to extend the settings form * From e88d4900214aa443bef9590933172d39005f03c3 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 13:40:05 +0200 Subject: [PATCH 29/74] SyncRule: add helper allowing to apply changes --- library/Director/Objects/SyncRule.php | 31 +++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/library/Director/Objects/SyncRule.php b/library/Director/Objects/SyncRule.php index 142e5037..d5b7d72d 100644 --- a/library/Director/Objects/SyncRule.php +++ b/library/Director/Objects/SyncRule.php @@ -28,6 +28,8 @@ class SyncRule extends DbObject 'last_attempt' => null, ); + private $sync; + private $filter; public function listInvolvedSourceIds() @@ -73,14 +75,23 @@ class SyncRule extends DbObject return $this->filter()->matches($row); } - public function checkForChanges() + public function checkForChanges($apply = false) { + $hadChanges = false; + Benchmark::measure('Checking sync rule ' . $this->rule_name); try { - $sync = new Sync($this); + $sync = $this->sync(); if ($sync->hasModifications()) { Benchmark::measure('Got modifications for sync rule ' . $this->rule_name); $this->sync_state = 'pending-changes'; + if ($apply && $sync->apply()) { + Benchmark::measure('Successfully synced rule ' . $rule->rule_name); + $this->sync_state = 'in-sync'; + } + + $hadChanges = true; + } else { Benchmark::measure('No modifications for sync rule ' . $this->rule_name); $this->sync_state = 'in-sync'; @@ -95,6 +106,22 @@ class SyncRule extends DbObject if ($this->hasBeenModified()) { $this->store(); } + + return $hadChanges; + } + + public function applyChanges() + { + return $this->checkForChanges(true); + } + + protected function sync() + { + if ($this->sync === null) { + $this->sync = new Sync($this); + } + + return $this->sync; } protected function filter() From 35c592ffbadeb967c2b02b077508fab9a763c166 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 14:27:09 +0200 Subject: [PATCH 30/74] css: add styling for jobs table --- public/css/module.less | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/public/css/module.less b/public/css/module.less index f696646d..4972edf8 100644 --- a/public/css/module.less +++ b/public/css/module.less @@ -722,7 +722,6 @@ table.tinystats { } /* Simple table, test */ - table.syncstate { tr td:first-child { padding-left: 2em; @@ -753,6 +752,41 @@ table.syncstate { } } +table.jobs { + tr td:first-child { + padding-left: 2em; + &::before { + font-family: 'ifont'; + // icon-help: + content: '\e85b'; + float: left; + font-weight: bold; + margin-left: -1.5em; + line-height: 1.5em; + } + } + + tr.ok td:first-child::before { + content: '\e803'; + color: @color-ok; + } + + tr.warning td:first-child::before { + content: '\e864'; + color: @color-warning; + } + + tr.pending td:first-child::before { + content: '\e864'; + color: @color-pending; + } + + tr.critical td:first-child::before { + content: '\e804'; + color: @color-critical; + } +} + table.icinga-objects { tr td:first-child { padding-left: 2em; From d05d58cc48e49654241cad6675f851d8b9f8908f Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 14:27:32 +0200 Subject: [PATCH 31/74] Jobs: controller and table --- application/controllers/JobsController.php | 32 +++++++++ application/tables/JobTable.php | 75 ++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 application/controllers/JobsController.php create mode 100644 application/tables/JobTable.php diff --git a/application/controllers/JobsController.php b/application/controllers/JobsController.php new file mode 100644 index 00000000..44577634 --- /dev/null +++ b/application/controllers/JobsController.php @@ -0,0 +1,32 @@ +view->title = $this->translate('Jobs'); + + $this->getTabs()->add('jobs', array( + 'url' => 'director/jobs', + 'label' => $this->translate('Jobs'), + ))->activate('jobs'); + + $this->view->addLink = $this->view->qlink( + $this->translate('Add'), + 'director/job', + null, + array('class' => 'icon-plus') + ); + + $this->view->table = $this->applyPaginationLimits( + $this->loadTable('job') + ->setConnection($this->db()) + ); + $this->setViewScript('list/table'); + + } +} diff --git a/application/tables/JobTable.php b/application/tables/JobTable.php new file mode 100644 index 00000000..d3d69c94 --- /dev/null +++ b/application/tables/JobTable.php @@ -0,0 +1,75 @@ + 'j.id', + 'job_name' => 'j.job_name', + 'job_class' => 'j.job_class', + 'disabled' => 'j.disabled', + 'run_interval' => 'j.run_interval', + 'last_attempt_succeeded' => 'j.last_attempt_succeeded', + 'ts_last_attempt' => 'j.ts_last_attempt', + 'unixts_last_attempt' => 'UNIX_TIMESTAMP(j.ts_last_attempt)', + 'ts_last_error' => 'j.ts_last_error', + 'last_error_message' => 'j.last_error_message', + ); + } + + protected function getActionUrl($row) + { + return $this->url('director/job', array('id' => $row->id)); + } + + protected function listTableClasses() + { + return array_merge(array('jobs'), parent::listTableClasses()); + } + + protected function getRowClasses($row) + { + if ($row->unixts_last_attempt === null) { + return 'pending'; + } + if ($row->unixts_last_attempt + $row->run_interval < time()) { + return 'pending'; + } + + if ($row->last_attempt_succeeded === 'y') { + return 'ok'; + } elseif ($row->last_attempt_succeeded === 'n') { + return 'critical'; + } else { + return 'unknown'; + } + } + + public function getTitles() + { + $view = $this->view(); + return array( + 'job_name' => $view->translate('Job name'), + 'object_type' => $view->translate('Object type'), + ); + } + + public function getBaseQuery() + { + $db = $this->connection()->getConnection(); + + $query = $db->select()->from( + array('j' => 'director_job'), + array() + )->order('job_name'); + + return $query; + } +} From b806cb6c64f02d552e09c45184c1843e610268a8 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 14:28:36 +0200 Subject: [PATCH 32/74] DirectorJobForm: new form, using job hooks --- application/forms/DirectorJobForm.php | 117 ++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 application/forms/DirectorJobForm.php diff --git a/application/forms/DirectorJobForm.php b/application/forms/DirectorJobForm.php new file mode 100644 index 00000000..51bf36ef --- /dev/null +++ b/application/forms/DirectorJobForm.php @@ -0,0 +1,117 @@ +addElement('select', 'job_class', array( + 'label' => $this->translate('Job Type'), + 'required' => true, + 'multiOptions' => $this->optionalEnum($this->enumJobTypes()), + 'description' => $this->translate( + 'These are different available job types' + ), + 'class' => 'autosubmit' + )); + + if (! $jobClass = $this->getJobClass()) { + return; + } + + if ($desc = $jobClass::getDescription($this)) { + $this->addHtmlHint($desc); + } + + $this->addBoolean( + 'disabled', + array( + 'label' => $this->translate('Disabled'), + 'description' => $this->translate( + 'This allows to temporarily disable this job' + ) + ), + 'n' + ); + + $this->addElement('text', 'run_interval', array( + 'label' => $this->translate('Run interval'), + 'description' => $this->translate( + 'Execution interval for this job, in seconds' + ), + 'value' => $jobClass::getSuggestedRunInterval($this) + )); + + $this->addElement('text', 'job_name', array( + 'label' => $this->translate('Job name'), + 'description' => $this->translate( + 'A short name identifying this job. Use something meaningful,' + . ' like "Import Puppet Hosts"' + ), + 'required' => true, + )); + + $this->addSettings(); + $this->setButtons(); + } + + public function getSentOrObjectSetting($name, $default = null) + { + if ($this->hasObject()) { + $value = $this->getSentValue($name); + if ($value === null) { + $object = $this->getObject(); + + return $object->getSetting($name, $default); + } else { + return $value; + } + } else { + return $this->getSentValue($name, $default); + } + } + + protected function getJobClass($class = null) + { + if ($class === null) { + $class = $this->getSentOrObjectValue('job_class'); + } + + if (array_key_exists($class, $this->enumJobTypes())) { + return $class; + } + + return null; + } + + protected function addSettings($class = null) + { + if (! $class = $this->getJobClass($class)) { + return; + } + + $class::addSettingsFormFields($this); + foreach ($this->object()->getSettings() as $key => $val) { + if ($el = $this->getElement($key)) { + $el->setValue($val); + } + } + } + + protected function enumJobTypes() + { + $hooks = Hook::all('Director\\Job'); + + $enum = array(); + foreach ($hooks as $hook) { + $enum[get_class($hook)] = $hook->getName(); + } + asort($enum); + + return $enum; + } +} From 4e0f1da65dcf5af35de45d5b51d92f7b61bb6fa3 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 14:30:01 +0200 Subject: [PATCH 33/74] SyncJob: provide job configuration --- library/Director/Job/SyncJob.php | 59 ++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/library/Director/Job/SyncJob.php b/library/Director/Job/SyncJob.php index 21bfe761..0f2c4b26 100644 --- a/library/Director/Job/SyncJob.php +++ b/library/Director/Job/SyncJob.php @@ -2,6 +2,7 @@ namespace Icinga\Module\Director\Job; +use Icinga\Module\Director\Db; use Icinga\Module\Director\Hook\JobHook; use Icinga\Module\Director\Web\Form\QuickForm; @@ -9,6 +10,11 @@ class SyncJob extends JobHook { public function run() { + if ($this->getSetting('apply_changes') === 'y') { + $this->syncRule()->applyChanges(); + } else{ + $this->syncRule()->checkForChanges(); + } } public static function getDescription(QuickForm $form) @@ -18,6 +24,59 @@ class SyncJob extends JobHook ); } + public static function addSettingsFormFields(QuickForm $form) + { + $rules = self::enumSyncRules($form); + + $form->addElement('select', 'rule_id', array( + 'label' => $form->translate('Synchronization rule'), + 'description' => $form->translate( + 'Please choose your synchronization rule that should be executed.' + . ' You could create different schedules for different rules or also' + . ' opt for running all of them at once.' + ), + 'required' => true, + 'class' => 'autosubmit', + 'multiOptions' => $rules + )); + + $form->addElement('select', 'apply_changes', array( + 'label' => $form->translate('Apply changes'), + 'description' => $form->translate( + 'You could immediately apply eventual changes or just learn about them.' + . ' In case you do not want them to be applied immediately, defining a' + . ' job still makes sense. You will be made aware of available changes' + . ' in your Director GUI.' + ), + 'value' => 'n', + 'multiOptions' => array( + 'y' => $form->translate('Yes'), + 'n' => $form->translate('No'), + ) + )); + + if (! strlen($form->getSentOrObjectValue('job_name'))) { + if (($ruleId = $form->getSentValue('rule_id')) && array_key_exists($ruleId, $rules)) { + $name = sprintf('Sync job: %s', $rules[$ruleId]); + $form->getElement('job_name')->setValue($name); + ///$form->getObject()->set('job_name', $name); + } + } + + return $form; + } + + protected static function enumSyncRules(QuickForm $form) + { + $db = $form->getDb(); + $query = $db->select()->from('sync_rule', array('id', 'rule_name'))->order('rule_name'); + $res = $db->fetchPairs($query); + return array( + null => $form->translate('- please choose -'), + '__ALL__' => $form->translate('Run all rules at once') + ) + $res; + } + public function isPending() { } From cf1e5d88a8ed18aba0630220cd13b84f320c1968 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 14:30:52 +0200 Subject: [PATCH 34/74] DirectorJob: fix settings, add state helpers --- library/Director/Objects/DirectorJob.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/library/Director/Objects/DirectorJob.php b/library/Director/Objects/DirectorJob.php index 289627f3..18c0373f 100644 --- a/library/Director/Objects/DirectorJob.php +++ b/library/Director/Objects/DirectorJob.php @@ -25,4 +25,24 @@ class DirectorJob extends DbObjectWithSettings ); protected $settingsTable = 'director_job_setting'; + + protected $settingsRemoteId = 'job_id'; + + public function isPending() + { + if ($this->ts_last_attempt === null) { + return true; + } + + if (strtotime($this->unixts_last_attempt) + $this->run_interval < time()) { + return true; + } + + return false; + } + + public function lastAttemptSucceeded() + { + return $this->last_attempt_succeeded === 'y'; + } } From 37d3c5aa045c86c7827322f29484d41c4a55316c Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 14:31:41 +0200 Subject: [PATCH 35/74] index: show and link jobs on dashboard --- application/controllers/IndexController.php | 29 ++++++++++++++++++++- application/views/scripts/index/index.phtml | 1 + 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/application/controllers/IndexController.php b/application/controllers/IndexController.php index 6a7c5770..7ee6f963 100644 --- a/application/controllers/IndexController.php +++ b/application/controllers/IndexController.php @@ -3,6 +3,7 @@ namespace Icinga\Module\Director\Controllers; use Exception; +use Icinga\Module\Director\Objects\DirectorJob; use Icinga\Module\Director\Objects\SyncRule; use Icinga\Module\Director\Web\Controller\ActionController; @@ -31,7 +32,8 @@ class IndexController extends ActionController 'label' => $this->translate('Overview') ))->activate('overview'); - $this->fetchSyncState(); + $this->fetchSyncState() + ->fetchJobState(); } } @@ -56,6 +58,31 @@ class IndexController extends ActionController } $this->view->syncState = $state; + + return $this; + } + + protected function fetchJobState() + { + $jobs = DirectorJob::loadAll($this->db()); + if (count($jobs) > 0) { + $state = 'ok'; + } else { + $state = null; + } + + foreach ($jobs as $job) { + if ($job->isPending()) { + $state = 'pending'; + } elseif (! $job->lastAttemptSucceeded()) { + $state = 'critical'; + break; + } + } + + $this->view->jobState = $state; + + return $this; } protected function hasDeploymentEndpoint() diff --git a/application/views/scripts/index/index.phtml b/application/views/scripts/index/index.phtml index afec2dd1..86d3892c 100644 --- a/application/views/scripts/index/index.phtml +++ b/application/views/scripts/index/index.phtml @@ -103,6 +103,7 @@ $all = array( $this->translate('Do more with your data') => array( array('database', $this->translate('Import data sources'), 'director/list/importsource', $this->translate('Define and manage imports from various data sources')), array('flapping', $this->translate('Synchronize'), 'director/list/syncrule', $this->translate('Define how imported data should be synchronized with Icinga'), $this->syncState), + array('clock', $this->translate('Jobs'), 'director/jobs', $this->translate('Schedule and automate Import, Syncronization, Config Deployment, Housekeeping and more'), $this->jobState), array('sort-name-up', $this->translate('Provide data lists'), 'director/data/lists', $this->translate('Provide data lists to make life easier for your users')), array('edit', $this->translate('Define data fields'), 'director/data/fields', $this->translate('Data fields make sure that configuration fits your rules')), ) From b9c36f6020fd554fd48687c5d56e1718de3daf0b Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 14:32:24 +0200 Subject: [PATCH 36/74] JobController: controller for job configuration --- application/controllers/JobController.php | 78 +++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 application/controllers/JobController.php diff --git a/application/controllers/JobController.php b/application/controllers/JobController.php new file mode 100644 index 00000000..0ac1ca55 --- /dev/null +++ b/application/controllers/JobController.php @@ -0,0 +1,78 @@ +indexAction(); + } + + public function editAction() + { + $this->indexAction(); + } + + public function runAction() + { + // TODO: Form, POST + $id = $this->params->get('id'); + $job = Job::load($id, $this->db()); + if ($job->run()) { + Notification::success('Job has successfully been completed'); + $this->redirectNow( + Url::fromPath( + 'director/job', + array('id' => $id) + ) + ); + } else { + Notification::success('Job run failed'); + } + } + + public function indexAction() + { + $form = $this->view->form = $this->loadForm('directorJob') + ->setSuccessUrl('director/job') + ->setDb($this->db()); + + if ($id = $this->params->get('id')) { + $this->prepareTabs($id)->activate('edit'); + $form->loadObject($id); + $this->view->title = sprintf( + $this->translate('Job %s'), + $form->getObject()->job_name + ); + } else { + $this->view->title = $this->translate('Add job'); + $this->prepareTabs()->activate('add'); + } + + $form->handleRequest(); + $this->setViewScript('object/form'); + } + + protected function prepareTabs($id = null) + { + if ($id) { + return $this->getTabs()->add('edit', array( + 'url' => 'director/job/edit', + 'urlParams' => array('id' => $id), + 'label' => $this->translate('Job'), + )); + } else { + return $this->getTabs()->add('add', array( + 'url' => 'director/job/add', + 'label' => $this->translate('Job'), + )); + } + } +} From c1ba91ece997155422a014da05020b29d6a0ed79 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 14:34:29 +0200 Subject: [PATCH 37/74] JobsController: autorefresh --- application/controllers/JobsController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/application/controllers/JobsController.php b/application/controllers/JobsController.php index 44577634..23ddd9c9 100644 --- a/application/controllers/JobsController.php +++ b/application/controllers/JobsController.php @@ -8,6 +8,7 @@ class JobsController extends ActionController { public function indexAction() { + $this->setAutoRefreshInterval(10); $this->view->title = $this->translate('Jobs'); $this->getTabs()->add('jobs', array( From d0e1ecb8d7b1366c0a890d96c121fee16e0bbd82 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 14:47:49 +0200 Subject: [PATCH 38/74] Sync: deletions are also modifications --- library/Director/Import/Sync.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/Director/Import/Sync.php b/library/Director/Import/Sync.php index 1a59cb0f..575429e3 100644 --- a/library/Director/Import/Sync.php +++ b/library/Director/Import/Sync.php @@ -105,6 +105,8 @@ class Sync foreach ($objects as $object) { if ($object->hasBeenModified()) { $modified[] = $object; + } elseif ($object instanceof IcingaObject && $object->shouldBeRemoved()) { + $modified[] = $object; } } From 5f165a59c7d2e2a662638f215163e183d5a87ccc Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 15:00:50 +0200 Subject: [PATCH 39/74] schema/mysql: add state columns for import sources --- schema/mysql-migrations/upgrade_95.sql | 22 ++++++++++++++++++++++ schema/mysql.sql | 10 +++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 schema/mysql-migrations/upgrade_95.sql diff --git a/schema/mysql-migrations/upgrade_95.sql b/schema/mysql-migrations/upgrade_95.sql new file mode 100644 index 00000000..aa49c5b2 --- /dev/null +++ b/schema/mysql-migrations/upgrade_95.sql @@ -0,0 +1,22 @@ +ALTER TABLE import_source + ADD COLUMN import_state ENUM( + 'unknown', + 'in-sync', + 'pending-changes', + 'failing' + ) NOT NULL DEFAULT 'unknown', + ADD COLUMN last_error_message TEXT DEFAULT NULL, + ADD COLUMN last_attempt DATETIME DEFAULT NULL +; + +UPDATE import_source s + JOIN ( + SELECT source_id, MAX(start_time) AS start_time + FROM import_run + GROUP BY source_id + ) ir ON s.id = ir.source_id + SET s.last_attempt = ir.start_time; + +INSERT INTO director_schema_migration + (schema_version, migration_time) + VALUES (95, NOW()); diff --git a/schema/mysql.sql b/schema/mysql.sql index e6663e70..69cf9efc 100644 --- a/schema/mysql.sql +++ b/schema/mysql.sql @@ -1107,6 +1107,14 @@ CREATE TABLE import_source ( source_name VARCHAR(64) NOT NULL, key_column VARCHAR(64) NOT NULL, provider_class VARCHAR(72) NOT NULL, + import_state ENUM( + 'unknown', + 'in-sync', + 'pending-changes', + 'failing' + ) NOT NULL DEFAULT 'unknown', + last_error_message TEXT DEFAULT NULL, + last_attempt DATETIME DEFAULT NULL, PRIMARY KEY (id), INDEX search_idx (key_column) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -1294,4 +1302,4 @@ CREATE TABLE sync_run ( INSERT INTO director_schema_migration SET migration_time = NOW(), - schema_version = 94; + schema_version = 95; From 31728783fc74fc2367df0402ab690370f5c25bac Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 15:26:47 +0200 Subject: [PATCH 40/74] ImportsourceTable: show import state --- application/tables/ImportsourceTable.php | 32 +++++++----------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/application/tables/ImportsourceTable.php b/application/tables/ImportsourceTable.php index 32e50bf2..54525111 100644 --- a/application/tables/ImportsourceTable.php +++ b/application/tables/ImportsourceTable.php @@ -9,8 +9,6 @@ use Exception; class ImportsourceTable extends QuickTable { - protected $revalidate = false; - protected $searchColumns = array( 'source_name', ); @@ -18,9 +16,11 @@ class ImportsourceTable extends QuickTable public function getColumns() { return array( - 'id' => 's.id', - 'source_name' => 's.source_name', - 'provider_class' => 's.provider_class', + 'id' => 's.id', + 'source_name' => 's.source_name', + 'provider_class' => 's.provider_class', + 'import_state' => 's.import_state', + 'last_error_message' => 's.last_error_message', ); } @@ -44,25 +44,11 @@ class ImportsourceTable extends QuickTable protected function getRowClasses($row) { - if (! $this->revalidate) { - return array(); - } - try { - $import = new Import(ImportSource::load($row->id, $this->connection())); - if ($import->providesChanges()) { - $row->source_name = sprintf( - '%s (%s)', - $row->source_name, - $this->view()->translate('has changes') - ); - return 'pending-changes'; - } else { - return 'in-sync'; - } - } catch (Exception $e) { - $row->source_name = $row->source_name . ' (' . $e->getMessage() . ')'; - return 'failing'; + if ($row->import_state === 'failing' && $row->last_error_message) { + $row->source_name .= ' (' . $row->last_error_message . ')'; } + + return $row->import_state; } public function getBaseQuery() From 4d1fe849d6dacf6b41affbad45ffa95ca84e9255 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 15:27:46 +0200 Subject: [PATCH 41/74] JobTable: remove superfluous header --- application/tables/JobTable.php | 1 - 1 file changed, 1 deletion(-) diff --git a/application/tables/JobTable.php b/application/tables/JobTable.php index d3d69c94..55f5014b 100644 --- a/application/tables/JobTable.php +++ b/application/tables/JobTable.php @@ -57,7 +57,6 @@ class JobTable extends QuickTable $view = $this->view(); return array( 'job_name' => $view->translate('Job name'), - 'object_type' => $view->translate('Object type'), ); } From a8904758f39ee8e071558dc60458a4fc0c8e1f73 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 15:31:02 +0200 Subject: [PATCH 42/74] ImportSource: refresh, provide helpers --- library/Director/Objects/ImportSource.php | 54 +++++++++++++++++++++-- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/library/Director/Objects/ImportSource.php b/library/Director/Objects/ImportSource.php index 8cdc12ca..cef74fac 100644 --- a/library/Director/Objects/ImportSource.php +++ b/library/Director/Objects/ImportSource.php @@ -2,7 +2,10 @@ namespace Icinga\Module\Director\Objects; +use Icinga\Application\Benchmark; use Icinga\Module\Director\Data\Db\DbObjectWithSettings; +use Icinga\Module\Director\Import\Import; +use Exception; class ImportSource extends DbObjectWithSettings { @@ -13,10 +16,13 @@ class ImportSource extends DbObjectWithSettings protected $autoincKeyName = 'id'; protected $defaultProperties = array( - 'id' => null, - 'source_name' => null, - 'provider_class' => null, - 'key_column' => null + 'id' => null, + 'source_name' => null, + 'provider_class' => null, + 'key_column' => null, + 'import_state' => null, + 'last_error_message' => null, + 'last_attempt' => null, ); protected $settingsTable = 'import_source_setting'; @@ -34,4 +40,44 @@ class ImportSource extends DbObjectWithSettings ->order('priority DESC') ); } + + public function checkForChanges($runImport = false) + { + $hadChanges = false; + + Benchmark::measure('Starting with import ' . $this->source_name); + try { + $import = new Import($this); + if ($import->providesChanges()) { + Benchmark::measure('Found changes for ' . $this->source_name); + $this->hadChanges = true; + $this->import_state = 'pending-changes'; + + if ($runImport && $import->run()) { + Benchmark::measure('Import succeeded for ' . $this->source_name); + $this->import_state = 'in-sync'; + } + } else { + $this->import_state = 'in-sync'; + } + + $this->last_error_message = null; + + } catch (Exception $e) { + $this->import_state = 'failing'; + Benchmark::measure('Import failed for ' . $this->source_name); + $this->last_error_message = 'ERR: ' . $e->getMessage(); + } + + if ($this->hasBeenModified()) { + $this->store(); + } + + return $hadChanges; + } + + public function runImport() + { + return $this->checkForChanges(true); + } } From 51f8591a43e446a225eaa8ab4d55b938220bba8b Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 15:51:38 +0200 Subject: [PATCH 43/74] index: show import state on dashboard --- application/controllers/IndexController.php | 27 +++++++++++++++++++++ application/views/scripts/index/index.phtml | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/application/controllers/IndexController.php b/application/controllers/IndexController.php index 7ee6f963..9e0ac311 100644 --- a/application/controllers/IndexController.php +++ b/application/controllers/IndexController.php @@ -4,6 +4,7 @@ namespace Icinga\Module\Director\Controllers; use Exception; use Icinga\Module\Director\Objects\DirectorJob; +use Icinga\Module\Director\Objects\ImportSource; use Icinga\Module\Director\Objects\SyncRule; use Icinga\Module\Director\Web\Controller\ActionController; @@ -33,6 +34,7 @@ class IndexController extends ActionController ))->activate('overview'); $this->fetchSyncState() + ->fetchImportState() ->fetchJobState(); } } @@ -62,6 +64,31 @@ class IndexController extends ActionController return $this; } + protected function fetchImportState() + { + $srcs = ImportSource::loadAll($this->db()); + if (count($srcs) > 0) { + $state = 'ok'; + } else { + $state = null; + } + + foreach ($srcs as $src) { + if ($src->import_state !== 'in-sync') { + if ($src->import_state === 'failing') { + $state = 'critical'; + break; + } else { + $state = 'warning'; + } + } + } + + $this->view->importState = $state; + + return $this; + } + protected function fetchJobState() { $jobs = DirectorJob::loadAll($this->db()); diff --git a/application/views/scripts/index/index.phtml b/application/views/scripts/index/index.phtml index 86d3892c..8b49452b 100644 --- a/application/views/scripts/index/index.phtml +++ b/application/views/scripts/index/index.phtml @@ -101,7 +101,7 @@ $all = array( array('globe', $this->translate('Zones'), 'director/zones', statSummary($this, 'zone')), ), $this->translate('Do more with your data') => array( - array('database', $this->translate('Import data sources'), 'director/list/importsource', $this->translate('Define and manage imports from various data sources')), + array('database', $this->translate('Import data sources'), 'director/list/importsource', $this->translate('Define and manage imports from various data sources'), $this->importState), array('flapping', $this->translate('Synchronize'), 'director/list/syncrule', $this->translate('Define how imported data should be synchronized with Icinga'), $this->syncState), array('clock', $this->translate('Jobs'), 'director/jobs', $this->translate('Schedule and automate Import, Syncronization, Config Deployment, Housekeeping and more'), $this->jobState), array('sort-name-up', $this->translate('Provide data lists'), 'director/data/lists', $this->translate('Provide data lists to make life easier for your users')), From bb6e3f58a5270e832ee8cdb0f7bb890b3bada522 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 17:03:49 +0200 Subject: [PATCH 44/74] IcingaHostTable: fix row count for filtered views fixes #11661 --- application/tables/IcingaHostTable.php | 1 + 1 file changed, 1 insertion(+) diff --git a/application/tables/IcingaHostTable.php b/application/tables/IcingaHostTable.php index 59cfb520..799d4ddf 100644 --- a/application/tables/IcingaHostTable.php +++ b/application/tables/IcingaHostTable.php @@ -73,6 +73,7 @@ class IcingaHostTable extends IcingaObjectTable $db = $this->connection()->getConnection(); $sub = clone($this->getBaseQuery()); $sub->columns($this->getColumns()); + $this->applyFiltersToQuery($sub); $query = $db->select()->from( array('sub' => $sub), 'COUNT(*)' From 4caf06f6895d74fc1bb7825fe86afeb8c4590bed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie?= Date: Fri, 22 Apr 2016 15:18:21 +0200 Subject: [PATCH 45/74] doc/installation: fix broken link Signed-off-by: Thomas Gelf --- doc/02-Installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/02-Installation.md b/doc/02-Installation.md index be4502d4..78d58878 100644 --- a/doc/02-Installation.md +++ b/doc/02-Installation.md @@ -42,7 +42,7 @@ Web-based Configuration The following steps should guide you through the web-based Kickstart wizard. In case you prefer automated configuration, you should check the dedicated -[documentation section](doc/03-Automation.md). +[documentation section](03-Automation.md). ### Create a Database resource From 4c44b46a829385426081928545bb68552ef1fdd1 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 17:42:38 +0200 Subject: [PATCH 46/74] data/listentry: fix redirect on save fixes #11503 --- application/controllers/DataController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/controllers/DataController.php b/application/controllers/DataController.php index 3223c951..8886305c 100644 --- a/application/controllers/DataController.php +++ b/application/controllers/DataController.php @@ -137,7 +137,7 @@ class DataController extends ActionController $listId = $list->id; $form = $this->view->form = $this->loadForm('directorDatalistentry') - ->setSuccessUrl('director/data/listentry') + ->setSuccessUrl('director/data/listentry?list_id=' . $listId) ->setList($list) ->setDb($this->db()); From 7760c0f62e315c970bbe2c889dd759093692359c Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 17:43:19 +0200 Subject: [PATCH 47/74] DatalistentryController: remove obsolete controller --- .../controllers/DatalistentryController.php | 66 ------------------- 1 file changed, 66 deletions(-) delete mode 100644 application/controllers/DatalistentryController.php diff --git a/application/controllers/DatalistentryController.php b/application/controllers/DatalistentryController.php deleted file mode 100644 index 2cf6c2be..00000000 --- a/application/controllers/DatalistentryController.php +++ /dev/null @@ -1,66 +0,0 @@ -indexAction(); - } - - public function editAction() - { - $this->indexAction(true); - } - - public function indexAction($edit = false) - { - $request = $this->getRequest(); - - $listId = $this->params->get('list_id'); - $this->view->lastId = $listId; - - if ($this->params->get('list_id') && $entryName = $this->params->get('entry_name')) { - $edit = true; - } - - if ($edit) { - $this->view->title = $this->translate('Edit entry'); - $this->getTabs()->add('editentry', array( - 'url' => 'director/datalistentry/edit' . '?list_id=' . $listId . '&entry_name=' . $entryName, - 'label' => $this->view->title, - ))->activate('editentry'); - } else { - $this->view->title = $this->translate('Add entry'); - $this->getTabs()->add('addlistentry', array( - 'url' => 'director/datalistentry/add' . '?list_id=' . $listId, - 'label' => $this->view->title, - ))->activate('addlistentry'); - } - - $form = $this->view->form = $this->loadForm('directorDatalistentry') - ->setListId($listId) - ->setSuccessUrl('director/datalistentry' . '?list_id=' . $listId) - ->setDb($this->db()); - - if ($request->isPost()) { - $listId = $request->getParam('list_id'); - $entryName = $request->getParam('entry_name'); - } - - if ($edit) { - $form->loadObject(array('list_id' => $listId, 'entry_name' => $entryName)); - if ($el = $form->getElement('entry_name')) { - // TODO: Doesn't work without setup - $el->setAttribs(array('readonly' => true)); - } - } - - $form->handleRequest(); - - $this->render('object/form', null, true); - } -} From 7118545c98386f08779fe6a7cb67a6249d228345 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Fri, 22 Apr 2016 18:15:33 +0200 Subject: [PATCH 48/74] IcingaConfig: make zone lookup and file public --- library/Director/IcingaConfig/IcingaConfig.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/library/Director/IcingaConfig/IcingaConfig.php b/library/Director/IcingaConfig/IcingaConfig.php index f4c126b7..48ae55a1 100644 --- a/library/Director/IcingaConfig/IcingaConfig.php +++ b/library/Director/IcingaConfig/IcingaConfig.php @@ -233,8 +233,14 @@ class IcingaConfig return $checksums; } - protected function getZoneName($id) + // TODO: prepare lookup cache if empty? + public function getZoneName($id) { + if (! array_key_exists($id, $this->zoneMap)) { + $zone = IcingaZone::loadWithAutoIncId($id, $this->connection); + $this->zoneMap[$id] = $zone->object_name; + } + return $this->zoneMap[$id]; } @@ -546,7 +552,7 @@ class IcingaConfig return in_array($type, $types); } - protected function configFile($name, $suffix = '.conf') + public function configFile($name, $suffix = '.conf') { $filename = $name . $suffix; if (! array_key_exists($filename, $this->files)) { From b6b15ce7e3175a544f480711c274c4039ebc1551 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Sat, 23 Apr 2016 14:08:25 +0200 Subject: [PATCH 49/74] IcingaCommand: prefer to be rendered to global --- library/Director/Objects/IcingaCommand.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/library/Director/Objects/IcingaCommand.php b/library/Director/Objects/IcingaCommand.php index 6020205d..1e48a3ab 100644 --- a/library/Director/Objects/IcingaCommand.php +++ b/library/Director/Objects/IcingaCommand.php @@ -2,6 +2,7 @@ namespace Icinga\Module\Director\Objects; +use Icinga\Module\Director\IcingaConfig\IcingaConfig; use Icinga\Module\Director\IcingaConfig\IcingaConfigHelper as c; class IcingaCommand extends IcingaObject @@ -74,6 +75,11 @@ class IcingaCommand extends IcingaObject return $value; } + public function getRenderingZone(IcingaConfig $config = null) + { + return $this->connection->getDefaultGlobalZoneName(); + } + protected function renderCommand() { $command = $this->command; From 8dda8a6a9a78f992be48b01472d5f3969e34de28 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Mon, 2 May 2016 10:21:22 +0200 Subject: [PATCH 50/74] index: allow to apply migrations from dashboard --- application/controllers/IndexController.php | 19 ++++++++++++++++--- application/views/scripts/index/index.phtml | 5 +++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/application/controllers/IndexController.php b/application/controllers/IndexController.php index 9e0ac311..34f06792 100644 --- a/application/controllers/IndexController.php +++ b/application/controllers/IndexController.php @@ -3,6 +3,7 @@ namespace Icinga\Module\Director\Controllers; use Exception; +use Icinga\Module\Director\Db\Migrations; use Icinga\Module\Director\Objects\DirectorJob; use Icinga\Module\Director\Objects\ImportSource; use Icinga\Module\Director\Objects\SyncRule; @@ -33,9 +34,21 @@ class IndexController extends ActionController 'label' => $this->translate('Overview') ))->activate('overview'); - $this->fetchSyncState() - ->fetchImportState() - ->fetchJobState(); + $migrations = new Migrations($this->db()); + + if ($migrations->hasPendingMigrations()) { + $this->view->migrationsForm = $this + ->loadForm('applyMigrations') + ->setMigrations($migrations) + ->handleRequest(); + } + + try { + $this->fetchSyncState() + ->fetchImportState() + ->fetchJobState(); + } catch (Exception $e) { + } } } diff --git a/application/views/scripts/index/index.phtml b/application/views/scripts/index/index.phtml index 8b49452b..30423a7e 100644 --- a/application/views/scripts/index/index.phtml +++ b/application/views/scripts/index/index.phtml @@ -84,6 +84,11 @@ if (!$this->hasDeploymentEndpoint) { echo $this->form; } +if ($this->migrationsForm) { + echo '

' . $this->translate('There are pending database schema migrations') . "

\n"; + echo $this->migrationsForm; +} + $all = array( $this->translate('Define whatever you want to be monitored') => array( array('host', $this->translate('Host objects'), 'director/hosts', statSummary($this, 'host')), From bd937e57c5d472e50628a524a04062b4bd727816 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Mon, 2 May 2016 10:22:22 +0200 Subject: [PATCH 51/74] IcingaZone: delegate rendering responsibility --- library/Director/Objects/IcingaZone.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/library/Director/Objects/IcingaZone.php b/library/Director/Objects/IcingaZone.php index ea05b8db..4fe0688b 100644 --- a/library/Director/Objects/IcingaZone.php +++ b/library/Director/Objects/IcingaZone.php @@ -2,6 +2,7 @@ namespace Icinga\Module\Director\Objects; +use Icinga\Module\Director\IcingaConfig\IcingaConfig; use Icinga\Module\Director\IcingaConfig\IcingaConfigHelper as c; class IcingaZone extends IcingaObject @@ -40,6 +41,21 @@ class IcingaZone extends IcingaObject return c::renderKeyValue('endpoints', c::renderArray($endpoints)); } + public function getRenderingZone(IcingaConfig $config = null) + { + // If the zone has a parent zone... + if ($this->get('parent_id')) { + // ...we render the zone object to the parent zone + return $this->parent; + } elseif ($this->is_global === 'y') { + // ...additional global zones are rendered to our global zone... + return $this->connection->getDefaultGlobalZoneName(); + } else { + // ...and all the other zones are rendered to our master zone + return $this->connection->getMasterZoneName(); + } + } + public function setEndpointList($list) { $this->endpointList = $list; From 1158409eebe4ddcc41f6478c6cdaf2ad8663c0be Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Mon, 2 May 2016 10:23:32 +0200 Subject: [PATCH 52/74] IcingaObject: fix rendering target and tests --- library/Director/Objects/IcingaObject.php | 3 +-- test/php/library/Director/Objects/IcingaHostTest.php | 8 ++++---- test/php/library/Director/Objects/IcingaServiceTest.php | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/library/Director/Objects/IcingaObject.php b/library/Director/Objects/IcingaObject.php index 31d5eeea..aedc825d 100644 --- a/library/Director/Objects/IcingaObject.php +++ b/library/Director/Objects/IcingaObject.php @@ -122,7 +122,6 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer return $this; } - private function loadMultiRelation($property) { if ($this->hasBeenLoadedFromDb()) { @@ -1129,7 +1128,7 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer } $config->configFile( - 'zones.d/' . $this->getRenderingZone($config) + 'zones.d/' . $this->getRenderingZone($config) . '/' . $filename )->addObject($this); } diff --git a/test/php/library/Director/Objects/IcingaHostTest.php b/test/php/library/Director/Objects/IcingaHostTest.php index c7ad2e36..c487bee6 100644 --- a/test/php/library/Director/Objects/IcingaHostTest.php +++ b/test/php/library/Director/Objects/IcingaHostTest.php @@ -284,7 +284,7 @@ class IcingaHostTest extends BaseTestCase $config = new IcingaConfig($db); $host->renderToConfig($config); $this->assertEquals( - array('zones.d/master.conf'), + array('zones.d/master/hosts.conf'), $config->getFileNames() ); @@ -295,7 +295,7 @@ class IcingaHostTest extends BaseTestCase $host->zone = '___TEST___zone'; $host->renderToConfig($config); $this->assertEquals( - array('zones.d/___TEST___zone.conf'), + array('zones.d/___TEST___zone/hosts.conf'), $config->getFileNames() ); @@ -306,7 +306,7 @@ class IcingaHostTest extends BaseTestCase $config = new IcingaConfig($db); $host->renderToConfig($config); $this->assertEquals( - array('zones.d/___TEST___zone.conf'), + array('zones.d/___TEST___zone/hosts.conf'), $config->getFileNames() ); @@ -316,7 +316,7 @@ class IcingaHostTest extends BaseTestCase $config = new IcingaConfig($db); $host->renderToConfig($config); $this->assertEquals( - array('zones.d/director-global.conf'), + array('zones.d/director-global/host_templates.conf'), $config->getFileNames() ); diff --git a/test/php/library/Director/Objects/IcingaServiceTest.php b/test/php/library/Director/Objects/IcingaServiceTest.php index d4aa8151..fdfcb3df 100644 --- a/test/php/library/Director/Objects/IcingaServiceTest.php +++ b/test/php/library/Director/Objects/IcingaServiceTest.php @@ -204,7 +204,7 @@ class IcingaServiceTest extends BaseTestCase $config = new IcingaConfig($db); $service->renderToConfig($config); $this->assertEquals( - array('zones.d/master.conf'), + array('zones.d/master/services.conf'), $config->getFileNames() ); } From 6708df8a610a986c275c0e74604d21485f2691c3 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Mon, 2 May 2016 10:24:32 +0200 Subject: [PATCH 53/74] DbObject: allow to statically clearPrefetchCache --- library/Director/Data/Db/DbObject.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/library/Director/Data/Db/DbObject.php b/library/Director/Data/Db/DbObject.php index 480e04b2..4406af26 100644 --- a/library/Director/Data/Db/DbObject.php +++ b/library/Director/Data/Db/DbObject.php @@ -1062,6 +1062,18 @@ abstract class DbObject return self::$prefetched[$class]; } + public static function clearPrefetchCache() + { + $class = get_called_class(); + if (! array_key_exists($class, self::$prefetched)) { + return false; + } + + unset(self::$prefetched[$class]); + unset(self::$prefetchedNames[$class]); + unset(self::$prefetchStats[$class]); + } + public static function exists($id, DbConnection $connection) { if (static::getPrefetched($id)) { From f7bd50838dae9fe691d16de8726322e27e9b3088 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Mon, 2 May 2016 10:26:41 +0200 Subject: [PATCH 54/74] Objects: delegate rendering zone for groups, users --- library/Director/Objects/IcingaObjectGroup.php | 7 +++++++ library/Director/Objects/IcingaUser.php | 7 +++++++ library/Director/Objects/IcingaUserGroup.php | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/library/Director/Objects/IcingaObjectGroup.php b/library/Director/Objects/IcingaObjectGroup.php index cb667b51..ff840876 100644 --- a/library/Director/Objects/IcingaObjectGroup.php +++ b/library/Director/Objects/IcingaObjectGroup.php @@ -2,6 +2,8 @@ namespace Icinga\Module\Director\Objects; +use Icinga\Module\Director\IcingaConfig\IcingaConfig; + abstract class IcingaObjectGroup extends IcingaObject { protected $supportsImports = true; @@ -13,4 +15,9 @@ abstract class IcingaObjectGroup extends IcingaObject 'disabled' => 'n', 'display_name' => null, ); + + public function getRenderingZone(IcingaConfig $config = null) + { + return $this->connection->getDefaultGlobalZoneName(); + } } diff --git a/library/Director/Objects/IcingaUser.php b/library/Director/Objects/IcingaUser.php index c50a2312..a344093e 100644 --- a/library/Director/Objects/IcingaUser.php +++ b/library/Director/Objects/IcingaUser.php @@ -2,6 +2,8 @@ namespace Icinga\Module\Director\Objects; +use Icinga\Module\Director\IcingaConfig\IcingaConfig; + class IcingaUser extends IcingaObject { protected $table = 'icinga_user'; @@ -40,4 +42,9 @@ class IcingaUser extends IcingaObject 'period' => 'IcingaTimePeriod', 'zone' => 'IcingaZone', ); + + public function getRenderingZone(IcingaConfig $config = null) + { + return $this->connection->getMasterZoneName(); + } } diff --git a/library/Director/Objects/IcingaUserGroup.php b/library/Director/Objects/IcingaUserGroup.php index b5386149..b85c312c 100644 --- a/library/Director/Objects/IcingaUserGroup.php +++ b/library/Director/Objects/IcingaUserGroup.php @@ -2,7 +2,14 @@ namespace Icinga\Module\Director\Objects; +use Icinga\Module\Director\IcingaConfig\IcingaConfig; + class IcingaUserGroup extends IcingaObjectGroup { protected $table = 'icinga_usergroup'; + + public function getRenderingZone(IcingaConfig $config = null) + { + return $this->connection->getMasterZoneName(); + } } From 7279fd7c40412a165dad8fec5878775b67162d2c Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Mon, 2 May 2016 10:30:01 +0200 Subject: [PATCH 55/74] views/scripts, css: fix web2.3.x issues, improve... ...responsiveness for dashboard and dashlets --- .../views/scripts/command/arguments.phtml | 2 +- application/views/scripts/config/file.phtml | 2 +- application/views/scripts/config/files.phtml | 2 +- application/views/scripts/host/services.phtml | 2 +- .../views/scripts/list/importrun.phtml | 2 +- application/views/scripts/list/table.phtml | 2 +- application/views/scripts/object/fields.phtml | 2 +- application/views/scripts/object/form.phtml | 2 +- application/views/scripts/object/show.phtml | 2 +- application/views/scripts/objects/table.phtml | 2 +- .../views/scripts/syncrule/history.phtml | 2 +- public/css/module.less | 21 ++++++++++++------- 12 files changed, 25 insertions(+), 18 deletions(-) diff --git a/application/views/scripts/command/arguments.phtml b/application/views/scripts/command/arguments.phtml index e5a04589..693053d6 100644 --- a/application/views/scripts/command/arguments.phtml +++ b/application/views/scripts/command/arguments.phtml @@ -1,7 +1,7 @@
tabs ?>

escape($this->title) ?>

- + addLink ?>
diff --git a/application/views/scripts/config/file.phtml b/application/views/scripts/config/file.phtml index 468bf95c..586ba879 100644 --- a/application/views/scripts/config/file.phtml +++ b/application/views/scripts/config/file.phtml @@ -1,7 +1,7 @@
tabs ?>

- + addLink ?>
diff --git a/application/views/scripts/config/files.phtml b/application/views/scripts/config/files.phtml index d9663b8a..db7dab97 100644 --- a/application/views/scripts/config/files.phtml +++ b/application/views/scripts/config/files.phtml @@ -1,7 +1,7 @@
tabs ?>

escape($this->title) ?>

- + addLink ?> filterEditor->getFilter()->isEmpty()): ?> diff --git a/application/views/scripts/host/services.phtml b/application/views/scripts/host/services.phtml index 83e1115c..2109b7b5 100644 --- a/application/views/scripts/host/services.phtml +++ b/application/views/scripts/host/services.phtml @@ -1,7 +1,7 @@
tabs ?>

escape($this->title) ?>

- + addLink ?> filterEditor && ! $this->filterEditor->getFilter()->isEmpty())): ?> diff --git a/application/views/scripts/list/importrun.phtml b/application/views/scripts/list/importrun.phtml index b58bdd81..ce1db762 100644 --- a/application/views/scripts/list/importrun.phtml +++ b/application/views/scripts/list/importrun.phtml @@ -27,7 +27,7 @@ $pt = $loc['thousands_sep']; - + addLink ?>
table->getPaginator() ?> diff --git a/application/views/scripts/list/table.phtml b/application/views/scripts/list/table.phtml index e444b7ab..518c1a78 100644 --- a/application/views/scripts/list/table.phtml +++ b/application/views/scripts/list/table.phtml @@ -1,7 +1,7 @@
tabs ?>

escape($this->title) ?>

-stayHere): ?> data-base-target="_next"> +stayHere): ?> data-base-target="_next"> addLink ?> filterEditor ?> diff --git a/application/views/scripts/object/fields.phtml b/application/views/scripts/object/fields.phtml index 64bd111b..22ae8a72 100644 --- a/application/views/scripts/object/fields.phtml +++ b/application/views/scripts/object/fields.phtml @@ -1,7 +1,7 @@
tabs ?>

escape($this->title) ?>

- + actionLinks ?>
diff --git a/application/views/scripts/object/form.phtml b/application/views/scripts/object/form.phtml index e697c04e..004f2dac 100644 --- a/application/views/scripts/object/form.phtml +++ b/application/views/scripts/object/form.phtml @@ -1,7 +1,7 @@
tabs ?>

escape($this->title) ?>

- + actionLinks ?> render('object/deploymentLink.phtml') ?> diff --git a/application/views/scripts/object/show.phtml b/application/views/scripts/object/show.phtml index 66fac28c..fd717243 100644 --- a/application/views/scripts/object/show.phtml +++ b/application/views/scripts/object/show.phtml @@ -1,7 +1,7 @@
tabs ?>

escape($this->title) ?>

- + actionLinks ?>
diff --git a/application/views/scripts/objects/table.phtml b/application/views/scripts/objects/table.phtml index 549f4605..e29bb64c 100644 --- a/application/views/scripts/objects/table.phtml +++ b/application/views/scripts/objects/table.phtml @@ -3,7 +3,7 @@ tabs ?>

escape($this->title) ?>quickSearch ?>

-stayHere): ?> data-base-target="_next"> +stayHere): ?> data-base-target="_next"> addLink ?> filterEditor): ?> diff --git a/application/views/scripts/syncrule/history.phtml b/application/views/scripts/syncrule/history.phtml index a225d77c..9994dd04 100644 --- a/application/views/scripts/syncrule/history.phtml +++ b/application/views/scripts/syncrule/history.phtml @@ -1,7 +1,7 @@
tabs ?>

escape($this->title) ?>

-stayHere): ?> data-base-target="_next"> +stayHere): ?> data-base-target="_next"> addLink ?> filterEditor ?> diff --git a/public/css/module.less b/public/css/module.less index 4972edf8..734d6021 100644 --- a/public/css/module.less +++ b/public/css/module.less @@ -34,9 +34,11 @@ span.disabled { color: @gray-light; } -.controls span a { - color: @icinga-blue; - margin-right: 1em; +.controls span.action-links { + a { + color: @icinga-blue; + margin-right: 1em; + } } pre.disabled { @@ -404,9 +406,15 @@ a:hover::before { text-decoration: none; } +h1 { + min-width: 27em; +} + ul.main-actions { margin: 0; padding: 0; + min-width: 36em; + li { list-style-type: none; @@ -415,7 +423,6 @@ ul.main-actions { padding: 0; clear: both; width: 19em; - min-width: 16em; vertical-align: top; a { @@ -450,7 +457,6 @@ ul.main-actions { } padding: 1em; - font-size: 1.1em; color: #666; font-weight: bold; display: block; @@ -477,8 +483,9 @@ ul.main-actions { #layout.poor-layout ul.main-actions { li { a { height: 12em; } + width: 18em; > a > i { - font-size: 2.4em; + font-size: 2em; } } } @@ -487,7 +494,7 @@ ul.main-actions { #layout.twocols ul.main-actions { li { a { height: 12em; } - width: 16em; + width: 17em; > a > i { font-size: 1.8em; } From 2ab802dcdb472a475018b24f33e69af977978c2b Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Mon, 2 May 2016 10:31:31 +0200 Subject: [PATCH 56/74] ApplyMigrationsForm: new form, just a button --- application/forms/ApplyMigrationsForm.php | 37 +++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 application/forms/ApplyMigrationsForm.php diff --git a/application/forms/ApplyMigrationsForm.php b/application/forms/ApplyMigrationsForm.php new file mode 100644 index 00000000..c1d954e4 --- /dev/null +++ b/application/forms/ApplyMigrationsForm.php @@ -0,0 +1,37 @@ +setSubmitLabel($this->translate('Apply schema migrations')); + } + + public function onSuccess() + { + try { + $this->setSuccessMessage($this->translate( + 'Pending database schema migrations have successfully been applied' + )); + + $this->migrations->applyPendingMigrations(); + parent::onSuccess(); + } catch (Exception $e) { + $this->addError($e->getMessage()); + } + } + + public function setMigrations(Migrations $migrations) + { + $this->migrations = $migrations; + return $this; + } +} From cfaa546c50768ec91d2588c18acbce07c00ed4c4 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Mon, 2 May 2016 10:42:53 +0200 Subject: [PATCH 57/74] config/diff: add full config diff capability --- application/controllers/ConfigController.php | 51 ++++++ application/tables/ConfigFileDiffTable.php | 151 ++++++++++++++++++ application/views/scripts/config/diff.phtml | 31 ++++ .../views/scripts/config/filediff.phtml | 11 ++ public/css/module.less | 44 +++++ 5 files changed, 288 insertions(+) create mode 100644 application/tables/ConfigFileDiffTable.php create mode 100644 application/views/scripts/config/diff.phtml create mode 100644 application/views/scripts/config/filediff.phtml diff --git a/application/controllers/ConfigController.php b/application/controllers/ConfigController.php index 7ee73a0d..f55f9294 100644 --- a/application/controllers/ConfigController.php +++ b/application/controllers/ConfigController.php @@ -2,6 +2,7 @@ namespace Icinga\Module\Director\Controllers; +use Icinga\Module\Director\ConfigDiff; use Icinga\Module\Director\IcingaConfig\IcingaConfig; use Icinga\Module\Director\Util; use Icinga\Module\Director\Web\Controller\ActionController; @@ -173,6 +174,56 @@ class ConfigController extends ActionController ); } + public function diffAction() + { + $db = $this->db(); + $this->view->title = $this->translate('Config diff'); + + $tabs = $this->getTabs()->add('diff', array( + 'label' => $this->translate('Config diff'), + 'url' => $this->getRequest()->getUrl() + ))->activate('diff'); + + $leftSum = $this->view->leftSum = $this->params->get('left'); + $rightSum = $this->view->rightSum = $this->params->get('right'); + $left = IcingaConfig::load(Util::hex2binary($leftSum), $db); + + $this->view->configs = $db->enumDeployedConfigs(); + if ($rightSum === null) { + return; + } + + $right = IcingaConfig::load(Util::hex2binary($rightSum), $db); + $this->view->table = $this + ->loadTable('ConfigFileDiff') + ->setConnection($this->db()) + ->setLeftChecksum($leftSum) + ->setRightChecksum($rightSum); + } + + public function filediffAction() + { + $db = $this->db(); + $leftSum = $this->params->get('left'); + $rightSum = $this->params->get('right'); + $filename = $this->view->filename = $this->params->get('file_path'); + + $left = IcingaConfig::load(Util::hex2binary($leftSum), $db); + $right = IcingaConfig::load(Util::hex2binary($rightSum), $db); + + $leftFile = $left->getFile($filename); + $rightFile = $right->getFile($filename); + + $d = ConfigDiff::create($leftFile, $rightFile); + + $this->view->title = sprintf( + $this->translate('Config file "%s"'), + $filename + ); + + $this->view->output = $d->renderHtml(); + } + protected function overviewTabs() { $this->view->tabs = $this->getTabs()->add( diff --git a/application/tables/ConfigFileDiffTable.php b/application/tables/ConfigFileDiffTable.php new file mode 100644 index 00000000..347c9967 --- /dev/null +++ b/application/tables/ConfigFileDiffTable.php @@ -0,0 +1,151 @@ +file_action; + } + + protected function getActionUrl($row) + { + $params = array('file_path' => $row->file_path); + + if ($row->file_checksum_left === $row->file_checksum_right) { + $params['config_checksum'] = $row->config_checksum_right; + } elseif ($row->file_checksum_left === null) { + $params['config_checksum'] = $row->config_checksum_right; + } elseif ($row->file_checksum_right === null) { + $params['config_checksum'] = $row->config_checksum_left; + } else { + $params['left'] = $row->config_checksum_left; + $params['right'] = $row->config_checksum_right; + return $this->url('director/config/filediff', $params); + } + + return $this->url('director/config/file', $params); + } + + public function setLeftChecksum($checksum) + { + $this->leftChecksum = $checksum; + return $this; + } + + public function setRightChecksum($checksum) + { + $this->rightChecksum = $checksum; + return $this; + } + + public function getTitles() + { + $view = $this->view(); + return array( + 'file_action' => $view->translate('Action'), + 'file_path' => $view->translate('File'), + ); + } + + public function count() + { + $db = $this->connection()->getConnection(); + $query = clone($this->getBaseQuery()); + $query->reset('order'); + $this->applyFiltersToQuery($query); + return $db->fetchOne($db->select()->from( + array('cntsub' => $query), + array('cnt' => 'COUNT(*)') + )); + } + + public function fetchData() + { + $db = $this->connection()->getConnection(); + $query = $this->getBaseQuery(); + + if ($this->hasLimit() || $this->hasOffset()) { + $query->limit($this->getLimit(), $this->getOffset()); + } + + $this->applyFiltersToQuery($query); + + return $db->fetchAll($query); + } + + public function getBaseQuery() + { + $conn = $this->connection(); + $db = $conn->getConnection(); + + $left = $db->select() + ->from( + array('cfl' => 'director_generated_config_file'), + array( + 'file_path' => 'COALESCE(cfl.file_path, cfr.file_path)', + 'config_checksum_left' => $conn->dbHexFunc('cfl.config_checksum'), + 'config_checksum_right' => $conn->dbHexFunc('cfr.config_checksum'), + 'file_checksum_left' => $conn->dbHexFunc('cfl.file_checksum'), + 'file_checksum_right' => $conn->dbHexFunc('cfr.file_checksum'), + 'file_action' => '(CASE WHEN cfr.config_checksum IS NULL' + . " THEN 'removed' WHEN cfl.file_checksum = cfr.file_checksum" + . " THEN 'unmodified' ELSE 'modified' END)", + ) + )->joinLeft( + array('cfr' => 'director_generated_config_file'), + $db->quoteInto( + 'cfl.file_path = cfr.file_path AND cfr.config_checksum = ?', + $conn->quoteBinary(Util::hex2binary($this->rightChecksum)) + ), + array() + )->where( + 'cfl.config_checksum = ?', + $conn->quoteBinary(Util::hex2binary($this->leftChecksum)) + ); + + $right = $db->select() + ->from( + array('cfl' => 'director_generated_config_file'), + array( + 'file_path' => 'COALESCE(cfr.file_path, cfl.file_path)', + 'config_checksum_left' => $conn->dbHexFunc('cfl.config_checksum'), + 'config_checksum_right' => $conn->dbHexFunc('cfr.config_checksum'), + 'file_checksum_left' => $conn->dbHexFunc('cfl.file_checksum'), + 'file_checksum_right' => $conn->dbHexFunc('cfr.file_checksum'), + 'file_action' => "('created')", + ) + )->joinRight( + array('cfr' => 'director_generated_config_file'), + $db->quoteInto( + 'cfl.file_path = cfr.file_path AND cfl.config_checksum = ?', + $conn->quoteBinary(Util::hex2binary($this->leftChecksum)) + ), + array() + )->where( + 'cfr.config_checksum = ?', + $conn->quoteBinary(Util::hex2binary($this->rightChecksum)) + )->where('cfl.file_checksum IS NULL'); + + return $db->select()->union(array($left, $right))->order('file_path'); + } +} diff --git a/application/views/scripts/config/diff.phtml b/application/views/scripts/config/diff.phtml new file mode 100644 index 00000000..d8bf64c5 --- /dev/null +++ b/application/views/scripts/config/diff.phtml @@ -0,0 +1,31 @@ +
+tabs ?> +

escape($this->title) ?>

+ +addLink ?> + +
+formSelect( + 'left', + $this->leftSum, + array('class' => 'autosubmit'), + array(null => $this->translate('- please choose -')) + $this->configs +) +?> +formSelect( + 'right', + $this->rightSum, + array('class' => 'autosubmit'), + array(null => $this->translate('- please choose -')) + $this->configs +) +?> +
+
+ +
+table)): ?> +
+table->render() ?> +
+ +
diff --git a/application/views/scripts/config/filediff.phtml b/application/views/scripts/config/filediff.phtml new file mode 100644 index 00000000..14e52d73 --- /dev/null +++ b/application/views/scripts/config/filediff.phtml @@ -0,0 +1,11 @@ +
+tabs ?> +

escape($this->title) ?>

+ +addLink ?> + +
+ +
+output ?> +
diff --git a/public/css/module.less b/public/css/module.less index 734d6021..c7c7663d 100644 --- a/public/css/module.less +++ b/public/css/module.less @@ -975,6 +975,50 @@ table.activity-log { } } +table.config-diff { + + tr th:first-child { + padding-left: 2em; + } + + tr td:first-child { + padding-left: 2em; + &::before { + font-family: 'ifont'; + // icon-help: + content: '\e85b'; + float: left; + font-weight: bold; + margin-left: -1.5em; + line-height: 1.5em; + } + } + + tr.file-unmodified td:first-child::before { + // icon-ok + color: @color-ok; + content: '\e803'; + } + + tr.file-created td:first-child::before { + // icon-plus + color: @color-pending; + content: '\e805'; + } + + tr.file-removed td:first-child::before { + // icon-cancel + color: @color-critical; + content: '\e804'; + } + + tr.file-modified td:first-child::before { + // icon-flapping + color: @color-warning; + content: '\e85d'; + } +} + .tree li a { display: inline-block; padding-left: 2.4em; From b155082454d581b6961afc048085b20d8e0ec271 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Mon, 2 May 2016 10:43:41 +0200 Subject: [PATCH 58/74] doc/rest-api: use https urls in examples --- doc/70-REST-API.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/70-REST-API.md b/doc/70-REST-API.md index d63166d7..97b22ef5 100644 --- a/doc/70-REST-API.md +++ b/doc/70-REST-API.md @@ -79,12 +79,12 @@ PASSWORD="***" test -z "$PASSWORD" || USERNAME="$USERNAME:$PASSWORD" test -z "$BODY" && curl -u "$USERNAME" \ - -i http://icingaweb/icingaweb/$URL \ + -i https://icingaweb/icingaweb/$URL \ -H 'Accept: application/json' \ -X $METHOD test -z "$BODY" || curl -u "$USERNAME" \ - -i http://icingaweb/icingaweb/$URL \ + -i https://icingaweb/icingaweb/$URL \ -H 'Accept: application/json' \ -X $METHOD \ -d "$BODY" From 2209607016ce616f66996196885b1a13492a27b5 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Mon, 2 May 2016 10:44:12 +0200 Subject: [PATCH 59/74] ImportSource: set a default state, according to db --- library/Director/Objects/ImportSource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Director/Objects/ImportSource.php b/library/Director/Objects/ImportSource.php index cef74fac..82e99f78 100644 --- a/library/Director/Objects/ImportSource.php +++ b/library/Director/Objects/ImportSource.php @@ -20,7 +20,7 @@ class ImportSource extends DbObjectWithSettings 'source_name' => null, 'provider_class' => null, 'key_column' => null, - 'import_state' => null, + 'import_state' => 'unknown', 'last_error_message' => null, 'last_attempt' => null, ); From d5e021658c52f0514e8e3439d85fcfcf3e50ef4d Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Mon, 2 May 2016 10:45:04 +0200 Subject: [PATCH 60/74] Db: provide enumDeployedConfigs --- library/Director/Db.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/library/Director/Db.php b/library/Director/Db.php index 4d099d02..5217d467 100644 --- a/library/Director/Db.php +++ b/library/Director/Db.php @@ -789,6 +789,32 @@ class Db extends DbConnection return $binary; } + public function enumDeployedConfigs() + { + $db = $this->db(); + + $columns = array( + 'checksum' => $this->dbHexFunc('c.checksum'), + ); + + if ($this->isPgsql()) { + $columns['caption'] = 'SUBSTRING(' . $columns['checksum'] . ' FROM 1 FOR 7)'; + } else { + $columns['caption'] = 'SUBSTRING(' . $columns['checksum'] . ', 1, 7)'; + } + + $query = $db->select()->from( + array('l' => 'director_deployment_log'), + $columns + )->joinLeft( + array('c' => 'director_generated_config'), + 'c.checksum = l.config_checksum', + array() + )->order('l.start_time DESC'); + + return $db->fetchPairs($query); + } + public function getUncollectedDeployments() { $db = $this->db(); From 63274afba30de7f66b603b59dc52a868468b0dc1 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Mon, 2 May 2016 10:49:07 +0200 Subject: [PATCH 61/74] IcingaAssignServiceToHostForm: remove obsolete form --- .../forms/IcingaAssignServiceToHostForm.php | 140 ------------------ 1 file changed, 140 deletions(-) delete mode 100644 application/forms/IcingaAssignServiceToHostForm.php diff --git a/application/forms/IcingaAssignServiceToHostForm.php b/application/forms/IcingaAssignServiceToHostForm.php deleted file mode 100644 index 3c63a499..00000000 --- a/application/forms/IcingaAssignServiceToHostForm.php +++ /dev/null @@ -1,140 +0,0 @@ -db = $db; - return $this; - } - - public function setIcingaObject($object) - { - $this->icingaObject = $object; -// $this->className = get_class($object) . 'Field'; - return $this; - } - - public function setup() - { - $this->addHidden('service_id', $this->icingaObject->id); - - if ($this->icingaObject->isTemplate()) { - $this->addHtmlHint( - 'Assign all services importing this service template to one or' - . ' more hosts' - ); - } else { - $this->addHtmlHint( - 'Assign this service to one or more hosts' - ); - } - - $this->addElement('select', 'object_type', array( - 'label' => 'Assign', - 'required' => true, - 'multiOptions' => $this->optionalEnum( - array( - 'host_group' => $this->translate('to a host group'), - 'host_property' => $this->translate('by host property'), - 'host_group_property' => $this->translate('by host group property'), - ) - ), - 'class' => 'autosubmit' - - )); - - switch ($this->getSentValue('object_type')) { - case 'host_group': - $this->addHostGroupElements(); - break; - case 'host_property': - $this->addHostPropertyElements(); - break; - case 'host_property': - $this->addHostFilterElements(); - break; - } - - $this->setSubmitLabel( - $this->translate('Assign') - ); - } - - protected function addHostGroupElements() - { - $this->addElement('select', 'host_id', array( - 'label' => 'Hostgroup', - 'required' => true, - 'multiOptions' => $this->optionalEnum($this->db->enumHostgroups()) - )); - } - - protected function addHostPropertyElements() - { - $this->addElement('select', 'host_property', array( - 'label' => 'Host property', - 'required' => true, - 'multiOptions' => $this->optionalEnum(IcingaHost::enumProperties($this->db)) - )); - $this->addElement('text', 'filter_expression', array( - 'label' => 'Filter expression', - 'required' => true, - )); - } - - protected function addHostFilterElements() - { - $this->addElement('text', 'host_filter', array( - 'label' => 'Host filter string', - 'required' => true, - )); - } - - public function onSuccess() - { - switch ($this->getValue('object_type')) { - case 'host_group': - $this->db->insert('icinga_service_assignment', array( - 'service_id' => $this->getValue('service_id'), - // TODO: in? - 'filter_string' => 'groups=' . $this->getValue('host_group'), - )); - break; - case 'host_property': - $this->db->insert('icinga_service_assignment', array( - 'service_id' => $this->getValue('service_id'), - 'filter_string' => sprintf( - 'host.%s=%s', - $this->getValue('host_property'), - c::renderString($this->getValue('filter_expression')) - ) - )); - break; - case 'host_filter': - $this->db->insert('icinga_service_assignment', array( - 'service_id' => $this->getValue('service_id'), - 'filter_string' => $this->getValue('filter_string'), - )); - break; - } - } -} From 8d0d747e1901d7e645c0a679387410cc2bd4ecef Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Mon, 2 May 2016 10:58:05 +0200 Subject: [PATCH 62/74] doc: re-structure getting started --- doc/03-Automation.md | 2 +- doc/04-Getting-started.md | 36 ++++++++++++++++++++++++++---------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/doc/03-Automation.md b/doc/03-Automation.md index f6579598..d212246f 100644 --- a/doc/03-Automation.md +++ b/doc/03-Automation.md @@ -1,4 +1,4 @@ -Automation - Configuration management +Automation - Configuration management ===================================== Director has been designed to work in distributed environments. In case diff --git a/doc/04-Getting-started.md b/doc/04-Getting-started.md index 20b7ba3a..9e83fd73 100644 --- a/doc/04-Getting-started.md +++ b/doc/04-Getting-started.md @@ -1,5 +1,13 @@ -Preparing your Icinga 2 environment for the Director -==================================================== +Getting started +=============== + +When new to the Director please make your first steps with a naked Icinga +environment. Director is not allowed to modify existing configuration in +`/etc/icinga2`. And while importing existing config is possible (happens for +example automagically at kickstart time), it is a pretty advanced task you +should not tackle at the early beginning. + + Create an API user ------------------ @@ -21,14 +29,22 @@ checking your clients, you will have to create them. The easiest way to set up Icinga 2 with a `zone` and `endpoint` is by running the [Icinga 2 Setup Wizard](http://docs.icinga.org/icinga2/latest/doc/module/icinga2/chapter/icinga2-client#icinga2-client-installation-master-setup). -Start with a new, empty Icinga setup. Director is not allowed to modify -existing configuration in `/etc/icinga2`, and while importing existing -config is possible (happens for example automagically at kickstart time) -this is an advanced task you should not tackle at the early beginning. - Take some time to really understand how to work with Icinga Director first. -Working with Agents and Config Zones -==================================== -Hint: Large: max packet size +Other topics that might interest you +------------------------------------ + +* [Working with agents](24-Working-with-agents.md) +* [Undstanding how Icinga Director works](10-How-it-works.md) + +What you should not try to start with +------------------------------------- + +Director has not bee built to help you with managing existing hand-crafted +configuration in /etc/icinga2. There are cases where it absolutely would +make sense to combine the Director with manual configuration. You can also +use multiple tools owning separare config packages. But these are pretty +advanced topics. + + From eee2b2e936700fbcaf6bd99b741d7ce0cc8c849b Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Mon, 2 May 2016 10:59:28 +0200 Subject: [PATCH 63/74] doc/04-Getting-started: fix typo --- doc/04-Getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/04-Getting-started.md b/doc/04-Getting-started.md index 9e83fd73..6293421e 100644 --- a/doc/04-Getting-started.md +++ b/doc/04-Getting-started.md @@ -41,7 +41,7 @@ Other topics that might interest you What you should not try to start with ------------------------------------- -Director has not bee built to help you with managing existing hand-crafted +Director has not been built to help you with managing existing hand-crafted configuration in /etc/icinga2. There are cases where it absolutely would make sense to combine the Director with manual configuration. You can also use multiple tools owning separare config packages. But these are pretty From cc01a9cb4466114d33fd2623f8e86f4afe8116fb Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Mon, 2 May 2016 10:59:52 +0200 Subject: [PATCH 64/74] config/files: link config diff --- application/views/scripts/config/files.phtml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/application/views/scripts/config/files.phtml b/application/views/scripts/config/files.phtml index db7dab97..7f0b22ef 100644 --- a/application/views/scripts/config/files.phtml +++ b/application/views/scripts/config/files.phtml @@ -24,6 +24,11 @@ 'director/show/activitylog', array('checksum' => $this->config->getLastActivityHexChecksum()), array('class' => 'icon-clock', 'data-base-target' => '_next') + ) ?>
qlink( + $this->translate('Diff with other config'), + 'director/config/diff', + array('left' => $this->config->getHexChecksum()), + array('class' => 'icon-flapping', 'data-base-target' => '_self') ) ?> From 31163b070831946024d2be81102e433e923f9360 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Mon, 2 May 2016 11:08:18 +0200 Subject: [PATCH 65/74] KickstartForm: fix exception catching... ...element ordering and give more hints when storing config is not possible fixes #11653 --- application/forms/KickstartForm.php | 41 +++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/application/forms/KickstartForm.php b/application/forms/KickstartForm.php index b9b8d4df..b1da43c5 100644 --- a/application/forms/KickstartForm.php +++ b/application/forms/KickstartForm.php @@ -27,9 +27,10 @@ class KickstartForm extends QuickForm $this->migrateDbLabel = $this->translate('Apply schema migrations'); $this->addResourceConfigElements(); + $this->addResourceDisplayGroup(); + if (!$this->config()->get('db', 'resource') || ($this->config()->get('db', 'resource') !== $this->getResourceName())) { - $this->addResourceDisplayGroup(); return; } @@ -66,6 +67,9 @@ class KickstartForm extends QuickForm )); $this->addHtmlHint($hint, array('name' => 'HINT_ready')); + $this->getDisplayGroup('config')->addElements( + array($this->getElement('HINT_ready')) + ); return; } @@ -168,7 +172,6 @@ class KickstartForm extends QuickForm $this->addHtmlHint($hint, array('name' => 'HINT_db_perms')); } - } } @@ -214,10 +217,11 @@ class KickstartForm extends QuickForm { $elements = array( 'HINT_no_resource', - 'HINT_ready', 'resource', + 'HINT_ready', 'HINT_schema', - 'HINT_db_perms' + 'HINT_db_perms', + 'HINT_config_store' ); $this->addDisplayGroup($elements, 'config', array( @@ -258,26 +262,41 @@ class KickstartForm extends QuickForm try { $config->saveIni(); $this->setSuccessMessage($this->translate('Configuration has been stored')); + + return true; } catch (Exception $e) { $this->getElement('resource')->addError( sprintf( - $this->translate('Unable to store the configuration to "%s"'), + $this->translate( + 'Unable to store the configuration to "%s". Please check' + . ' file permissions or manually store the content shown below' + ), $config->getConfigFile() ) - )->removeDecorator('description'); - $this->addHtmlHint( - '
' . $config . '
' ); - } + $this->addHtmlHint( + '
' . $config . '
', + array('name' => 'HINT_config_store') + ); + $this->getDisplayGroup('config')->addElements( + array($this->getElement('HINT_config_store')) + ); + $this->removeElement('HINT_ready'); + + return false; + } } public function onSuccess() { try { if ($this->getSubmitLabel() === $this->storeConfigLabel) { - $this->storeResourceConfig(); - return parent::onSuccess(); + if ($this->storeResourceConfig()) { + return parent::onSuccess(); + } else { + return; + } } if ($this->getSubmitLabel() === $this->createDbLabel From d84369ce77cace57cfd1e07a17218c306ba2e86b Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Mon, 2 May 2016 11:10:47 +0200 Subject: [PATCH 66/74] IcingaArgument: enforce command object id on set --- library/Director/Objects/IcingaArguments.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/library/Director/Objects/IcingaArguments.php b/library/Director/Objects/IcingaArguments.php index ecc543cc..4d39ed98 100644 --- a/library/Director/Objects/IcingaArguments.php +++ b/library/Director/Objects/IcingaArguments.php @@ -82,7 +82,9 @@ class IcingaArguments implements Iterator, Countable, IcingaConfigRenderer public function set($key, $value) { - $argument = IcingaCommandArgument::create($this->mungeCommandArgument($key, $value)); + $argument = IcingaCommandArgument::create( + $this->mungeCommandArgument($key, $value) + )->set('command_id', $this->object->id); $key = $argument->argument_name; if (array_key_exists($key, $this->arguments)) { $this->arguments[$key]->replaceWith($argument); From fddd4488b568112858ba3670aac963755503e48b Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 3 May 2016 08:27:12 +0200 Subject: [PATCH 67/74] host/agent: show a friendly error message... ...when the deployment endpoint is not reachable --- application/controllers/HostController.php | 27 ++++++++++++++++++++-- application/views/scripts/host/agent.phtml | 8 +++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/application/controllers/HostController.php b/application/controllers/HostController.php index 8219676f..c8196a24 100644 --- a/application/controllers/HostController.php +++ b/application/controllers/HostController.php @@ -2,6 +2,7 @@ namespace Icinga\Module\Director\Controllers; +use Exception; use Icinga\Exception\NotFoundError; use Icinga\Module\Director\Objects\IcingaEndpoint; use Icinga\Module\Director\Objects\IcingaZone; @@ -78,7 +79,24 @@ class HostController extends ObjectController $this->view->title = 'Agent deployment instructions'; // TODO: Fail when no ticket $this->view->certname = $this->object->object_name; - $this->view->ticket = Util::getIcingaTicket($this->view->certname, $this->api()->getTicketSalt()); + + try { + $this->view->ticket = Util::getIcingaTicket( + $this->view->certname, + $this->api()->getTicketSalt() + ); + + } catch (Exception $e) { + $this->view->ticket = 'ERROR'; + $this->view->error = sprintf( + $this->translate( + 'A ticket for this agent could not have been requested from' + . ' your deployment endpoint: %s' + ), + $e->getMessage() + ); + } + $this->view->master = $this->db()->getDeploymentEndpointName(); $this->view->masterzone = $this->db()->getMasterZoneName(); $this->view->globalzone = $this->db()->getDefaultGlobalZoneName(); @@ -95,7 +113,12 @@ class HostController extends ObjectController throw new NotFoundError('The host "%s" is not an agent', $host->object_name); } - return $this->sendJson(Util::getIcingaTicket($host->object_name, $this->api()->getTicketSalt())); + return $this->sendJson( + Util::getIcingaTicket( + $host->object_name, + $this->api()->getTicketSalt() + ) + ); } public function renderAction() diff --git a/application/views/scripts/host/agent.phtml b/application/views/scripts/host/agent.phtml index e7998347..e6149a42 100644 --- a/application/views/scripts/host/agent.phtml +++ b/application/views/scripts/host/agent.phtml @@ -8,7 +8,11 @@ $cert = $this->escape($this->certname); $master = $this->escape($this->master); ?> -Please check the Icinga 2 Client documentation for more related information. The Director-assisted setup corresponds to configuring the Client as Command Execution Bridge. +

Please check the Icinga 2 Client documentation for more related information. The Director-assisted setup corresponds to configuring the Client as Command Execution Bridge.

+ +error): ?> +

escape($this->error) ?>

+

When using the node wizard

Ticket : escape($ticket) ?>

@@ -49,7 +53,7 @@ icinga2 pki request --host \ include "constants.conf" include <itl> include <plugins> -include <plugins-contrib> +// include <plugins-contrib> object FileLogger "main-log" { severity = "information" From 3d03baff3844275a104f8b4fa5aa1b8039cf1048 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 3 May 2016 09:07:28 +0200 Subject: [PATCH 68/74] Object/HostController: delegate preview rendering --- application/controllers/HostController.php | 42 ------------------- application/views/scripts/object/show.phtml | 12 ++++-- .../Web/Controller/ObjectController.php | 32 +++++++++----- 3 files changed, 30 insertions(+), 56 deletions(-) diff --git a/application/controllers/HostController.php b/application/controllers/HostController.php index c8196a24..fb193551 100644 --- a/application/controllers/HostController.php +++ b/application/controllers/HostController.php @@ -120,46 +120,4 @@ class HostController extends ObjectController ) ); } - - public function renderAction() - { - $this->renderAgentExtras(); - return parent::renderAction(); - } - - protected function renderAgentExtras() - { - $host = $this->object; - $db = $this->db(); - if ($host->object_type !== 'object') { - return; - } - - if ($host->getResolvedProperty('has_agent') !== 'y') { - return; - } - - $name = $host->object_name; - if (IcingaEndpoint::exists($name, $db)) { - return; - } - - $props = array( - 'object_name' => $name, - 'object_type' => 'object', - 'log_duration' => 0 - ); - if ($host->getResolvedProperty('master_should_connect') === 'y') { - $props['host'] = $host->getResolvedProperty('address'); - $props['zone_id'] = $host->getResolvedProperty('zone_id'); - } - - $this->view->extraObjects = array( - IcingaEndpoint::create($props), - IcingaZone::create(array( - 'object_name' => $name, - 'parent' => $db->getMasterZoneName() - ), $db)->setEndpointList(array($name)) - ); - } } diff --git a/application/views/scripts/object/show.phtml b/application/views/scripts/object/show.phtml index fd717243..bc20c9f4 100644 --- a/application/views/scripts/object/show.phtml +++ b/application/views/scripts/object/show.phtml @@ -10,7 +10,7 @@ disabled === 'y'): ?>

translate('This object will not be deployed as it has been disabled') ?>

-isExternal()): ?> +isExternal): ?>

translate( 'This is an external object. It has been imported from Icinga 2 throught the' . ' Core API and cannot be managed with the Icinga Director. It is however' @@ -19,7 +19,11 @@ . ' object more enjoyable' ) ?>

-disabled === 'y'): ?> class="disabled">escape($object) ?>extraObjects): ?> -extraObjects) ?> - +config->getFiles() as $filename => $file): ?> +isExternal): ?>

escape($filename) ?>

+isDisabled): ?> class="disabled"isExternal): ?> class="logfile"> +escape($file->getContent()) ?> + + +
diff --git a/library/Director/Web/Controller/ObjectController.php b/library/Director/Web/Controller/ObjectController.php index 47922514..b38525b7 100644 --- a/library/Director/Web/Controller/ObjectController.php +++ b/library/Director/Web/Controller/ObjectController.php @@ -6,6 +6,7 @@ use Exception; use Icinga\Exception\IcingaException; use Icinga\Exception\InvalidPropertyException; use Icinga\Exception\NotFoundError; +use Icinga\Module\Director\IcingaConfig\IcingaConfig; use Icinga\Module\Director\Objects\IcingaObject; use Icinga\Web\Url; @@ -103,23 +104,22 @@ abstract class ObjectController extends ActionController $type = $this->getType(); $this->getTabs()->activate('render'); $object = $this->object; + $this->view->isDisabled = $object->disabled === 'y'; + $this->view->isExternal = $object->isExternal(); if ($this->params->shift('resolved')) { - $this->view->object = $object::fromPlainObject( + $object = $object::fromPlainObject( $object->toPlainObject(true), $object->getConnection() ); - if ($object->imports()->count() > 0) { - $this->view->actionLinks = $this->view->qlink( - $this->translate('Show normal'), - $this->getRequest()->getUrl()->without('resolved'), - null, - array('class' => 'icon-resize-small state-warning') - ); - } + $this->view->actionLinks = $this->view->qlink( + $this->translate('Show normal'), + $this->getRequest()->getUrl()->without('resolved'), + null, + array('class' => 'icon-resize-small state-warning') + ); } else { - $this->view->object = $object; if ($object->supportsImports() && $object->imports()->count() > 0) { $this->view->actionLinks = $this->view->qlink( @@ -131,6 +131,18 @@ abstract class ObjectController extends ActionController } } + if ($this->view->isExternal) { + $object->object_type = 'object'; + } + + if ($this->view->isDisabledd) { + $object->disabled = 'n'; + } + + $this->view->object = $object; + $this->view->config = new IcingaConfig($this->db()); + $object->renderToConfig($this->view->config); + $this->view->title = sprintf( $this->translate('Config preview: %s'), $object->object_name From 60856a62ef96a365bff9b04b6a1cdc4eb074ee9d Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 3 May 2016 09:08:11 +0200 Subject: [PATCH 69/74] css: use gray instead of bold for undeployed... ...activities --- public/css/module.less | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/css/module.less b/public/css/module.less index c7c7663d..ce53dcb2 100644 --- a/public/css/module.less +++ b/public/css/module.less @@ -967,7 +967,8 @@ table.activity-log { } tr.undeployed td, tr.undeployed a { - font-weight: bold; + color: @gray; + background-color: @gray-lightest; } tr.undeployed td:first-child::before { From 24b201db13d4b6b120fff359744fba79bc11daed Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 3 May 2016 09:09:01 +0200 Subject: [PATCH 70/74] Object/CommandController: unify titles --- application/controllers/CommandController.php | 7 +++++-- library/Director/Web/Controller/ObjectController.php | 5 ++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/application/controllers/CommandController.php b/application/controllers/CommandController.php index 0327b40a..b546ced1 100644 --- a/application/controllers/CommandController.php +++ b/application/controllers/CommandController.php @@ -10,7 +10,7 @@ class CommandController extends ObjectController public function init() { parent::init(); - if ($this->object) { + if ($this->object && ! $this->object->isExternal()) { $this->getTabs()->add('arguments', array( 'url' => 'director/command/arguments', 'urlParams' => array('name' => $this->object->object_name), @@ -22,7 +22,10 @@ class CommandController extends ObjectController public function argumentsAction() { $this->getTabs()->activate('arguments'); - $this->view->title = $this->translate('Command arguments'); + $this->view->title = sprintf( + $this->translate('Command arguments: %s'), + $this->object->object_name + ); $this->view->table = $this ->loadTable('icingaCommandArgument') diff --git a/library/Director/Web/Controller/ObjectController.php b/library/Director/Web/Controller/ObjectController.php index b38525b7..c435efaf 100644 --- a/library/Director/Web/Controller/ObjectController.php +++ b/library/Director/Web/Controller/ObjectController.php @@ -239,10 +239,9 @@ abstract class ObjectController extends ActionController $type = $this->getType(); $this->getTabs()->activate('fields'); - $title = $this->translate('%s template "%s": custom fields'); + $this->view->title = sprintf( - $title, - $this->translate(ucfirst($type)), + $this->translate('Custom fields: %s'), $object->object_name ); From 3d1840d3104447bdfa61e53d0ea4a234d286cfb9 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 3 May 2016 10:00:48 +0200 Subject: [PATCH 71/74] configuration: rename menu entries --- configuration.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configuration.php b/configuration.php index 65134dcf..711d5412 100644 --- a/configuration.php +++ b/configuration.php @@ -37,10 +37,10 @@ $section->add($this->translate('Hosts'))->setUrl('director/hosts')->setPriority( $section->add($this->translate('Services'))->setUrl('director/services')->setPriority(40); $section->add($this->translate('Commands'))->setUrl('director/commands')->setPriority(50); $section->add($this->translate('Users'))->setUrl('director/users')->setPriority(70); -$section->add($this->translate('Import / Sync')) +$section->add($this->translate('Automation')) ->setUrl('director/list/importsource') ->setPriority(901); -$section->add($this->translate('Deployments / History')) +$section->add($this->translate('Config history')) ->setUrl('director/config/deployments') ->setPriority(902) ->setRenderer('ConfigHealthItemRenderer'); From 576d62da26ff1eb4f4657c55a0b3bf481802ba31 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 10 May 2016 20:23:45 +0200 Subject: [PATCH 72/74] IcingaObject: use resolved zone for apply/tpl --- library/Director/Objects/IcingaObject.php | 4 ++-- library/Director/Web/Controller/ActionController.php | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/library/Director/Objects/IcingaObject.php b/library/Director/Objects/IcingaObject.php index aedc825d..b6d5e10e 100644 --- a/library/Director/Objects/IcingaObject.php +++ b/library/Director/Objects/IcingaObject.php @@ -1134,9 +1134,9 @@ abstract class IcingaObject extends DbObject implements IcingaConfigRenderer public function getRenderingZone(IcingaConfig $config = null) { - if ($this->zone_id) { + if ($zoneId = $this->getResolvedProperty('zone_id')) { // Config has a lookup cache, is faster: - return $config->getZoneName($this->zone_id); + return $config->getZoneName($zoneId); } if ($this->isTemplate() || $this->isApplyRule()) { diff --git a/library/Director/Web/Controller/ActionController.php b/library/Director/Web/Controller/ActionController.php index 1e7d9556..3e9b2c5a 100644 --- a/library/Director/Web/Controller/ActionController.php +++ b/library/Director/Web/Controller/ActionController.php @@ -152,6 +152,12 @@ abstract class ActionController extends Controller 'label' => $this->translate('Sync rule'), 'url' => 'director/list/syncrule' ) + )->add( + 'jobs', + array( + 'label' => $this->translate('Jobs'), + 'url' => 'director/jobs' + ) ); return $this->view->tabs; } From bea8988df1cd6d41c2810bf68d6589eecb5151de Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 10 May 2016 21:13:07 +0200 Subject: [PATCH 73/74] IcingaService: fix and simplify command_endpoint --- library/Director/Objects/IcingaService.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/library/Director/Objects/IcingaService.php b/library/Director/Objects/IcingaService.php index 27168d1f..02834aca 100644 --- a/library/Director/Objects/IcingaService.php +++ b/library/Director/Objects/IcingaService.php @@ -162,13 +162,18 @@ class IcingaService extends IcingaObject protected function renderCustomExtensions() { - if ($this->command_endpoint_id !== null - || $this->object_type !== 'object' - || $this->getResolvedProperty('use_agent') !== 'y') { + // A hand-crafted command endpoint overrides use_agent + if ($this->command_endpoint_id !== null) { return ''; } - if ($this->hasBeenAssignedToHostTemplate()) { + // In case use_agent isn't defined, do nothing + // TODO: what if we inherit use_agent and override it with 'n'? + if ($this->use_agent !== 'y') { + return ''; + } + + if ($this->hasBeenAssignedToHostTemplate() || $this->object_type !== 'object') { return c::renderKeyValue('command_endpoint', 'host.name'); } else { return $this->renderRelationProperty('host', $this->host_id, 'command_endpoint'); From 45f9d33843f4d9d104b861d71a9c0edb5bebe401 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Tue, 10 May 2016 21:32:14 +0200 Subject: [PATCH 74/74] DeploymentLogTable: show short config checksum --- application/tables/DeploymentLogTable.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/application/tables/DeploymentLogTable.php b/application/tables/DeploymentLogTable.php index 1f56715c..79375add 100644 --- a/application/tables/DeploymentLogTable.php +++ b/application/tables/DeploymentLogTable.php @@ -45,22 +45,29 @@ class DeploymentLogTable extends QuickTable public function getColumns() { + $db = $this->connection(); + $columns = array( 'id' => 'l.id', 'peer_identity' => 'l.peer_identity', + 'identifier' => "l.peer_identity || ' (' || SUBSTRING(", 'start_time' => 'l.start_time', 'stage_collected' => 'l.stage_collected', 'dump_succeeded' => 'l.dump_succeeded', 'stage_name' => 'l.stage_name', 'startup_succeeded' => 'l.startup_succeeded', - 'checksum' => 'LOWER(HEX(c.checksum))', + 'checksum' => $db->dbHexFunc('c.checksum'), 'duration' => "l.duration_dump || 'ms'", ); if ($this->connection->isPgsql()) { - $columns['checksum'] = "LOWER(ENCODE(c.checksum, 'hex'))"; + $columns['identifier'] .= $columns['checksum'] . ' FROM 1 FOR 7)'; + } else { + $columns['identifier'] .= $columns['checksum'] . ', 1, 7)'; } + $columns['identifier'] .= " || ')'"; + return $columns; } @@ -73,8 +80,8 @@ class DeploymentLogTable extends QuickTable { $view = $this->view(); return array( - 'peer_identity' => $view->translate('Icinga Node'), - 'start_time' => $view->translate('Time'), + 'identifier' => $view->translate('Icinga Node'), + 'start_time' => $view->translate('Time'), ); }