diff --git a/application/controllers/ConfigController.php b/application/controllers/ConfigController.php index 15ba0a6ff..078503346 100644 --- a/application/controllers/ConfigController.php +++ b/application/controllers/ConfigController.php @@ -1,41 +1,70 @@ view->tabs = Widget::create('tabs')->add('index', array( - 'title' => $this->translate('Application'), - 'url' => 'config' - ))->add('authentication', array( - 'title' => $this->translate('Authentication'), - 'url' => 'config/authentication' - ))->add('resources', array( - 'title' => $this->translate('Resources'), - 'url' => 'config/resource' - ))->add('roles', array( - 'title' => $this->translate('Roles'), - 'url' => 'roles' - )); + $tabs = $this->getTabs(); + $auth = $this->Auth(); + $allowedActions = array(); + if ($auth->hasPermission('system/config/application')) { + $tabs->add('application', array( + 'title' => $this->translate('Application'), + 'url' => 'config/application' + )); + $allowedActions[] = 'application'; + } + if ($auth->hasPermission('system/config/authentication')) { + $tabs->add('authentication', array( + 'title' => $this->translate('Authentication'), + 'url' => 'config/authentication' + )); + $allowedActions[] = 'authentication'; + } + if ($auth->hasPermission('system/config/resources')) { + $tabs->add('resource', array( + 'title' => $this->translate('Resources'), + 'url' => 'config/resource' + )); + $allowedActions[] = 'resource'; + } + if ($auth->hasPermission('system/config/roles')) { + $tabs->add('roles', array( + 'title' => $this->translate('Roles'), + 'url' => 'roles' + )); + $allowedActions[] = 'roles'; + } + $this->firstAllowedAction = array_shift($allowedActions); } public function devtoolsAction() @@ -44,16 +73,35 @@ class ConfigController extends ActionController } /** - * Index action, entry point for configuration + * Forward or redirect to the first allowed configuration action */ public function indexAction() { + if ($this->firstAllowedAction === null) { + throw new SecurityException('No permission for configuration'); + } + $action = $this->getTabs()->get($this->firstAllowedAction); + if (substr($action->getUrl()->getPath(), 0, 7) === 'config/') { + $this->forward($this->firstAllowedAction); + } else { + $this->redirectNow($action->getUrl()); + } + } + + /** + * Application configuration + * + * @throws SecurityException If the user lacks the permission for configuring the application + */ + public function applicationAction() + { + $this->assertPermission('system/config/application'); $form = new GeneralConfigForm(); $form->setIniConfig(Config::app()); $form->handleRequest(); $this->view->form = $form; - $this->view->tabs->activate('index'); + $this->view->tabs->activate('application'); } /** @@ -61,16 +109,19 @@ class ConfigController extends ActionController */ public function modulesAction() { - $this->view->tabs = Widget::create('tabs')->add('modules', array( - 'title' => $this->translate('Modules'), - 'url' => 'config/modules' - )); - - $this->view->tabs->activate('modules'); + // Overwrite tabs created in init + // @TODO(el): This seems not natural to me. Module configuration should have its own controller. + $this->view->tabs = Widget::create('tabs') + ->add('modules', array( + 'title' => $this->translate('Modules'), + 'url' => 'config/modules' + )) + ->activate('modules'); $this->view->modules = Icinga::app()->getModuleManager()->select() ->from('modules') ->order('enabled', 'desc') - ->order('name')->paginate(); + ->order('name') + ->paginate(); } public function moduleAction() @@ -79,8 +130,12 @@ class ConfigController extends ActionController $app = Icinga::app(); $manager = $app->getModuleManager(); if ($manager->hasInstalled($name)) { - $this->view->moduleData = Icinga::app()->getModuleManager()->select() - ->from('modules')->where('name', $name)->fetchRow(); + $this->view->moduleData = Icinga::app() + ->getModuleManager() + ->select() + ->from('modules') + ->where('name', $name) + ->fetchRow(); $module = new Module($app, $name, $manager->getModuleDir($name)); $this->view->module = $module; } else { @@ -94,6 +149,7 @@ class ConfigController extends ActionController */ public function moduleenableAction() { + $this->assertPermission('system/config/modules'); $module = $this->getParam('name'); $manager = Icinga::app()->getModuleManager(); try { @@ -114,6 +170,7 @@ class ConfigController extends ActionController */ public function moduledisableAction() { + $this->assertPermission('system/config/modules'); $module = $this->getParam('name'); $manager = Icinga::app()->getModuleManager(); try { @@ -133,6 +190,7 @@ class ConfigController extends ActionController */ public function authenticationAction() { + $this->assertPermission('system/config/authentication'); $form = new AuthenticationBackendReorderForm(); $form->setIniConfig(Config::app('authentication')); $form->handleRequest(); @@ -147,6 +205,7 @@ class ConfigController extends ActionController */ public function createauthenticationbackendAction() { + $this->assertPermission('system/config/authentication'); $form = new AuthenticationBackendConfigForm(); $form->setIniConfig(Config::app('authentication')); $form->setResourceConfig(ResourceFactory::getResourceConfigs()); @@ -163,6 +222,7 @@ class ConfigController extends ActionController */ public function editauthenticationbackendAction() { + $this->assertPermission('system/config/authentication'); $form = new AuthenticationBackendConfigForm(); $form->setIniConfig(Config::app('authentication')); $form->setResourceConfig(ResourceFactory::getResourceConfigs()); @@ -179,6 +239,7 @@ class ConfigController extends ActionController */ public function removeauthenticationbackendAction() { + $this->assertPermission('system/config/authentication'); $form = new ConfirmRemovalForm(array( 'onSuccess' => function ($form) { $configForm = new AuthenticationBackendConfigForm(); @@ -215,8 +276,9 @@ class ConfigController extends ActionController */ public function resourceAction() { + $this->assertPermission('system/config/resources'); $this->view->resources = Config::app('resources', true)->keys(); - $this->view->tabs->activate('resources'); + $this->view->tabs->activate('resource'); } /** @@ -224,6 +286,7 @@ class ConfigController extends ActionController */ public function createresourceAction() { + $this->assertPermission('system/config/resources'); $form = new ResourceConfigForm(); $form->setIniConfig(Config::app('resources')); $form->setRedirectUrl('config/resource'); @@ -238,6 +301,7 @@ class ConfigController extends ActionController */ public function editresourceAction() { + $this->assertPermission('system/config/resources'); $form = new ResourceConfigForm(); $form->setIniConfig(Config::app('resources')); $form->setRedirectUrl('config/resource'); @@ -252,6 +316,7 @@ class ConfigController extends ActionController */ public function removeresourceAction() { + $this->assertPermission('system/config/resources'); $form = new ConfirmRemovalForm(array( 'onSuccess' => function ($form) { $configForm = new ResourceConfigForm(); diff --git a/application/controllers/DashboardController.php b/application/controllers/DashboardController.php index ba2125e12..12a5180c8 100644 --- a/application/controllers/DashboardController.php +++ b/application/controllers/DashboardController.php @@ -2,14 +2,13 @@ // {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}} -use Icinga\Application\Logger; +use \Exception; use Icinga\Exception\ProgrammingError; use Icinga\Forms\ConfirmRemovalForm; use Icinga\Forms\Dashboard\DashletForm; use Icinga\Web\Form; use Icinga\Web\Notification; use Icinga\Web\Controller\ActionController; -use Icinga\Web\Request; use Icinga\Web\Url; use Icinga\Web\Widget\Dashboard; use Icinga\Web\Widget\Tabextension\DashboardSettings; @@ -25,11 +24,11 @@ class DashboardController extends ActionController * @var Dashboard; */ private $dashboard; - + public function init() { $this->dashboard = new Dashboard(); - $this->dashboard->setUser($this->getRequest()->getUser()); + $this->dashboard->setUser($this->Auth()->getUser()); $this->dashboard->load(); } @@ -56,11 +55,12 @@ class DashboardController extends ActionController $dashlet = new Dashboard\Dashlet($form->getValue('dashlet'), $form->getValue('url'), $pane); $dashlet->setUserWidget(); $pane->addDashlet($dashlet); + $dashboardConfig = $dashboard->getConfig(); try { - $dashboard->write(); - } catch (\Zend_Config_Exception $e) { + $dashboardConfig->saveIni(); + } catch (Exception $e) { $action->view->error = $e; - $action->view->config = $dashboard->createWriter(); + $action->view->config = $dashboardConfig; $action->render('error'); return false; } @@ -117,11 +117,12 @@ class DashboardController extends ActionController $oldPane = $dashboard->getPane($form->getValue('org_pane')); $oldPane->removeDashlet($dashlet->getTitle()); } + $dashboardConfig = $dashboard->getConfig(); try { - $dashboard->write(); - } catch (\Zend_Config_Exception $e) { + $dashboardConfig->saveIni(); + } catch (Exception $e) { $action->view->error = $e; - $action->view->config = $dashboard->createWriter(); + $action->view->config = $dashboardConfig; $action->render('error'); return false; } @@ -158,15 +159,16 @@ class DashboardController extends ActionController $dashlet = $this->_request->getParam('dashlet'); $action = $this; $form->setOnSuccess(function (Form $form) use ($dashboard, $dashlet, $pane, $action) { + $pane = $dashboard->getPane($pane); + $pane->removeDashlet($dashlet); + $dashboardConfig = $dashboard->getConfig(); try { - $pane = $dashboard->getPane($pane); - $pane->removeDashlet($dashlet); - $dashboard->write(); + $dashboardConfig->saveIni(); Notification::success(t('Dashlet has been removed from') . ' ' . $pane->getTitle()); return true; - } catch (\Zend_Config_Exception $e) { + } catch (Exception $e) { $action->view->error = $e; - $action->view->config = $dashboard->createWriter(); + $action->view->config = $dashboardConfig; $action->render('error'); return false; } catch (ProgrammingError $e) { @@ -196,15 +198,16 @@ class DashboardController extends ActionController $pane = $this->_request->getParam('pane'); $action = $this; $form->setOnSuccess(function (Form $form) use ($dashboard, $pane, $action) { + $pane = $dashboard->getPane($pane); + $dashboard->removePane($pane->getTitle()); + $dashboardConfig = $dashboard->getConfig(); try { - $pane = $dashboard->getPane($pane); - $dashboard->removePane($pane->getTitle()); - $dashboard->write(); + $dashboardConfig->saveIni(); Notification::success(t('Dashboard has been removed') . ': ' . $pane->getTitle()); return true; - } catch (\Zend_Config_Exception $e) { + } catch (Exception $e) { $action->view->error = $e; - $action->view->config = $dashboard->createWriter(); + $action->view->config = $dashboardConfig; $action->render('error'); return false; } catch (ProgrammingError $e) { @@ -241,7 +244,7 @@ class DashboardController extends ActionController $this->view->title = $this->dashboard->getActivePane()->getTitle() . ' :: Dashboard'; if ($this->hasParam('remove')) { $this->dashboard->getActivePane()->removeDashlet($this->getParam('remove')); - $this->dashboard->write(); + $this->dashboard->getConfig()->saveIni(); $this->redirectNow(URL::fromRequest()->remove('remove')); } $this->view->tabs->add( diff --git a/application/controllers/ErrorController.php b/application/controllers/ErrorController.php index 23051bd9a..0548b5c9c 100644 --- a/application/controllers/ErrorController.php +++ b/application/controllers/ErrorController.php @@ -1,12 +1,9 @@ getResponse()->setHttpResponseCode(403); + $this->view->message = $exception->getMessage(); + break; + } + // Move to default default: $title = preg_replace('/\r?\n.*$/s', '', $exception->getMessage()); $this->getResponse()->setHttpResponseCode(500); diff --git a/application/controllers/RolesController.php b/application/controllers/RolesController.php index 90163812e..f4c6aeed2 100644 --- a/application/controllers/RolesController.php +++ b/application/controllers/RolesController.php @@ -1,6 +1,4 @@ view->tabs = Widget::create('tabs')->add('index', array( - 'title' => $this->translate('Application'), - 'url' => 'config' - ))->add('authentication', array( - 'title' => $this->translate('Authentication'), - 'url' => 'config/authentication' - ))->add('resources', array( - 'title' => $this->translate('Resources'), - 'url' => 'config/resource' - ))->add('roles', array( + $this->assertPermission('system/config/roles'); + $tabs = $this->getTabs(); + $auth = $this->Auth(); + if ($auth->hasPermission('system/config/application')) { + $tabs->add('application', array( + 'title' => $this->translate('Application'), + 'url' => 'config' + )); + } + if ($auth->hasPermission('system/config/authentication')) { + $tabs->add('authentication', array( + 'title' => $this->translate('Authentication'), + 'url' => 'config/authentication' + )); + } + if ($auth->hasPermission('system/config/resources')) { + $tabs->add('resource', array( + 'title' => $this->translate('Resources'), + 'url' => 'config/resource' + )); + } + $tabs->add('roles', array( 'title' => $this->translate('Roles'), 'url' => 'roles' )); } + /** + * List roles + */ public function indexAction() { $this->view->tabs->activate('roles'); $this->view->roles = Config::app('roles', true); } + /** + * Create a new role + */ public function newAction() { $role = new RoleForm(array( @@ -61,6 +85,11 @@ class RolesController extends ActionController $this->view->form = $role; } + /** + * Update a role + * + * @throws Zend_Controller_Action_Exception If the required parameter 'role' is missing or the role does not exist + */ public function updateAction() { $name = $this->_request->getParam('role'); @@ -105,6 +134,11 @@ class RolesController extends ActionController $this->view->form = $role; } + /** + * Remove a role + * + * @throws Zend_Controller_Action_Exception If the required parameter 'role' is missing or the role does not exist + */ public function removeAction() { $name = $this->_request->getParam('role'); diff --git a/application/forms/ConfigForm.php b/application/forms/ConfigForm.php index 28d3111ea..224633f1f 100644 --- a/application/forms/ConfigForm.php +++ b/application/forms/ConfigForm.php @@ -8,7 +8,6 @@ use Exception; use Zend_Form_Decorator_Abstract; use Icinga\Web\Form; use Icinga\Application\Config; -use Icinga\File\Ini\IniWriter; /** * Form base-class providing standard functionality for configuration forms @@ -44,21 +43,14 @@ class ConfigForm extends Form */ public function save() { - $writer = new IniWriter( - array( - 'config' => $this->config, - 'filename' => $this->config->getConfigFile() - ) - ); - try { - $writer->write(); + $this->config->saveIni(); } catch (Exception $e) { $this->addDecorator('ViewScript', array( 'viewModule' => 'default', 'viewScript' => 'showConfiguration.phtml', 'errorMessage' => $e->getMessage(), - 'configString' => $writer->render(), + 'configString' => $this->config, 'filePath' => $this->config->getConfigFile(), 'placement' => Zend_Form_Decorator_Abstract::PREPEND )); diff --git a/application/forms/Security/RoleForm.php b/application/forms/Security/RoleForm.php index f32c0cab4..cc5f4037e 100644 --- a/application/forms/Security/RoleForm.php +++ b/application/forms/Security/RoleForm.php @@ -21,7 +21,15 @@ class RoleForm extends ConfigForm * * @type array */ - protected $providedPermissions = array('*' => '*'); + protected $providedPermissions = array( + '*' => '*', + 'system/config/*' => 'system/config/*', + 'system/config/application' => 'system/config/application', + 'system/config/authentication' => 'system/config/authentication', + 'system/config/modules' => 'system/config/modules', + 'system/config/resources' => 'system/config/resources', + 'system/config/roles' => 'system/config/roles' + ); /** * Provided restrictions by currently loaded modules diff --git a/application/views/scripts/config/index.phtml b/application/views/scripts/config/application.phtml similarity index 100% rename from application/views/scripts/config/index.phtml rename to application/views/scripts/config/application.phtml diff --git a/application/views/scripts/dashboard/error.phtml b/application/views/scripts/dashboard/error.phtml index e5a0f3939..56e32faf4 100644 --- a/application/views/scripts/dashboard/error.phtml +++ b/application/views/scripts/dashboard/error.phtml @@ -1,13 +1,13 @@
-

+

translate('Could not persist dashboard'); ?>

- - config->getFilename(); ?>;. + translate('Please copy the following dashboard snippet to '); ?> + config->getConfigFile(); ?>;.
- + translate('Make sure that the webserver can write to this file.'); ?>

-
config->render(); ?>
+
config; ?>

-

+

translate('Error details'); ?>

error->getMessage(); ?>

-
+ \ No newline at end of file diff --git a/application/views/scripts/error/error.phtml b/application/views/scripts/error/error.phtml index 2a900651b..d9422474c 100644 --- a/application/views/scripts/error/error.phtml +++ b/application/views/scripts/error/error.phtml @@ -1,8 +1,9 @@ -title): ?>
+tabs->showOnlyCloseButton() ?> +title): ?>

escape($title) ?>

-
+
message): ?>

escape($message)) ?>

diff --git a/application/views/scripts/roles/new.phtml b/application/views/scripts/roles/new.phtml index d7b277008..4c21c6f6b 100644 --- a/application/views/scripts/roles/new.phtml +++ b/application/views/scripts/roles/new.phtml @@ -1,4 +1,5 @@
+ showOnlyCloseButton() ?>

translate('New Role') ?>

diff --git a/application/views/scripts/roles/remove.phtml b/application/views/scripts/roles/remove.phtml index e2d01ed62..4abff566a 100644 --- a/application/views/scripts/roles/remove.phtml +++ b/application/views/scripts/roles/remove.phtml @@ -1,4 +1,5 @@
+ showOnlyCloseButton() ?>

translate('Remove Role %s'), $name) ?>

diff --git a/application/views/scripts/roles/update.phtml b/application/views/scripts/roles/update.phtml index 44f756b06..f48f1ca69 100644 --- a/application/views/scripts/roles/update.phtml +++ b/application/views/scripts/roles/update.phtml @@ -1,4 +1,5 @@
+ showOnlyCloseButton() ?>

translate('Update Role %s'), $name) ?>

diff --git a/icingaweb2.spec b/icingaweb2.spec index cd1ee485f..a012751ea 100644 --- a/icingaweb2.spec +++ b/icingaweb2.spec @@ -5,7 +5,7 @@ Version: 2.0.0 Release: %{revision}%{?dist} Summary: Icinga Web 2 Group: Applications/System -License: GPL +License: GPLv2+ and MIT and BSD URL: https://icinga.org Source0: https://github.com/Icinga/%{name}/archive/v%{version}.tar.gz BuildArch: noarch @@ -29,7 +29,6 @@ Packager: Icinga Team %endif %endif - %if 0%{?suse_version} %define wwwconfigdir %{_sysconfdir}/apache2/conf.d %define wwwuser wwwrun @@ -43,15 +42,17 @@ Requires: apache2-mod_php5 %endif %endif -Requires(pre): shadow-utils -Requires: %{name}-common = %{version}-%{release} -Requires: php-Icinga = %{version}-%{release} -Requires: %{name}-vendor-dompdf -Requires: %{name}-vendor-HTMLPurifier -Requires: %{name}-vendor-JShrink -Requires: %{name}-vendor-lessphp -Requires: %{name}-vendor-Parsedown -Requires: %{zend} +%{?fedora:Requires(pre): shadow-utils} +%{?rhel:Requires(pre): shadow-utils} +%{?suse_version:Requires(pre): pwdutils} +Requires: %{name}-common = %{version}-%{release} +Requires: php-Icinga = %{version}-%{release} +Requires: %{name}-vendor-dompdf +Requires: %{name}-vendor-HTMLPurifier +Requires: %{name}-vendor-JShrink +Requires: %{name}-vendor-lessphp +Requires: %{name}-vendor-Parsedown +Requires: %{zend} %description @@ -68,8 +69,11 @@ Icinga Web 2 %package common -Summary: Common files for Icinga Web 2 and the Icinga CLI -Group: Applications/System +Summary: Common files for Icinga Web 2 and the Icinga CLI +Group: Applications/System +%{?fedora:Requires(pre): shadow-utils} +%{?rhel:Requires(pre): shadow-utils} +%{?suse_version:Requires(pre): pwdutils} %description common Common files for Icinga Web 2 and the Icinga CLI @@ -82,7 +86,7 @@ Requires: %{php} >= 5.3.0 Requires: %{php}-gd %{php}-intl %{?fedora:Requires: php-pecl-imagick} %{?rhel:Requires: php-pecl-imagick} -%{?suse_version:Requires: %{php}-gettext %{php}-openssl php5-imagick} +%{?suse_version:Requires: %{php}-gettext %{php}-json %{php}-openssl %{php}-posix} %description -n php-Icinga Icinga Web 2 PHP library @@ -106,6 +110,7 @@ Version: 0.6.1 Release: 1%{?dist} Summary: Icinga Web 2 vendor library dompdf Group: Development/Libraries +License: LGPLv2.1 Requires: %{php} >= 5.3.0 %description vendor-dompdf @@ -117,6 +122,7 @@ Version: 4.6.0 Release: 1%{?dist} Summary: Icinga Web 2 vendor library HTMLPurifier Group: Development/Libraries +License: LGPLv2.1 Requires: %{php} >= 5.3.0 %description vendor-HTMLPurifier @@ -128,6 +134,7 @@ Version: 1.0.1 Release: 1%{?dist} Summary: Icinga Web 2 vendor library JShrink Group: Development/Libraries +License: BSD Requires: %{php} >= 5.3.0 %description vendor-JShrink @@ -139,6 +146,7 @@ Version: 0.4.0 Release: 1%{?dist} Summary: Icinga Web 2 vendor library lessphp Group: Development/Libraries +License: MIT Requires: %{php} >= 5.3.0 %description vendor-lessphp @@ -150,6 +158,7 @@ Version: 1.0.0 Release: 1%{?dist} Summary: Icinga Web 2 vendor library Parsedown Group: Development/Libraries +License: MIT Requires: %{php} >= 5.3.0 %description vendor-Parsedown @@ -161,6 +170,7 @@ Version: 1.12.9 Release: 1%{?dist} Summary: Icinga Web 2 vendor library Zend Framework Group: Development/Libraries +License: BSD Requires: %{php} >= 5.3.0 %description vendor-Zend diff --git a/library/Icinga/Application/Config.php b/library/Icinga/Application/Config.php index 361f5f11d..484cff6df 100644 --- a/library/Icinga/Application/Config.php +++ b/library/Icinga/Application/Config.php @@ -6,8 +6,11 @@ namespace Icinga\Application; use Iterator; use Countable; +use LogicException; use UnexpectedValueException; +use Icinga\Util\File; use Icinga\Data\ConfigObject; +use Icinga\File\Ini\IniWriter; use Icinga\Exception\NotReadableError; /** @@ -299,6 +302,49 @@ class Config implements Countable, Iterator return $emptyConfig; } + /** + * Save configuration to the given INI file + * + * @param string|null $filePath The path to the INI file or null in case this config's path should be used + * @param int $fileMode The file mode to store the file with + * + * @throws LogicException In case this config has no path and none is passed in either + * @throws NotWritableError In case the INI file cannot be written + * + * @todo create basepath and throw NotWritableError in case its not possible + */ + public function saveIni($filePath = null, $fileMode = 0660) + { + if ($filePath === null && $this->configFile) { + $filePath = $this->configFile; + } elseif ($filePath === null) { + throw new LogicException('You need to pass $filePath or set a path using Config::setConfigFile()'); + } + + if (! file_exists($filePath)) { + File::create($filePath, $fileMode); + } + + $this->getIniWriter($filePath, $fileMode)->write(); + } + + /** + * Return a IniWriter for this config + * + * @param string|null $filePath + * @param int $fileMode + * + * @return IniWriter + */ + protected function getIniWriter($filePath = null, $fileMode = null) + { + return new IniWriter(array( + 'config' => $this, + 'filename' => $filePath, + 'filemode' => $fileMode + )); + } + /** * Prepend configuration base dir to the given relative path * @@ -354,4 +400,14 @@ class Config implements Countable, Iterator return $moduleConfigs[$configname]; } + + /** + * Return this config rendered as a INI structured string + * + * @return string + */ + public function __toString() + { + return $this->getIniWriter()->render(); + } } diff --git a/library/Icinga/Application/Modules/Manager.php b/library/Icinga/Application/Modules/Manager.php index 7ee2461f9..0a1b23f78 100644 --- a/library/Icinga/Application/Modules/Manager.php +++ b/library/Icinga/Application/Modules/Manager.php @@ -103,25 +103,22 @@ class Manager */ private function detectEnabledModules() { - $canonical = $this->enableDir; - if ($canonical === false || ! file_exists($canonical)) { - // TODO: I guess the check for false has something to do with a - // call to realpath no longer present + if (! file_exists($this->enableDir)) { return; } - if (!is_dir($this->enableDir)) { + if (! is_dir($this->enableDir)) { throw new NotReadableError( 'Cannot read enabled modules. Module directory "%s" is not a directory', $this->enableDir ); } - if (!is_readable($this->enableDir)) { + if (! is_readable($this->enableDir)) { throw new NotReadableError( 'Cannot read enabled modules. Module directory "%s" is not readable', $this->enableDir ); } - if (($dh = opendir($canonical)) !== false) { + if (($dh = opendir($this->enableDir)) !== false) { $this->enabledDirs = array(); while (($file = readdir($dh)) !== false) { @@ -129,7 +126,7 @@ class Manager continue; } - $link = $this->enableDir . '/' . $file; + $link = $this->enableDir . DIRECTORY_SEPARATOR . $file; if (! is_link($link)) { Logger::warning( 'Found invalid module in enabledModule directory "%s": "%s" is not a symlink', @@ -140,7 +137,7 @@ class Manager } $dir = realpath($link); - if (!file_exists($dir) || !is_dir($dir)) { + if (! file_exists($dir) || !is_dir($dir)) { Logger::warning( 'Found invalid module in enabledModule directory "%s": "%s" points to non existing path "%s"', $this->enableDir, @@ -208,7 +205,7 @@ class Manager */ public function enableModule($name) { - if (!$this->hasInstalled($name)) { + if (! $this->hasInstalled($name)) { throw new ConfigurationError( 'Cannot enable module "%s". Module is not installed.', $name @@ -217,11 +214,16 @@ class Manager clearstatcache(true); $target = $this->installedBaseDirs[$name]; - $link = $this->enableDir . '/' . $name; + $link = $this->enableDir . DIRECTORY_SEPARATOR . $name; - if (! is_dir($this->enableDir)) { - throw new NotFoundError('Cannot enable module "%s". Path "%s" not found.', $name, $this->enableDir); - } elseif (!is_writable($this->enableDir)) { + if (! is_dir($this->enableDir) && !@mkdir($this->enableDir, 02770, true)) { + $error = error_get_last(); + throw new SystemPermissionException( + 'Failed to create enabledModule directory "%s" (%s)', + $this->enableDir, + $error['message'] + ); + } elseif (! is_writable($this->enableDir)) { throw new SystemPermissionException( 'Cannot enable module "%s". Insufficient system permissions for enabling modules.', $name @@ -232,7 +234,7 @@ class Manager return $this; } - if (!@symlink($target, $link)) { + if (! @symlink($target, $link)) { $error = error_get_last(); if (strstr($error["message"], "File exists") === false) { throw new SystemPermissionException( @@ -246,9 +248,7 @@ class Manager } $this->enabledDirs[$name] = $link; - $this->loadModule($name); - return $this; } @@ -264,22 +264,22 @@ class Manager */ public function disableModule($name) { - if (!$this->hasEnabled($name)) { + if (! $this->hasEnabled($name)) { return $this; } - if (!is_writable($this->enableDir)) { + if (! is_writable($this->enableDir)) { throw new SystemPermissionException( 'Could not disable module. Module path is not writable.' ); } - $link = $this->enableDir . '/' . $name; - if (!file_exists($link)) { + $link = $this->enableDir . DIRECTORY_SEPARATOR . $name; + if (! file_exists($link)) { throw new ConfigurationError( 'Could not disable module. The module %s was not found.', $name ); } - if (!is_link($link)) { + if (! is_link($link)) { throw new ConfigurationError( 'Could not disable module. The module "%s" is not a symlink. ' . 'It looks like you have installed this module manually and moved it to your module folder. ' @@ -290,7 +290,7 @@ class Manager } if (file_exists($link) && is_link($link)) { - if (!@unlink($link)) { + if (! @unlink($link)) { $error = error_get_last(); throw new SystemPermissionException( 'Could not disable module "%s" due to file system errors. ' diff --git a/library/Icinga/Data/SimpleQuery.php b/library/Icinga/Data/SimpleQuery.php index 41d050ea2..e865d86fa 100644 --- a/library/Icinga/Data/SimpleQuery.php +++ b/library/Icinga/Data/SimpleQuery.php @@ -320,7 +320,7 @@ class SimpleQuery implements QueryInterface, Queryable // Detect parameters from request $request = Icinga::app()->getFrontController()->getRequest(); if ($itemsPerPage === null) { - $itemsPerPage = $request->getParam('limit', 20); + $itemsPerPage = $request->getParam('limit', 25); } if ($pageNumber === null) { $pageNumber = $request->getParam('page', 0); diff --git a/library/Icinga/Security/SecurityException.php b/library/Icinga/Security/SecurityException.php new file mode 100644 index 000000000..168d4a9be --- /dev/null +++ b/library/Icinga/Security/SecurityException.php @@ -0,0 +1,12 @@ +writer === null) { - if (! file_exists($this->preferencesFile)) { - if (! is_writable($this->getStoreConfig()->location)) { - throw new NotWritableError( - 'Path to the preferences INI files %s is not writable', - $this->getStoreConfig()->location - ); - } - - File::create($this->preferencesFile, 0664); - } - - if (! is_writable($this->preferencesFile)) { - throw new NotWritableError( - 'Preferences INI file %s for user %s is not writable', - $this->preferencesFile, - $this->getUser()->getUsername() - ); - } - - $this->writer = new IniWriter( - array( - 'config' => Config::fromArray($this->preferences), - 'filename' => $this->preferencesFile - ) - ); - } - - $this->writer->write(); + Config::fromArray($this->preferences)->saveIni($this->preferencesFile); } /** diff --git a/library/Icinga/Util/File.php b/library/Icinga/Util/File.php index 9ce1ec893..99421b33d 100644 --- a/library/Icinga/Util/File.php +++ b/library/Icinga/Util/File.php @@ -23,6 +23,13 @@ class File extends SplFileObject */ protected $openMode; + /** + * The access mode to use when creating directories + * + * @var int + */ + public static $dirMode = 1528; // 2770 + /** * @see SplFileObject::__construct() */ @@ -37,21 +44,74 @@ class File extends SplFileObject } /** - * Create a file with an access mode + * Create a file using the given access mode and return a instance of File open for writing * * @param string $path The path to the file * @param int $accessMode The access mode to set + * @param bool $recursive Whether missing nested directories of the given path should be created + * + * @return File * * @throws RuntimeException In case the file cannot be created or the access mode cannot be set + * @throws NotWritableError In case the path's (existing) parent is not writable */ - public static function create($path, $accessMode) + public static function create($path, $accessMode, $recursive = true) { - if (!@touch($path)) { - throw new RuntimeException('Cannot create file "' . $path . '" with acces mode "' . $accessMode . '"'); + $dirPath = dirname($path); + if ($recursive && !is_dir($dirPath)) { + static::createDirectories($dirPath); + } elseif (! is_writable($dirPath)) { + throw new NotWritableError(sprintf('Path "%s" is not writable', $dirPath)); } - if (!@chmod($path, $accessMode)) { - throw new RuntimeException('Cannot set access mode "' . $accessMode . '" on file "' . $path . '"'); + $file = new static($path, 'x'); + + if (! @chmod($path, $accessMode)) { + $error = error_get_last(); + throw new RuntimeException(sprintf( + 'Cannot set access mode "%s" on file "%s" (%s)', + decoct($accessMode), + $path, + $error['message'] + )); + } + + return $file; + } + + /** + * Create missing directories + * + * @param string $path + * + * @throws RuntimeException In case a directory cannot be created or the access mode cannot be set + */ + protected static function createDirectories($path) + { + $part = strpos($path, DIRECTORY_SEPARATOR) === 0 ? DIRECTORY_SEPARATOR : ''; + foreach (explode(DIRECTORY_SEPARATOR, ltrim($path, DIRECTORY_SEPARATOR)) as $dir) { + $part .= $dir . DIRECTORY_SEPARATOR; + + if (! is_dir($part)) { + if (! @mkdir($part, static::$dirMode)) { + $error = error_get_last(); + throw new RuntimeException(sprintf( + 'Failed to create missing directory "%s" (%s)', + $part, + $error['message'] + )); + } + + if (! @chmod($part, static::$dirMode)) { + $error = error_get_last(); + throw new RuntimeException(sprintf( + 'Failed to set access mode "%s" for directory "%s" (%s)', + decoct(static::$dirMode), + $part, + $error['message'] + )); + } + } } } diff --git a/library/Icinga/Web/Controller/ActionController.php b/library/Icinga/Web/Controller/ActionController.php index 78fcaf7bf..87d293be6 100644 --- a/library/Icinga/Web/Controller/ActionController.php +++ b/library/Icinga/Web/Controller/ActionController.php @@ -1,23 +1,22 @@ Auth()->hasPermission($name)) { - // TODO: Shall this be an Auth Exception? Or a 404? - throw new IcingaException( - 'Auth error, no permission for "%s"', - $name - ); + if (! $this->Auth()->hasPermission($permission)) { + throw new SecurityException('No permission for %s', $permission); } } diff --git a/library/Icinga/Web/Form.php b/library/Icinga/Web/Form.php index 1f3e0d4bd..daa6dcf40 100644 --- a/library/Icinga/Web/Form.php +++ b/library/Icinga/Web/Form.php @@ -1,6 +1,4 @@ create(); return parent::render($view); } + + /** + * Get the authentication manager + * + * @return Manager + */ + public function Auth() + { + if ($this->auth === null) { + $this->auth = Manager::getInstance(); + } + return $this->auth; + } + + /** + * Whether the current user has the given permission + * + * @param string $permission Name of the permission + * + * @return bool + */ + public function hasPermission($permission) + { + return $this->Auth()->hasPermission($permission); + } + + /** + * Assert that the current user has the given permission + * + * @param string $permission Name of the permission + * + * @throws SecurityException If the current user lacks the given permission + */ + public function assertPermission($permission) + { + if (! $this->Auth()->hasPermission($permission)) { + throw new SecurityException('No permission for %s', $permission); + } + } } diff --git a/library/Icinga/Web/Widget/Dashboard.php b/library/Icinga/Web/Widget/Dashboard.php index 45e1e6830..bb62cc8f9 100644 --- a/library/Icinga/Web/Widget/Dashboard.php +++ b/library/Icinga/Web/Widget/Dashboard.php @@ -6,12 +6,9 @@ namespace Icinga\Web\Widget; use Icinga\Application\Icinga; use Icinga\Application\Config; -use Icinga\Data\ConfigObject; use Icinga\Exception\ConfigurationError; use Icinga\Exception\NotReadableError; use Icinga\Exception\ProgrammingError; -use Icinga\Exception\SystemPermissionException; -use Icinga\File\Ini\IniWriter; use Icinga\User; use Icinga\Web\Widget\Dashboard\Pane; use Icinga\Web\Widget\Dashboard\Dashlet as DashboardDashlet; @@ -86,13 +83,12 @@ class Dashboard extends AbstractWidget } /** - * Create a writer object + * Create and return a Config object for this dashboard * - * @return IniWriter + * @return Config */ - public function createWriter() + public function getConfig() { - $configFile = $this->getConfigFile(); $output = array(); foreach ($this->panes as $pane) { if ($pane->isUserWidget() === true) { @@ -105,17 +101,7 @@ class Dashboard extends AbstractWidget } } - $co = new ConfigObject($output); - $config = new Config($co); - return new IniWriter(array('config' => $config, 'filename' => $configFile)); - } - - /** - * Write user specific dashboards to disk - */ - public function write() - { - $this->createWriter()->write(); + return Config::fromArray($output)->setConfigFile($this->getConfigFile()); } /** @@ -438,28 +424,8 @@ class Dashboard extends AbstractWidget public function getConfigFile() { if ($this->user === null) { - return ''; + throw new ProgrammingError('Can\'t load dashboards. User is not set'); } - - $baseDir = '/var/lib/icingaweb'; - - if (! file_exists($baseDir)) { - throw new NotReadableError('Could not read: ' . $baseDir); - } - - $userDir = $baseDir . '/' . $this->user->getUsername(); - - if (! file_exists($userDir)) { - $success = @mkdir($userDir); - if (!$success) { - throw new SystemPermissionException('Could not create: ' . $userDir); - } - } - - if (! file_exists($userDir)) { - throw new NotReadableError('Could not read: ' . $userDir); - } - - return $userDir . '/dashboard.ini'; + return Config::resolvePath('dashboards/' . $this->user->getUsername() . '/dashboard.ini'); } } diff --git a/library/Icinga/Web/Widget/Tab.php b/library/Icinga/Web/Widget/Tab.php index a06389b98..7eb5abeb1 100644 --- a/library/Icinga/Web/Widget/Tab.php +++ b/library/Icinga/Web/Widget/Tab.php @@ -138,6 +138,16 @@ class Tab extends AbstractWidget $this->url = $url; } + /** + * Get the tab's target URL + * + * @return Url + */ + public function getUrl() + { + return $this->url; + } + /** * Set the parameters to be set for this tabs Url * diff --git a/modules/monitoring/application/controllers/AlertsummaryController.php b/modules/monitoring/application/controllers/AlertsummaryController.php index a4ef76b44..d9cc798a7 100644 --- a/modules/monitoring/application/controllers/AlertsummaryController.php +++ b/modules/monitoring/application/controllers/AlertsummaryController.php @@ -375,7 +375,7 @@ class Monitoring_AlertsummaryController extends Controller $query->order('notification_start_time', 'asc'); - $records = $query->getQuery()->fetchAll(); + $records = $query->getQuery()->fetchAll(); $interval = $this->getInterval(); $period = $this->createPeriod($interval); @@ -582,7 +582,7 @@ class Monitoring_AlertsummaryController extends Controller } elseif ($interval === '1w') { return new DatePeriod($this->getBeginDate($interval), new DateInterval('P1D'), 7); } elseif ($interval === '1m') { - return new DatePeriod($this->getBeginDate($interval), new DateInterval('P1D'), 30); + return new DatePeriod($this->getBeginDate($interval), new DateInterval('P1D'), 31); } elseif ($interval === '1y') { return new DatePeriod($this->getBeginDate($interval), new DateInterval('P1M'), 12); } diff --git a/modules/monitoring/application/controllers/ProcessController.php b/modules/monitoring/application/controllers/ProcessController.php index bb5a4e5c2..5530f9c52 100644 --- a/modules/monitoring/application/controllers/ProcessController.php +++ b/modules/monitoring/application/controllers/ProcessController.php @@ -92,6 +92,7 @@ class Monitoring_ProcessController extends Controller */ public function disableNotificationsAction() { + $this->assertPermission('monitoring/command/feature/instance'); $this->view->title = $this->translate('Disable Notifications'); $programStatus = $this->backend ->select() diff --git a/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php b/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php index 469c1b6eb..60486307e 100644 --- a/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php +++ b/modules/monitoring/application/forms/Command/Instance/ToggleInstanceFeaturesCommandForm.php @@ -59,12 +59,16 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm public function createElements(array $formData = array()) { if ((bool) $this->status->notifications_enabled) { - $notificationDescription = sprintf( - '%s', - $this->translate('Disable notifications for a specific time on a program-wide basis'), - $this->getView()->href('monitoring/process/disable-notifications'), - $this->translate('Disable temporarily') - ); + if ($this->hasPermission('monitoring/command/feature/instance')) { + $notificationDescription = sprintf( + '%s', + $this->translate('Disable notifications for a specific time on a program-wide basis'), + $this->getView()->href('monitoring/process/disable-notifications'), + $this->translate('Disable temporarily') + ); + } else { + $notificationDescription = null; + } } elseif ($this->status->disable_notif_expire_time) { $notificationDescription = sprintf( $this->translate('Notifications will be re-enabled in %s'), @@ -73,13 +77,15 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm } else { $notificationDescription = null; } + $toggleDisabled = $this->hasPermission('monitoring/command/feature/instance') ? null : ''; $this->addElements(array( array( 'checkbox', ToggleInstanceFeatureCommand::FEATURE_ACTIVE_HOST_CHECKS, array( 'label' => $this->translate('Active Host Checks Being Executed'), - 'autosubmit' => true + 'autosubmit' => true, + 'disabled' => $toggleDisabled ) ), array( @@ -87,7 +93,8 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm ToggleInstanceFeatureCommand::FEATURE_ACTIVE_SERVICE_CHECKS, array( 'label' => $this->translate('Active Service Checks Being Executed'), - 'autosubmit' => true + 'autosubmit' => true, + 'disabled' => $toggleDisabled ) ), array( @@ -95,7 +102,8 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm ToggleInstanceFeatureCommand::FEATURE_EVENT_HANDLERS, array( 'label' => $this->translate('Event Handlers Enabled'), - 'autosubmit' => true + 'autosubmit' => true, + 'disabled' => $toggleDisabled ) ), array( @@ -103,7 +111,8 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm ToggleInstanceFeatureCommand::FEATURE_FLAP_DETECTION, array( 'label' => $this->translate('Flap Detection Enabled'), - 'autosubmit' => true + 'autosubmit' => true, + 'disabled' => $toggleDisabled ) ), array( @@ -122,7 +131,8 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm ), 'Label', array('HtmlTag', array('tag' => 'div')) - ) + ), + 'disabled' => $toggleDisabled ) ), array( @@ -130,7 +140,8 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm ToggleInstanceFeatureCommand::FEATURE_HOST_OBSESSING, array( 'label' => $this->translate('Obsessing Over Hosts'), - 'autosubmit' => true + 'autosubmit' => true, + 'disabled' => $toggleDisabled ) ), array( @@ -138,7 +149,8 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm ToggleInstanceFeatureCommand::FEATURE_SERVICE_OBSESSING, array( 'label' => $this->translate('Obsessing Over Services'), - 'autosubmit' => true + 'autosubmit' => true, + 'disabled' => $toggleDisabled ) ), array( @@ -146,7 +158,8 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm ToggleInstanceFeatureCommand::FEATURE_PASSIVE_HOST_CHECKS, array( 'label' => $this->translate('Passive Host Checks Being Accepted'), - 'autosubmit' => true + 'autosubmit' => true, + 'disabled' => $toggleDisabled ) ), array( @@ -154,7 +167,8 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm ToggleInstanceFeatureCommand::FEATURE_PASSIVE_SERVICE_CHECKS, array( 'label' => $this->translate('Passive Service Checks Being Accepted'), - 'autosubmit' => true + 'autosubmit' => true, + 'disabled' => $toggleDisabled ) ), array( @@ -162,7 +176,8 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm ToggleInstanceFeatureCommand::FEATURE_PERFORMANCE_DATA, array( 'label' => $this->translate('Performance Data Being Processed'), - 'autosubmit' => true + 'autosubmit' => true, + 'disabled' => $toggleDisabled ) ) )); @@ -191,6 +206,7 @@ class ToggleInstanceFeaturesCommandForm extends CommandForm */ public function onSuccess() { + $this->assertPermission('monitoring/command/feature/instance'); foreach ($this->getValues() as $feature => $enabled) { $toggleFeature = new ToggleInstanceFeatureCommand(); $toggleFeature diff --git a/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php b/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php index bf0a1d8b1..d3cc63ecf 100644 --- a/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php +++ b/modules/monitoring/application/forms/Command/Object/ToggleObjectFeaturesCommandForm.php @@ -28,13 +28,15 @@ class ToggleObjectFeaturesCommandForm extends ObjectsCommandForm */ public function createElements(array $formData = array()) { + $toggleDisabled = $this->hasPermission('monitoring/command/feature/instance') ? null : ''; $this->addElements(array( array( 'checkbox', ToggleObjectFeatureCommand::FEATURE_ACTIVE_CHECKS, array( 'label' => $this->translate('Active Checks'), - 'autosubmit' => true + 'autosubmit' => true, + 'disabled' => $toggleDisabled ) ), array( @@ -42,7 +44,8 @@ class ToggleObjectFeaturesCommandForm extends ObjectsCommandForm ToggleObjectFeatureCommand::FEATURE_PASSIVE_CHECKS, array( 'label' => $this->translate('Passive Checks'), - 'autosubmit' => true + 'autosubmit' => true, + 'disabled' => $toggleDisabled ) ), array( @@ -50,7 +53,8 @@ class ToggleObjectFeaturesCommandForm extends ObjectsCommandForm ToggleObjectFeatureCommand::FEATURE_OBSESSING, array( 'label' => $this->translate('Obsessing'), - 'autosubmit' => true + 'autosubmit' => true, + 'disabled' => $toggleDisabled ) ), array( @@ -58,7 +62,8 @@ class ToggleObjectFeaturesCommandForm extends ObjectsCommandForm ToggleObjectFeatureCommand::FEATURE_NOTIFICATIONS, array( 'label' => $this->translate('Notifications'), - 'autosubmit' => true + 'autosubmit' => true, + 'disabled' => $toggleDisabled ) ), array( @@ -66,7 +71,8 @@ class ToggleObjectFeaturesCommandForm extends ObjectsCommandForm ToggleObjectFeatureCommand::FEATURE_EVENT_HANDLER, array( 'label' => $this->translate('Event Handler'), - 'autosubmit' => true + 'autosubmit' => true, + 'disabled' => $toggleDisabled ) ), array( @@ -74,7 +80,8 @@ class ToggleObjectFeaturesCommandForm extends ObjectsCommandForm ToggleObjectFeatureCommand::FEATURE_FLAP_DETECTION, array( 'label' => $this->translate('Flap Detection'), - 'autosubmit' => true + 'autosubmit' => true, + 'disabled' => $toggleDisabled ) ) )); @@ -107,6 +114,7 @@ class ToggleObjectFeaturesCommandForm extends ObjectsCommandForm */ public function onSuccess() { + $this->assertPermission('monitoring/command/feature/object'); foreach ($this->objects as $object) { /** @var \Icinga\Module\Monitoring\Object\MonitoredObject $object */ foreach ($this->getValues() as $feature => $enabled) { diff --git a/modules/monitoring/application/views/scripts/config/createbackend.phtml b/modules/monitoring/application/views/scripts/config/createbackend.phtml index 0ef51cd6a..10ee02541 100644 --- a/modules/monitoring/application/views/scripts/config/createbackend.phtml +++ b/modules/monitoring/application/views/scripts/config/createbackend.phtml @@ -1,2 +1,5 @@ +
+ showOnlyCloseButton() ?> +

translate('Add New Backend'); ?>

- \ No newline at end of file + diff --git a/modules/monitoring/application/views/scripts/config/createinstance.phtml b/modules/monitoring/application/views/scripts/config/createinstance.phtml index 49c8b5ec5..3515514df 100644 --- a/modules/monitoring/application/views/scripts/config/createinstance.phtml +++ b/modules/monitoring/application/views/scripts/config/createinstance.phtml @@ -1,2 +1,5 @@ -

translate('Add New Instance'); ?>

- \ No newline at end of file +
+ showOnlyCloseButton() ?> +
+

translate('Add New Instance') ?>

+ diff --git a/modules/monitoring/application/views/scripts/config/editbackend.phtml b/modules/monitoring/application/views/scripts/config/editbackend.phtml index 58b6c18f1..630cb83f2 100644 --- a/modules/monitoring/application/views/scripts/config/editbackend.phtml +++ b/modules/monitoring/application/views/scripts/config/editbackend.phtml @@ -1,2 +1,5 @@ -

translate('Edit Existing Backend'); ?>

- \ No newline at end of file +
+ showOnlyCloseButton() ?> +
+

translate('Edit Existing Backend') ?>

+ diff --git a/modules/monitoring/application/views/scripts/config/editinstance.phtml b/modules/monitoring/application/views/scripts/config/editinstance.phtml index b12f262c3..2c91ee656 100644 --- a/modules/monitoring/application/views/scripts/config/editinstance.phtml +++ b/modules/monitoring/application/views/scripts/config/editinstance.phtml @@ -1,2 +1,5 @@ +
+ showOnlyCloseButton() ?> +

translate('Edit Existing Instance'); ?>

- \ No newline at end of file + diff --git a/modules/monitoring/application/views/scripts/config/removebackend.phtml b/modules/monitoring/application/views/scripts/config/removebackend.phtml index fc7da17e3..d297f615b 100644 --- a/modules/monitoring/application/views/scripts/config/removebackend.phtml +++ b/modules/monitoring/application/views/scripts/config/removebackend.phtml @@ -1,2 +1,5 @@ -

translate('Remove Existing Backend'); ?>

- \ No newline at end of file +
+ showOnlyCloseButton() ?> +
+

translate('Remove Existing Backend') ?>

+ diff --git a/modules/monitoring/application/views/scripts/config/removeinstance.phtml b/modules/monitoring/application/views/scripts/config/removeinstance.phtml index 306d41815..eee29fb02 100644 --- a/modules/monitoring/application/views/scripts/config/removeinstance.phtml +++ b/modules/monitoring/application/views/scripts/config/removeinstance.phtml @@ -1,4 +1,7 @@ +
+ showOnlyCloseButton() ?> +

translate('Remove Existing Instance'); ?>

translate('Are you sure you want to remove this instance?'); ?>

translate('If you have still any environments or views referring to this instance, you won\'t be able to send commands anymore after deletion.'); ?>

- \ No newline at end of file + diff --git a/modules/monitoring/application/views/scripts/list/hostgroups.phtml b/modules/monitoring/application/views/scripts/list/hostgroups.phtml index 9e773c61a..69b30f717 100644 --- a/modules/monitoring/application/views/scripts/list/hostgroups.phtml +++ b/modules/monitoring/application/views/scripts/list/hostgroups.phtml @@ -28,7 +28,7 @@ - + services_critical_last_state_change_unhandled): ?> translate('CRITICAL'); ?> @@ -84,7 +84,7 @@ - services_total; ?> + qlink($h->services_total, 'monitoring/list/services', array('hostgroup' => $h->hostgroup)) ?> services_ok): ?> diff --git a/modules/monitoring/configuration.php b/modules/monitoring/configuration.php index 72df4e73f..f34b4b0f5 100644 --- a/modules/monitoring/configuration.php +++ b/modules/monitoring/configuration.php @@ -47,8 +47,8 @@ $this->providePermission( $this->translate('Allow processing host and service check results') ); $this->providePermission( - 'monitoring/command/feature/program', - $this->translate('Allow processing commands for toggling features on a program-wide basis') + 'monitoring/command/feature/instance', + $this->translate('Allow processing commands for toggling features on an instance-wide basis') ); $this->providePermission( 'monitoring/command/feature/object', @@ -87,29 +87,36 @@ $this->provideSearchUrl($this->translate('Servicegroups'), 'monitoring/list/serv * Problems Section */ $section = $this->menuSection($this->translate('Problems'), array( - 'renderer' => 'ProblemMenuItemRenderer', - 'icon' => 'block', - 'priority' => 20 + 'renderer' => 'ProblemMenuItemRenderer', + 'icon' => 'block', + 'priority' => 20 )); $section->add($this->translate('Unhandled Hosts'), array( - 'renderer' => 'UnhandledHostMenuItemRenderer', - 'url' => 'monitoring/list/hosts?host_problem=1&host_handled=0', - 'priority' => 40 + 'renderer' => 'UnhandledHostMenuItemRenderer', + 'url' => 'monitoring/list/hosts?host_problem=1&host_handled=0', + 'priority' => 30 )); $section->add($this->translate('Unhandled Services'), array( - 'renderer' => 'UnhandledServiceMenuItemRenderer', - 'url' => 'monitoring/list/services?service_problem=1&service_handled=0&sort=service_severity', - 'priority' => 40 + 'renderer' => 'UnhandledServiceMenuItemRenderer', + 'url' => 'monitoring/list/services?service_problem=1&service_handled=0&sort=service_severity', + 'priority' => 40 )); $section->add($this->translate('Host Problems'), array( - 'url' => 'monitoring/list/hosts?host_problem=1&sort=host_severity', - 'priority' => 50 + 'url' => 'monitoring/list/hosts?host_problem=1&sort=host_severity', + 'priority' => 50 )); $section->add($this->translate('Service Problems'), array( - 'url' => 'monitoring/list/services?service_problem=1&sort=service_severity&dir=desc', - 'priority' => 50 + 'url' => 'monitoring/list/services?service_problem=1&sort=service_severity&dir=desc', + 'priority' => 60 +)); +$section->add($this->translate('Service Grid'), array( + 'url' => 'monitoring/list/servicegrid?service_problem=1', + 'priority' => 70 +)); +$section->add($this->translate('Current Downtimes'), array( + 'url' => 'monitoring/list/downtimes?downtime_is_in_effect=1', + 'priority' => 80 )); -$section->add($this->translate('Current Downtimes'))->setUrl('monitoring/list/downtimes?downtime_is_in_effect=1'); /* * Overview Section @@ -130,10 +137,6 @@ $section->add($this->translate('Services'), array( 'url' => 'monitoring/list/services', 'priority' => 50 )); -$section->add($this->translate('Service Grid'), array( - 'url' => 'monitoring/list/servicegrid?service_problem=1', - 'priority' => 51 -)); $section->add($this->translate('Servicegroups'), array( 'url' => 'monitoring/list/servicegroups', 'priority' => 60 diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php index 0cc807ac8..cd7d0aef7 100644 --- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php +++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/StatusQuery.php @@ -518,31 +518,35 @@ class StatusQuery extends IdoQuery protected function joinServiceproblemsummary() { - $sub = new Zend_Db_Expr('(SELECT' - . ' SUM(CASE WHEN (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) > 0 THEN 0 ELSE 1 END) AS unhandled_services_count,' - . ' SUM(CASE WHEN (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) > 0 THEN 1 ELSE 0 END) AS handled_services_count,' - . ' s.host_object_id FROM icinga_servicestatus ss' - . ' JOIN icinga_services s' - . ' ON s.service_object_id = ss.service_object_id' - . ' AND ss.current_state > 0' - . ' JOIN icinga_hoststatus hs' - . ' ON hs.host_object_id = s.host_object_id' - . ' GROUP BY s.host_object_id)'); + $select = <<<'SQL' +SELECT + SUM( + CASE WHEN(ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0)) > 0 + THEN 0 + ELSE 1 + END + ) AS unhandled_services_count, + SUM( + CASE WHEN (ss.problem_has_been_acknowledged + ss.scheduled_downtime_depth + COALESCE(hs.current_state, 0) ) > 0 + THEN 1 + ELSE 0 + END + ) AS handled_services_count, + s.host_object_id +FROM + icinga_servicestatus ss + JOIN icinga_objects o ON o.object_id = ss.service_object_id + JOIN icinga_services s ON s.service_object_id = o.object_id + JOIN icinga_hoststatus hs ON hs.host_object_id = s.host_object_id +WHERE + o.is_active = 1 + AND o.objecttype_id = 2 + AND ss.current_state > 0 +GROUP BY + s.host_object_id +SQL; $this->select->joinLeft( - array('sps' => $sub), - 'sps.host_object_id = hs.host_object_id', - array() - ); - return; - - $this->select->joinleft( - array ('sps' => new \Zend_Db_Expr( - '(SELECT COUNT(s.service_object_id) as unhandled_service_count, s.host_object_id as host_object_id '. - 'FROM icinga_services s INNER JOIN icinga_servicestatus ss ON ss.current_state != 0 AND '. - 'ss.problem_has_been_acknowledged = 0 AND ss.scheduled_downtime_depth = 0 AND '. - 'ss.service_object_id = s.service_object_id '. - 'GROUP BY s.host_object_id'. - ')')), + array('sps' => new Zend_Db_Expr('(' . $select . ')')), 'sps.host_object_id = hs.host_object_id', array() ); diff --git a/modules/monitoring/library/Monitoring/BackendStep.php b/modules/monitoring/library/Monitoring/BackendStep.php index c0dc3d4ea..7f9fc11cf 100644 --- a/modules/monitoring/library/Monitoring/BackendStep.php +++ b/modules/monitoring/library/Monitoring/BackendStep.php @@ -7,7 +7,6 @@ namespace Icinga\Module\Monitoring; use Exception; use Icinga\Module\Setup\Step; use Icinga\Application\Config; -use Icinga\File\Ini\IniWriter; class BackendStep extends Step { @@ -38,11 +37,9 @@ class BackendStep extends Step ); try { - $writer = new IniWriter(array( - 'config' => Config::fromArray($config), - 'filename' => Config::resolvePath('modules/monitoring/backends.ini') - )); - $writer->write(); + Config::fromArray($config) + ->setConfigFile(Config::resolvePath('modules/monitoring/backends.ini')) + ->saveIni(); } catch (Exception $e) { $this->backendIniError = $e; return false; @@ -61,13 +58,7 @@ class BackendStep extends Step try { $config = Config::app('resources', true); $config->setSection($resourceName, $resourceConfig); - - $writer = new IniWriter(array( - 'config' => $config, - 'filename' => Config::resolvePath('resources.ini'), - 'filemode' => 0660 - )); - $writer->write(); + $config->saveIni(); } catch (Exception $e) { $this->resourcesIniError = $e; return false; diff --git a/modules/monitoring/library/Monitoring/InstanceStep.php b/modules/monitoring/library/Monitoring/InstanceStep.php index 2cb8d7d8d..ab54b6548 100644 --- a/modules/monitoring/library/Monitoring/InstanceStep.php +++ b/modules/monitoring/library/Monitoring/InstanceStep.php @@ -7,7 +7,6 @@ namespace Icinga\Module\Monitoring; use Exception; use Icinga\Module\Setup\Step; use Icinga\Application\Config; -use Icinga\File\Ini\IniWriter; class InstanceStep extends Step { @@ -27,11 +26,9 @@ class InstanceStep extends Step unset($instanceConfig['name']); try { - $writer = new IniWriter(array( - 'config' => Config::fromArray(array($instanceName => $instanceConfig)), - 'filename' => Config::resolvePath('modules/monitoring/instances.ini') - )); - $writer->write(); + Config::fromArray(array($instanceName => $instanceConfig)) + ->setConfigFile(Config::resolvePath('modules/monitoring/instances.ini')) + ->saveIni(); } catch (Exception $e) { $this->error = $e; return false; diff --git a/modules/monitoring/library/Monitoring/MonitoringWizard.php b/modules/monitoring/library/Monitoring/MonitoringWizard.php index d7a3671a4..68d686e21 100644 --- a/modules/monitoring/library/Monitoring/MonitoringWizard.php +++ b/modules/monitoring/library/Monitoring/MonitoringWizard.php @@ -4,14 +4,13 @@ namespace Icinga\Module\Monitoring; -use Icinga\Application\Icinga; +use Icinga\Application\Platform; use Icinga\Web\Form; use Icinga\Web\Wizard; use Icinga\Web\Request; use Icinga\Module\Setup\Setup; use Icinga\Module\Setup\SetupWizard; use Icinga\Module\Setup\Requirements; -use Icinga\Module\Setup\Utils\MakeDirStep; use Icinga\Module\Setup\Forms\SummaryPage; use Icinga\Module\Monitoring\Forms\Setup\WelcomePage; use Icinga\Module\Monitoring\Forms\Setup\BackendPage; @@ -108,8 +107,6 @@ class MonitoringWizard extends Wizard implements SetupWizard $pageData = $this->getPageData(); $setup = new Setup(); - $setup->addStep(new MakeDirStep(array(Icinga::app()->getConfigDir() . '/modules/monitoring'), 2770)); - $setup->addStep( new BackendStep(array( 'backendConfig' => $pageData['setup_monitoring_backend'], @@ -139,6 +136,22 @@ class MonitoringWizard extends Wizard implements SetupWizard */ public function getRequirements() { - return new Requirements(); + $requirements = new Requirements(); + + $requirements->addOptional( + 'existing_php_mod_sockets', + mt('monitoring', 'PHP Module: Sockets'), + mt( + 'monitoring', + 'In case it\'s desired that a TCP connection is being used by Icinga Web 2 to' + . ' access a Livestatus interface, the Sockets module for PHP is required.' + ), + Platform::extensionLoaded('sockets'), + Platform::extensionLoaded('sockets') ? mt('monitoring', 'The PHP Module sockets is available.') : ( + mt('monitoring', 'The PHP Module sockets is not available.') + ) + ); + + return $requirements; } } diff --git a/modules/monitoring/library/Monitoring/SecurityStep.php b/modules/monitoring/library/Monitoring/SecurityStep.php index 4a2c0f846..b3eda02c4 100644 --- a/modules/monitoring/library/Monitoring/SecurityStep.php +++ b/modules/monitoring/library/Monitoring/SecurityStep.php @@ -7,7 +7,6 @@ namespace Icinga\Module\Monitoring; use Exception; use Icinga\Module\Setup\Step; use Icinga\Application\Config; -use Icinga\File\Ini\IniWriter; class SecurityStep extends Step { @@ -26,11 +25,9 @@ class SecurityStep extends Step $config['security'] = $this->data['securityConfig']; try { - $writer = new IniWriter(array( - 'config' => Config::fromArray($config), - 'filename' => Config::resolvePath('modules/monitoring/config.ini') - )); - $writer->write(); + Config::fromArray($config) + ->setConfigFile(Config::resolvePath('modules/monitoring/config.ini')) + ->saveIni(); } catch (Exception $e) { $this->error = $e; return false; diff --git a/modules/setup/library/Setup/Steps/AuthenticationStep.php b/modules/setup/library/Setup/Steps/AuthenticationStep.php index ce0b49f58..c06b4e294 100644 --- a/modules/setup/library/Setup/Steps/AuthenticationStep.php +++ b/modules/setup/library/Setup/Steps/AuthenticationStep.php @@ -6,7 +6,6 @@ namespace Icinga\Module\Setup\Steps; use Exception; use Icinga\Application\Config; -use Icinga\File\Ini\IniWriter; use Icinga\Data\ConfigObject; use Icinga\Data\ResourceFactory; use Icinga\Authentication\Backend\DbUserBackend; @@ -50,11 +49,9 @@ class AuthenticationStep extends Step } try { - $writer = new IniWriter(array( - 'config' => Config::fromArray($config), - 'filename' => Config::resolvePath('authentication.ini') - )); - $writer->write(); + Config::fromArray($config) + ->setConfigFile(Config::resolvePath('authentication.ini')) + ->saveIni(); } catch (Exception $e) { $this->authIniError = $e; return false; @@ -73,11 +70,9 @@ class AuthenticationStep extends Step ); try { - $writer = new IniWriter(array( - 'config' => Config::fromArray($config), - 'filename' => Config::resolvePath('permissions.ini') - )); - $writer->write(); + Config::fromArray($config) + ->setConfigFile(Config::resolvePath('permissions.ini')) + ->saveIni(); } catch (Exception $e) { $this->permIniError = $e; return false; diff --git a/modules/setup/library/Setup/Steps/GeneralConfigStep.php b/modules/setup/library/Setup/Steps/GeneralConfigStep.php index 39c160628..bf3d8cb89 100644 --- a/modules/setup/library/Setup/Steps/GeneralConfigStep.php +++ b/modules/setup/library/Setup/Steps/GeneralConfigStep.php @@ -7,7 +7,6 @@ namespace Icinga\Module\Setup\Steps; use Exception; use Icinga\Application\Logger; use Icinga\Application\Config; -use Icinga\File\Ini\IniWriter; use Icinga\Module\Setup\Step; class GeneralConfigStep extends Step @@ -35,11 +34,9 @@ class GeneralConfigStep extends Step } try { - $writer = new IniWriter(array( - 'config' => Config::fromArray($config), - 'filename' => Config::resolvePath('config.ini') - )); - $writer->write(); + Config::fromArray($config) + ->setConfigFile(Config::resolvePath('config.ini')) + ->saveIni(); } catch (Exception $e) { $this->error = $e; return false; diff --git a/modules/setup/library/Setup/Steps/ResourceStep.php b/modules/setup/library/Setup/Steps/ResourceStep.php index c04cdbc79..aae1c3aca 100644 --- a/modules/setup/library/Setup/Steps/ResourceStep.php +++ b/modules/setup/library/Setup/Steps/ResourceStep.php @@ -6,7 +6,6 @@ namespace Icinga\Module\Setup\Steps; use Exception; use Icinga\Application\Config; -use Icinga\File\Ini\IniWriter; use Icinga\Module\Setup\Step; class ResourceStep extends Step @@ -38,12 +37,9 @@ class ResourceStep extends Step } try { - $writer = new IniWriter(array( - 'config' => Config::fromArray($resourceConfig), - 'filename' => Config::resolvePath('resources.ini'), - 'filemode' => 0660 - )); - $writer->write(); + Config::fromArray($resourceConfig) + ->setConfigFile(Config::resolvePath('resources.ini')) + ->saveIni(); } catch (Exception $e) { $this->error = $e; return false; diff --git a/modules/setup/library/Setup/Utils/MakeDirStep.php b/modules/setup/library/Setup/Utils/MakeDirStep.php index d7813541b..b758ccd79 100644 --- a/modules/setup/library/Setup/Utils/MakeDirStep.php +++ b/modules/setup/library/Setup/Utils/MakeDirStep.php @@ -16,12 +16,12 @@ class MakeDirStep extends Step /** * @param array $paths - * @param int $dirmode Directory mode in octal notation + * @param int $dirmode */ public function __construct($paths, $dirmode) { $this->paths = $paths; - $this->dirmode = octdec((string) $dirmode); + $this->dirmode = $dirmode; $this->errors = array(); } diff --git a/modules/setup/library/Setup/WebWizard.php b/modules/setup/library/Setup/WebWizard.php index 8ae7344ea..b580101ed 100644 --- a/modules/setup/library/Setup/WebWizard.php +++ b/modules/setup/library/Setup/WebWizard.php @@ -334,18 +334,6 @@ class WebWizard extends Wizard implements SetupWizard ); } - $configDir = Icinga::app()->getConfigDir(); - $setup->addStep( - new MakeDirStep( - array( - $configDir . DIRECTORY_SEPARATOR . 'modules', - $configDir . DIRECTORY_SEPARATOR . 'preferences', - $configDir . DIRECTORY_SEPARATOR . 'enabledModules' - ), - 2770 - ) - ); - foreach ($this->getWizards() as $wizard) { if ($wizard->isComplete()) { $setup->addSteps($wizard->getSetup()->getSteps()); diff --git a/public/js/icinga/loader.js b/public/js/icinga/loader.js index 31a0d10a8..1a8aadca5 100644 --- a/public/js/icinga/loader.js +++ b/public/js/icinga/loader.js @@ -24,8 +24,6 @@ this.failureNotice = null; - this.exception = null; - /** * Pending requests */ @@ -109,7 +107,8 @@ url : url, data : data, headers: headers, - context: self + context: self, + failure: false }); req.$target = $target; @@ -313,15 +312,12 @@ onResponse: function (data, textStatus, req) { var self = this; if (this.failureNotice !== null) { - this.failureNotice.remove(); + if (! this.failureNotice.hasClass('fading-out')) { + this.failureNotice.remove(); + } this.failureNotice = null; } - if (this.exception !== null) { - this.exception.remove(); - this.exception = null; - } - // Remove 'impact' class if there was such if (req.$target.hasClass('impact')) { req.$target.removeClass('impact'); @@ -346,42 +342,11 @@ var rendered = false; var classes; - if (! req.autorefresh) { - // TODO: Hook for response/url? - var $forms = $('[action="' + this.icinga.utils.parseUrl(url).path + '"]'); - var $matches = $.merge($('[href="' + url + '"]'), $forms); - $matches.each(function (idx, el) { - if ($(el).closest('#menu').length) { - if (req.$target[0].id === 'col1') { - self.icinga.behaviors.navigation.resetActive(); - } - } else if ($(el).closest('table.action').length) { - $(el).closest('table.action').find('.active').removeClass('active'); - } - }); - - $matches.each(function (idx, el) { - var $el = $(el); - if ($el.closest('#menu').length) { - if ($el.is('form')) { - $('input', $el).addClass('active'); - } else { - if (req.$target[0].id === 'col1') { - self.icinga.behaviors.navigation.setActive($el); - } - } - // Interrupt .each, only on menu item shall be active - return false; - } else if ($(el).closest('table.action').length) { - $el.addClass('active'); - } - }); - } else { + if (req.autorefresh) { // TODO: next container url active = $('[href].active', req.$target).attr('href'); } - var target = req.getResponseHeader('X-Icinga-Container'); var newBody = false; var oldNotifications = false; @@ -555,10 +520,43 @@ * Regardless of whether a request succeeded of failed, clean up */ onComplete: function (req, textStatus) { + if (! req.autorefresh) { + // TODO: Hook for response/url? + var url = req.url; + var $forms = $('[action="' + this.icinga.utils.parseUrl(url).path + '"]'); + var $matches = $.merge($('[href="' + url + '"]'), $forms); + $matches.each(function (idx, el) { + if ($(el).closest('#menu').length) { + if (req.$target[0].id === 'col1') { + self.icinga.behaviors.navigation.resetActive(); + } + } else if ($(el).closest('table.action').length) { + $(el).closest('table.action').find('.active').removeClass('active'); + } + }); + + $matches.each(function (idx, el) { + var $el = $(el); + if ($el.closest('#menu').length) { + if ($el.is('form')) { + $('input', $el).addClass('active'); + } else { + if (req.$target[0].id === 'col1') { + self.icinga.behaviors.navigation.setActive($el); + } + } + // Interrupt .each, only on menu item shall be active + return false; + } else if ($(el).closest('table.action').length) { + $el.addClass('active'); + } + }); + } + // Update history when necessary. Don't do so for requests triggered // by history or autorefresh events if (! req.historyTriggered && ! req.autorefresh) { - if (req.$target.hasClass('container')) { + if (req.$target.hasClass('container') && req.failure === false) { // We only want to care about top-level containers if (req.$target.parent().closest('.container').length === 0) { this.icinga.history.pushCurrentState(); @@ -594,18 +592,18 @@ onFailure: function (req, textStatus, errorThrown) { var url = req.url; - if (req.status === 500) { - if (this.exception === null) { - req.$target.addClass('impact'); + req.failure = true; - this.exception = this.createNotice( - 'error', - $('h1', $(req.responseText)).first().html(), - true - ); - this.icinga.ui.fixControls(); - } - } else if (req.status > 0) { + /* + * Test if a manual actions comes in and autorefresh is active: Stop refreshing + */ + if (! req.historyTriggered && ! req.autorefresh && req.$target.data('icingaRefresh') > 0 + && req.$target.data('icingaUrl') !== url) { + req.$target.data('icingaRefresh', 0); + req.$target.data('icingaUrl', url); + } + + if (req.status > 0) { this.icinga.logger.error( req.status, errorThrown + ':', @@ -617,9 +615,6 @@ req.action, req.autorefresh ); - - // Header example: - // Icinga.debug(req.getResponseHeader('X-Icinga-Redirect')); } else { if (errorThrown === 'abort') { this.icinga.logger.debug( @@ -660,7 +655,13 @@ var $notice = $( '
  • ' + message + '
  • ' ).appendTo($('#notifications')); + this.icinga.ui.fixControls(); + + if (!persist) { + this.icinga.ui.fadeNotificationsAway(); + } + return $notice; }, @@ -721,11 +722,12 @@ // $container.html(content); } else { - if ($container.closest('.dashboard').length && - ! $('h1', $content).length + if ($container.closest('.dashboard').length ) { - var title = $('h1', $container).first().detach(); - $('h1', $content).first().detach(); + if (! $('h1', $content).length) { + var title = $('h1', $container).first().detach(); + $('h1', $content).first().detach(); + } $container.html(title).append(content); } else if (action === 'replace') { $container.html(content);