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;