Merge branch 'master' into feature/redesign-7144

Conflicts:
	application/views/scripts/authentication/login.phtml
	public/css/icinga/login.less
This commit is contained in:
Bernd Erk 2014-11-12 17:21:54 +01:00
commit ac8cc0613c
150 changed files with 9002 additions and 1037 deletions

View File

@ -601,27 +601,15 @@ exec { 'create-pgsql-icingaweb-db':
require => Service['postgresql'] require => Service['postgresql']
} }
exec { 'populate-icingaweb-mysql-db-accounts': exec { 'populate-icingaweb-mysql-db-tables':
unless => 'mysql -uicingaweb -picingaweb icingaweb -e "SELECT * FROM account;" &> /dev/null', unless => 'mysql -uicingaweb -picingaweb icingaweb -e "SELECT * FROM account;" &> /dev/null',
command => 'mysql -uicingaweb -picingaweb icingaweb < /vagrant/etc/schema/accounts.mysql.sql', command => 'mysql -uicingaweb -picingaweb icingaweb < /vagrant/etc/schema/mysql.sql',
require => [ Exec['create-mysql-icingaweb-db'] ] require => [ Exec['create-mysql-icingaweb-db'] ]
} }
exec { 'populate-icingweba-pgsql-db-accounts': exec { 'populate-icingweba-pgsql-db-tables':
unless => 'psql -U icingaweb -d icingaweb -c "SELECT * FROM account;" &> /dev/null', unless => 'psql -U icingaweb -d icingaweb -c "SELECT * FROM account;" &> /dev/null',
command => 'sudo -u postgres psql -U icingaweb -d icingaweb -f /vagrant/etc/schema/accounts.pgsql.sql', command => 'sudo -u postgres psql -U icingaweb -d icingaweb -f /vagrant/etc/schema/pgsql.sql',
require => [ Exec['create-pgsql-icingaweb-db'] ]
}
exec { 'populate-icingaweb-mysql-db-preferences':
unless => 'mysql -uicingaweb -picingaweb icingaweb -e "SELECT * FROM preference;" &> /dev/null',
command => 'mysql -uicingaweb -picingaweb icingaweb < /vagrant/etc/schema/preferences.mysql.sql',
require => [ Exec['create-mysql-icingaweb-db'] ]
}
exec { 'populate-icingweba-pgsql-db-preferences':
unless => 'psql -U icingaweb -d icingaweb -c "SELECT * FROM preference;" &> /dev/null',
command => 'sudo -u postgres psql -U icingaweb -d icingaweb -f /vagrant/etc/schema/preferences.pgsql.sql',
require => [ Exec['create-pgsql-icingaweb-db'] ] require => [ Exec['create-pgsql-icingaweb-db'] ]
} }

View File

@ -33,6 +33,17 @@ class AuthenticationController extends ActionController
*/ */
public function loginAction() public function loginAction()
{ {
if (@file_exists(Config::$configDir . '/setup.token')) {
try {
$config = Config::app()->toArray();
if (empty($config)) {
$this->redirectNow(Url::fromPath('setup'));
}
} catch (NotReadableError $e) {
// Gets thrown in case of insufficient permission only
}
}
$auth = $this->Auth(); $auth = $this->Auth();
$this->view->form = $form = new LoginForm(); $this->view->form = $form = new LoginForm();
$this->view->title = $this->translate('Icingaweb Login'); $this->view->title = $this->translate('Icingaweb Login');
@ -93,30 +104,30 @@ class AuthenticationController extends ActionController
} }
} }
if ($backendsTried === 0) { if ($backendsTried === 0) {
throw new ConfigurationError( $this->view->form->addError(
$this->translate( $this->translate(
'No authentication methods available. Did you create' 'No authentication methods available. Did you create'
. ' authentication.ini when installing Icinga Web 2?' . ' authentication.ini when setting up Icinga Web 2?'
) )
); );
} } else if ($backendsTried === $backendsWithError) {
if ($backendsTried === $backendsWithError) { $this->view->form->addError(
throw new ConfigurationError(
$this->translate( $this->translate(
'All configured authentication methods failed.' 'All configured authentication methods failed.'
. ' Please check the system log or Icinga Web 2 log for more information.' . ' Please check the system log or Icinga Web 2 log for more information.'
) )
); );
} } elseif ($backendsWithError) {
if ($backendsWithError) { $this->view->form->addError(
$this->view->form->getElement('username')->addError(
$this->translate( $this->translate(
'Please note that not all authentication methods were available.' 'Please note that not all authentication methods were available.'
. ' Check the system log or Icinga Web 2 log for more information.' . ' Check the system log or Icinga Web 2 log for more information.'
) )
); );
} }
$this->view->form->getElement('password')->addError($this->translate('Incorrect username or password')); if ($backendsTried > 0 && $backendsTried !== $backendsWithError) {
$this->view->form->getElement('password')->addError($this->translate('Incorrect username or password'));
}
} elseif ($request->isGet()) { } elseif ($request->isGet()) {
$user = new User(''); $user = new User('');
foreach ($chain as $backend) { foreach ($chain as $backend) {
@ -134,6 +145,8 @@ class AuthenticationController extends ActionController
} catch (Exception $e) { } catch (Exception $e) {
$this->view->errorInfo = $e->getMessage(); $this->view->errorInfo = $e->getMessage();
} }
$this->view->configMissing = is_dir(Config::$configDir) === false;
} }
/** /**

View File

@ -99,7 +99,7 @@ class ConfigController extends ActionController
Notification::success(sprintf($this->translate('Module "%s" enabled'), $module)); Notification::success(sprintf($this->translate('Module "%s" enabled'), $module));
$this->rerenderLayout()->reloadCss()->redirectNow('config/modules'); $this->rerenderLayout()->reloadCss()->redirectNow('config/modules');
} catch (Exception $e) { } catch (Exception $e) {
$this->view->exceptionMesssage = $e->getMessage(); $this->view->exceptionMessage = $e->getMessage();
$this->view->moduleName = $module; $this->view->moduleName = $module;
$this->view->action = 'enable'; $this->view->action = 'enable';
$this->render('module-configuration-error'); $this->render('module-configuration-error');

View File

@ -31,7 +31,9 @@ class AutologinBackendForm extends Form
array( array(
'required' => true, 'required' => true,
'label' => t('Backend Name'), 'label' => t('Backend Name'),
'description' => t('The name of this authentication backend'), 'description' => t(
'The name of this authentication provider that is used to differentiate it from others'
),
'validators' => array( 'validators' => array(
array( array(
'Regex', 'Regex',
@ -50,9 +52,8 @@ class AutologinBackendForm extends Form
'text', 'text',
'strip_username_regexp', 'strip_username_regexp',
array( array(
'required' => true, 'label' => t('Filter Pattern'),
'label' => t('Backend Domain Pattern'), 'description' => t('The regular expression to use to strip specific parts off from usernames. Leave empty if you do not want to strip off anything'),
'description' => t('The domain pattern of this authentication backend'),
'value' => '/\@[^$]+$/', 'value' => '/\@[^$]+$/',
'validators' => array( 'validators' => array(
new Zend_Validate_Callback(function ($value) { new Zend_Validate_Callback(function ($value) {
@ -82,7 +83,7 @@ class AutologinBackendForm extends Form
* *
* @return bool Whether validation succeeded or not * @return bool Whether validation succeeded or not
*/ */
public function isValidAuthenticationBackend(Form $form) public static function isValidAuthenticationBackend(Form $form)
{ {
return true; return true;
} }

View File

@ -5,6 +5,7 @@
namespace Icinga\Form\Config\Authentication; namespace Icinga\Form\Config\Authentication;
use Exception; use Exception;
use Icinga\Application\Config;
use Icinga\Web\Form; use Icinga\Web\Form;
use Icinga\Web\Request; use Icinga\Web\Request;
use Icinga\Data\ResourceFactory; use Icinga\Data\ResourceFactory;
@ -54,7 +55,9 @@ class DbBackendForm extends Form
array( array(
'required' => true, 'required' => true,
'label' => t('Backend Name'), 'label' => t('Backend Name'),
'description' => t('The name of this authentication provider'), 'description' => t(
'The name of this authentication provider that is used to differentiate it from others'
),
) )
); );
$this->addElement( $this->addElement(
@ -88,7 +91,7 @@ class DbBackendForm extends Form
*/ */
public function onSuccess(Request $request) public function onSuccess(Request $request)
{ {
if (false === $this->isValidAuthenticationBackend($this)) { if (false === static::isValidAuthenticationBackend($this)) {
return false; return false;
} }
} }
@ -100,21 +103,29 @@ class DbBackendForm extends Form
* *
* @return bool Whether validation succeeded or not * @return bool Whether validation succeeded or not
*/ */
public function isValidAuthenticationBackend(Form $form) public static function isValidAuthenticationBackend(Form $form)
{ {
$element = $form->getElement('resource');
try { try {
$dbUserBackend = new DbUserBackend(ResourceFactory::create($element->getValue())); $dbUserBackend = new DbUserBackend(ResourceFactory::createResource($form->getResourceConfig()));
if ($dbUserBackend->count() < 1) { if ($dbUserBackend->count() < 1) {
$element->addError(t('No users found under the specified database backend')); $form->addError(t('No users found under the specified database backend'));
return false; return false;
} }
} catch (Exception $e) { } catch (Exception $e) {
$element->addError(sprintf(t('Using the specified backend failed: %s'), $e->getMessage())); $form->addError(sprintf(t('Using the specified backend failed: %s'), $e->getMessage()));
return false; return false;
} }
return true; return true;
} }
/**
* Return the configuration for the chosen resource
*
* @return Config
*/
public function getResourceConfig()
{
return ResourceFactory::getResourceConfig($this->getValue('resource'));
}
} }

View File

@ -5,9 +5,11 @@
namespace Icinga\Form\Config\Authentication; namespace Icinga\Form\Config\Authentication;
use Exception; use Exception;
use Icinga\Application\Config;
use Icinga\Web\Form; use Icinga\Web\Form;
use Icinga\Web\Request; use Icinga\Web\Request;
use Icinga\Data\ResourceFactory; use Icinga\Data\ResourceFactory;
use Icinga\Exception\AuthenticationException;
use Icinga\Authentication\Backend\LdapUserBackend; use Icinga\Authentication\Backend\LdapUserBackend;
/** /**
@ -54,7 +56,9 @@ class LdapBackendForm extends Form
array( array(
'required' => true, 'required' => true,
'label' => t('Backend Name'), 'label' => t('Backend Name'),
'description' => t('The name of this authentication backend') 'description' => t(
'The name of this authentication provider that is used to differentiate it from others'
)
) )
); );
$this->addElement( $this->addElement(
@ -97,7 +101,16 @@ class LdapBackendForm extends Form
'value' => 'ldap' 'value' => 'ldap'
) )
); );
$this->addElement(
'text',
'base_dn',
array(
'required' => false,
'label' => t('Base DN'),
'description' => t('The path where users can be found on the ldap server. ' .
' Leave empty to select all users available on the specified resource.')
)
);
return $this; return $this;
} }
@ -108,7 +121,7 @@ class LdapBackendForm extends Form
*/ */
public function onSuccess(Request $request) public function onSuccess(Request $request)
{ {
if (false === $this->isValidAuthenticationBackend($this)) { if (false === static::isValidAuthenticationBackend($this)) {
return false; return false;
} }
} }
@ -120,22 +133,34 @@ class LdapBackendForm extends Form
* *
* @return bool Whether validation succeeded or not * @return bool Whether validation succeeded or not
*/ */
public function isValidAuthenticationBackend(Form $form) public static function isValidAuthenticationBackend(Form $form)
{ {
$element = $form->getElement('resource');
try { try {
$ldapUserBackend = new LdapUserBackend( $ldapUserBackend = new LdapUserBackend(
ResourceFactory::create($element->getValue()), ResourceFactory::createResource($form->getResourceConfig()),
$form->getElement('user_class')->getValue(), $form->getElement('user_class')->getValue(),
$form->getElement('user_name_attribute')->getValue() $form->getElement('user_name_attribute')->getValue(),
$form->getElement('base_dn')->getValue()
); );
$ldapUserBackend->assertAuthenticationPossible(); $ldapUserBackend->assertAuthenticationPossible();
} catch (AuthenticationException $e) {
$form->addError($e->getMessage());
return false;
} catch (Exception $e) { } catch (Exception $e) {
$element->addError(sprintf(t('Connection validation failed: %s'), $e->getMessage())); $form->addError(sprintf(t('Unable to validate authentication: %s'), $e->getMessage()));
return false; return false;
} }
return true; return true;
} }
/**
* Return the configuration for the chosen resource
*
* @return Config
*/
public function getResourceConfig()
{
return ResourceFactory::getResourceConfig($this->getValue('resource'));
}
} }

View File

@ -10,6 +10,7 @@ use Icinga\Form\ConfigForm;
use Icinga\Web\Notification; use Icinga\Web\Notification;
use Icinga\Application\Config; use Icinga\Application\Config;
use Icinga\Application\Platform; use Icinga\Application\Platform;
use Icinga\Data\ResourceFactory;
use Icinga\Exception\ConfigurationError; use Icinga\Exception\ConfigurationError;
use Icinga\Form\Config\Authentication\DbBackendForm; use Icinga\Form\Config\Authentication\DbBackendForm;
use Icinga\Form\Config\Authentication\LdapBackendForm; use Icinga\Form\Config\Authentication\LdapBackendForm;
@ -39,8 +40,6 @@ class AuthenticationBackendConfigForm extends ConfigForm
* @param Config $resources The resource configuration * @param Config $resources The resource configuration
* *
* @return self * @return self
*
* @throws ConfigurationError In case no resources are available for authentication
*/ */
public function setResourceConfig(Config $resourceConfig) public function setResourceConfig(Config $resourceConfig)
{ {
@ -49,10 +48,6 @@ class AuthenticationBackendConfigForm extends ConfigForm
$resources[strtolower($resource->type)][] = $name; $resources[strtolower($resource->type)][] = $name;
} }
if (empty($resources)) {
throw new ConfigurationError(t('Could not find any resources for authentication'));
}
$this->resources = $resources; $this->resources = $resources;
return $this; return $this;
} }
@ -201,7 +196,7 @@ class AuthenticationBackendConfigForm extends ConfigForm
{ {
if (($el = $this->getElement('force_creation')) === null || false === $el->isChecked()) { if (($el = $this->getElement('force_creation')) === null || false === $el->isChecked()) {
$backendForm = $this->getBackendForm($this->getElement('type')->getValue()); $backendForm = $this->getBackendForm($this->getElement('type')->getValue());
if (false === $backendForm->isValidAuthenticationBackend($this)) { if (false === $backendForm::isValidAuthenticationBackend($this)) {
$this->addElement($this->getForceCreationCheckbox()); $this->addElement($this->getForceCreationCheckbox());
return false; return false;
} }
@ -251,6 +246,17 @@ class AuthenticationBackendConfigForm extends ConfigForm
$configValues['type'] = $configValues['backend']; $configValues['type'] = $configValues['backend'];
$configValues['name'] = $authBackend; $configValues['name'] = $authBackend;
$this->populate($configValues); $this->populate($configValues);
} elseif (empty($this->resources)) {
$autologinBackends = array_filter(
$this->config->toArray(),
function ($authBackendCfg) {
return isset($authBackendCfg['backend']) && $authBackendCfg['backend'] === 'autologin';
}
);
if (false === empty($autologinBackends)) {
throw new ConfigurationError(t('Could not find any resources for authentication'));
}
} }
} }
@ -280,7 +286,7 @@ class AuthenticationBackendConfigForm extends ConfigForm
public function createElements(array $formData) public function createElements(array $formData)
{ {
$backendTypes = array(); $backendTypes = array();
$backendType = isset($formData['type']) ? $formData['type'] : 'db'; $backendType = isset($formData['type']) ? $formData['type'] : null;
if (isset($this->resources['db'])) { if (isset($this->resources['db'])) {
$backendTypes['db'] = t('Database'); $backendTypes['db'] = t('Database');
@ -299,6 +305,10 @@ class AuthenticationBackendConfigForm extends ConfigForm
$backendTypes['autologin'] = t('Autologin'); $backendTypes['autologin'] = t('Autologin');
} }
if ($backendType === null) {
$backendType = key($backendTypes);
}
$this->addElement( $this->addElement(
'select', 'select',
'type', 'type',
@ -307,7 +317,7 @@ class AuthenticationBackendConfigForm extends ConfigForm
'required' => true, 'required' => true,
'autosubmit' => true, 'autosubmit' => true,
'label' => t('Backend Type'), 'label' => t('Backend Type'),
'description' => t('The type of the resource to use for this authenticaton backend'), 'description' => t('The type of the resource to use for this authenticaton provider'),
'multiOptions' => $backendTypes 'multiOptions' => $backendTypes
) )
); );
@ -319,4 +329,14 @@ class AuthenticationBackendConfigForm extends ConfigForm
$this->addElements($this->getBackendForm($backendType)->createElements($formData)->getElements()); $this->addElements($this->getBackendForm($backendType)->createElements($formData)->getElements());
} }
/**
* Return the configuration for the chosen resource
*
* @return Config
*/
public function getResourceConfig()
{
return ResourceFactory::getResourceConfig($this->getValue('resource'));
}
} }

View File

@ -30,13 +30,13 @@ class LoggingConfigForm extends Form
'logging_log', 'logging_log',
array( array(
'required' => true, 'required' => true,
'class' => 'autosubmit', 'autosubmit' => true,
'label' => t('Logging Type'), 'label' => t('Logging Type'),
'description' => t('The type of logging to utilize.'), 'description' => t('The type of logging to utilize.'),
'multiOptions' => array( 'multiOptions' => array(
'syslog' => 'Syslog', 'syslog' => 'Syslog',
'file' => t('File'), 'file' => t('File', 'app.config.logging.type'),
'none' => t('None') 'none' => t('None', 'app.config.logging.type')
) )
) )
); );
@ -50,16 +50,16 @@ class LoggingConfigForm extends Form
'label' => t('Logging Level'), 'label' => t('Logging Level'),
'description' => t('The maximum logging level to emit.'), 'description' => t('The maximum logging level to emit.'),
'multiOptions' => array( 'multiOptions' => array(
Logger::$levels[Logger::ERROR] => t('Error'), Logger::$levels[Logger::ERROR] => t('Error', 'app.config.logging.level'),
Logger::$levels[Logger::WARNING] => t('Warning'), Logger::$levels[Logger::WARNING] => t('Warning', 'app.config.logging.level'),
Logger::$levels[Logger::INFO] => t('Information'), Logger::$levels[Logger::INFO] => t('Information', 'app.config.logging.level'),
Logger::$levels[Logger::DEBUG] => t('Debug') Logger::$levels[Logger::DEBUG] => t('Debug', 'app.config.logging.level')
) )
) )
); );
} }
if (isset($formData['logging_log']) && $formData['logging_log'] === 'syslog') { if (false === isset($formData['logging_log']) || $formData['logging_log'] === 'syslog') {
$this->addElement( $this->addElement(
'text', 'text',
'logging_application', 'logging_application',

View File

@ -10,6 +10,7 @@ use Icinga\Web\Form;
use Icinga\Web\Request; use Icinga\Web\Request;
use Icinga\Web\Form\Element\Number; use Icinga\Web\Form\Element\Number;
use Icinga\Data\ResourceFactory; use Icinga\Data\ResourceFactory;
use Icinga\Application\Platform;
/** /**
* Form class for adding/modifying database resources * Form class for adding/modifying database resources
@ -29,6 +30,23 @@ class DbResourceForm extends Form
*/ */
public function createElements(array $formData) public function createElements(array $formData)
{ {
$dbChoices = array();
if (Platform::zendClassExists('Zend_Db_Adapter_Pdo_Mysql')) {
$dbChoices['mysql'] = 'MySQL';
}
if (Platform::zendClassExists('Zend_Db_Adapter_Pdo_Pgsql')) {
$dbChoices['pgsql'] = 'PostgreSQL';
}
$this->addElement(
'text',
'name',
array(
'required' => true,
'label' => t('Resource Name'),
'description' => t('The unique name of this resource')
)
);
$this->addElement( $this->addElement(
'select', 'select',
'db', 'db',
@ -36,11 +54,7 @@ class DbResourceForm extends Form
'required' => true, 'required' => true,
'label' => t('Database Type'), 'label' => t('Database Type'),
'description' => t('The type of SQL database'), 'description' => t('The type of SQL database'),
'multiOptions' => array( 'multiOptions' => $dbChoices
'mysql' => 'MySQL',
'pgsql' => 'PostgreSQL'
//'oracle' => 'Oracle'
)
) )
); );
$this->addElement( $this->addElement(
@ -103,7 +117,7 @@ class DbResourceForm extends Form
*/ */
public function onSuccess(Request $request) public function onSuccess(Request $request)
{ {
if (false === $this->isValidResource($this)) { if (false === static::isValidResource($this)) {
return false; return false;
} }
} }
@ -115,7 +129,7 @@ class DbResourceForm extends Form
* *
* @return bool Whether validation succeeded or not * @return bool Whether validation succeeded or not
*/ */
public function isValidResource(Form $form) public static function isValidResource(Form $form)
{ {
try { try {
$resource = ResourceFactory::createResource(new Config($form->getValues())); $resource = ResourceFactory::createResource(new Config($form->getValues()));

View File

@ -25,6 +25,15 @@ class FileResourceForm extends Form
*/ */
public function createElements(array $formData) public function createElements(array $formData)
{ {
$this->addElement(
'text',
'name',
array(
'required' => true,
'label' => t('Resource Name'),
'description' => t('The unique name of this resource')
)
);
$this->addElement( $this->addElement(
'text', 'text',
'filename', 'filename',

View File

@ -29,6 +29,15 @@ class LdapResourceForm extends Form
*/ */
public function createElements(array $formData) public function createElements(array $formData)
{ {
$this->addElement(
'text',
'name',
array(
'required' => true,
'label' => t('Resource Name'),
'description' => t('The unique name of this resource')
)
);
$this->addElement( $this->addElement(
'text', 'text',
'hostname', 'hostname',
@ -56,7 +65,7 @@ class LdapResourceForm extends Form
array( array(
'required' => true, 'required' => true,
'label' => t('Root DN'), 'label' => t('Root DN'),
'description' => t('The path where users can be found on the ldap server') 'description' => t('Only the root and its child nodes will be accessible on this resource.')
) )
); );
$this->addElement( $this->addElement(
@ -89,7 +98,7 @@ class LdapResourceForm extends Form
*/ */
public function onSuccess(Request $request) public function onSuccess(Request $request)
{ {
if (false === $this->isValidResource($this)) { if (false === static::isValidResource($this)) {
return false; return false;
} }
} }
@ -101,11 +110,17 @@ class LdapResourceForm extends Form
* *
* @return bool Whether validation succeeded or not * @return bool Whether validation succeeded or not
*/ */
public function isValidResource(Form $form) public static function isValidResource(Form $form)
{ {
try { try {
$resource = ResourceFactory::createResource(new Config($form->getValues())); $resource = ResourceFactory::createResource(new Config($form->getValues()));
$resource->connect(); if (false === $resource->testCredentials(
$form->getElement('bind_dn')->getValue(),
$form->getElement('bind_pw')->getValue()
)
) {
throw new Exception();
}
} catch (Exception $e) { } catch (Exception $e) {
$form->addError(t('Connectivity validation failed, connection to the given resource not possible.')); $form->addError(t('Connectivity validation failed, connection to the given resource not possible.'));
return false; return false;

View File

@ -29,6 +29,15 @@ class LivestatusResourceForm extends Form
*/ */
public function createElements(array $formData) public function createElements(array $formData)
{ {
$this->addElement(
'text',
'name',
array(
'required' => true,
'label' => t('Resource Name'),
'description' => t('The unique name of this resource')
)
);
$this->addElement( $this->addElement(
'text', 'text',
'socket', 'socket',
@ -50,7 +59,7 @@ class LivestatusResourceForm extends Form
*/ */
public function onSuccess(Request $request) public function onSuccess(Request $request)
{ {
if (false === $this->isValidResource($this)) { if (false === static::isValidResource($this)) {
return false; return false;
} }
} }
@ -62,7 +71,7 @@ class LivestatusResourceForm extends Form
* *
* @return bool Whether validation succeeded or not * @return bool Whether validation succeeded or not
*/ */
public function isValidResource(Form $form) public static function isValidResource(Form $form)
{ {
try { try {
$resource = ResourceFactory::createResource(new Config($form->getValues())); $resource = ResourceFactory::createResource(new Config($form->getValues()));

View File

@ -132,7 +132,7 @@ class ResourceConfigForm extends ConfigForm
{ {
if (($el = $this->getElement('force_creation')) === null || false === $el->isChecked()) { if (($el = $this->getElement('force_creation')) === null || false === $el->isChecked()) {
$resourceForm = $this->getResourceForm($this->getElement('type')->getValue()); $resourceForm = $this->getResourceForm($this->getElement('type')->getValue());
if (method_exists($resourceForm, 'isValidResource') && false === $resourceForm->isValidResource($this)) { if (method_exists($resourceForm, 'isValidResource') && false === $resourceForm::isValidResource($this)) {
$this->addElement($this->getForceCreationCheckbox()); $this->addElement($this->getForceCreationCheckbox());
return false; return false;
} }
@ -220,15 +220,6 @@ class ResourceConfigForm extends ConfigForm
$resourceTypes['db'] = t('SQL Database'); $resourceTypes['db'] = t('SQL Database');
} }
$this->addElement(
'text',
'name',
array(
'required' => true,
'label' => t('Resource Name'),
'description' => t('The unique name of this resource')
)
);
$this->addElement( $this->addElement(
'select', 'select',
'type', 'type',

View File

@ -0,0 +1,213 @@
<?php
namespace Icinga\Form;
use Icinga\Application\Config;
use Icinga\Application\Logger;
use Icinga\Protocol\Ldap\Exception as LdapException;
use Icinga\Protocol\Ldap\Connection;
use Icinga\Protocol\Dns;
use Icinga\Web\Form\Element\Note;
use Icinga\Web\Form;
/**
* Form class for application-wide and logging specific settings
*/
class LdapDiscoveryForm extends Form
{
/**
* The discovered server settings
*
* @var array
*/
private $capabilities = null;
/**
* The discovered root_dn
*
* @var null
*/
private $namingContext = null;
/**
* The working domain name
*
* @var null
*/
private $domain = null;
/**
* The working port name
*
* @var int
*/
private $port = 389;
/**
* Initialize this page
*/
public function init()
{
$this->setName('form_ldap_discovery');
}
/**
* @see Form::createElements()
*/
public function createElements(array $formData)
{
$this->addElement(
'text',
'domain',
array(
'required' => true,
'label' => t('Search Domain'),
'description' => t('Search this domain for records of available servers.'),
)
);
if (false) {
$this->addElement(
new Note(
'additional_description',
array(
'value' => t('No Ldap servers found on this domain.'
. ' You can try to specify host and port and try again, or just skip this step and '
. 'configure the server manually.'
)
)
)
);
$this->addElement(
'text',
'hostname',
array(
'required' => false,
'label' => t('Host'),
'description' => t('IP or host name to search.'),
)
);
$this->addElement(
'text',
'port',
array(
'required' => false,
'label' => t('Port'),
'description' => t('Port', 389),
)
);
}
return $this;
}
public function isValid($data)
{
if (false === parent::isValid($data)) {
return false;
}
if ($this->discover($this->getValue('domain'))) {
return true;
}
return true;
}
private function discover($domain)
{
// Attempt 1: Connect to the domain directly
if ($this->discoverCapabilities(array(
'hostname' => $domain,
'port' => 389)
)) {
return true;
}
// Attempt 2: Discover all available ldap dns records and connect to the first one
$cap = false;
$records = array_merge(Dns::getSrvRecords($domain, 'ldap'), Dns::getSrvRecords($domain, 'ldaps'));
if (isset($records[0])) {
$record = $records[0];
if (isset($record['port'])) {
$cap = $this->discoverCapabilities(array(
'hostname' => $record['target'],
'port' => $record['port']
));
} else {
$cap = $this->discoverCapabilities(array(
'hostname' => $record['target'],
'port' => 389
));
}
}
return $cap;
}
private function discoverCapabilities($config)
{
$conn = new Connection(new Config($config));
try {
$conn->connect();
$this->capabilities = $conn->getCapabilities();
$this->namingContext = $conn->getDefaultNamingContext();
$this->port = $config['port'];
$this->domain = $config['hostname'];
return true;
} catch (LdapException $e) {
Logger::info(
'Ldap discovery for ' . $config['hostname'] . ':' . $config['port'] . ' failed: ' . $e->getMessage()
);
return false;
}
}
public function suggestResourceSettings()
{
if (! isset($this->capabilities)) {
return array();
}
if ($this->capabilities->msCapabilities->ActiveDirectoryOid) {
return array(
'hostname' => $this->domain,
'port' => $this->port,
'root_dn' => $this->namingContext
);
} else {
return array(
'hostname' => $this->domain,
'port' => $this->port,
'root_dn' => $this->namingContext
);
}
}
public function hasSuggestion()
{
return isset($this->capabilities);
}
public function suggestBackendSettings()
{
if (! isset($this->capabilities)) {
return array();
}
if ($this->capabilities->msCapabilities->ActiveDirectoryOid) {
return array(
'base_dn' => $this->namingContext,
'user_class' => 'user',
'user_name_attribute' => 'sAMAccountName'
);
} else {
return array(
'base_dn' => $this->namingContext,
'user_class' => 'inetOrgPerson',
'user_name_attribute' => 'uid'
);
}
}
public function isAd()
{
return $this->capabilities->msCapabilities->ActiveDirectoryOid;
}
}

View File

@ -100,10 +100,7 @@ class PreferenceForm extends Form
} }
$this->preferences->icingaweb = $webPreferences; $this->preferences->icingaweb = $webPreferences;
// TODO: Is this even necessary in case the session is written on response? Session::getSession()->user->setPreferences($this->preferences);
$session = Session::getSession();
$session->user->setPreferences($this->preferences);
$session->write();
try { try {
$this->save(); $this->save();

View File

@ -14,5 +14,17 @@
<?php endif ?> <?php endif ?>
<?= $this->form ?> <?= $this->form ?>
<div class="footer">Icinga Web 2 &copy; 2013-2014<br><a href="https://www.icinga.org">The Icinga Project</a></div> <div class="footer">Icinga Web 2 &copy; 2013-2014<br><a href="https://www.icinga.org">The Icinga Project</a></div>
<?php if ($configMissing): ?>
<div class="config-note"><?= sprintf(
t(
'You seem not to have Icinga Web 2 configured yet so it\'s not possible to log in without any defined '
. 'authentication method. Please define a authentication method by following the instructions in the'
. ' %1$sdocumentation%3$s or by using our %2$sweb-based setup-wizard%3$s.'
),
'<a href="http://docs.icinga.org/" title="Icinga Web 2 Documentation">', // TODO: Documentation link
'<a href="' . $this->href('setup') . '" title="Icinga Web 2 Setup-Wizard">',
'</a>'
); ?></div>
<?php endif ?>
</div> </div>
</div> </div>

View File

@ -0,0 +1,99 @@
<?php
use Icinga\Web\Url;
if ($xAxisPaginator->count() <= 1 && $yAxisPaginator->count() <= 1) {
return; // Display this pagination only if there are multiple pages
}
$fromTo = t('%s: %d to %d of %d');
$xAxisPages = $xAxisPaginator->getPages('all');
$yAxisPages = $yAxisPaginator->getPages('all');
$totalYAxisPages = $yAxisPaginator->count();
$currentYAxisPage = $yAxisPaginator->getCurrentPageNumber();
$prevYAxisPage = $currentYAxisPage > 1 ? $currentYAxisPage - 1 : null;
$nextYAxisPage = $currentYAxisPage < $totalYAxisPages ? $currentYAxisPage + 1 : null;
$totalXAxisPages = $xAxisPaginator->count();
$currentXAxisPage = $xAxisPaginator->getCurrentPageNumber();
$prevXAxisPage = $currentXAxisPage > 1 ? $currentXAxisPage - 1 : null;
$nextXAxisPage = $currentXAxisPage < $totalXAxisPages ? $currentXAxisPage + 1 : null;
?>
<table class="joystick-pagination">
<tbody>
<tr>
<td>&nbsp;</td>
<td>
<?php if ($prevYAxisPage): ?>
<a target="_self" href="<?= Url::fromRequest()->overwriteParams(array(
'page' => $currentXAxisPage . ',' . $prevYAxisPage
))->getAbsoluteUrl(); ?>" title="<?= sprintf(
$fromTo,
t('Hosts'),
($prevYAxisPage - 1) * $yAxisPages->itemCountPerPage + 1,
$prevYAxisPage * $yAxisPages->itemCountPerPage,
$yAxisPages->totalItemCount
); ?>"><?= $this->icon('up_petrol.png'); ?></a>
<?php else: ?>
<?= $this->icon('up.png'); ?>
<?php endif ?>
</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>
<?php if ($prevXAxisPage): ?>
<a target="_self" href="<?= Url::fromRequest()->overwriteParams(array(
'page' => $prevXAxisPage . ',' . $currentYAxisPage
))->getAbsoluteUrl(); ?>" title="<?= sprintf(
$fromTo,
t('Services'),
($prevXAxisPage - 1) * $xAxisPages->itemCountPerPage + 1,
$prevXAxisPage * $xAxisPages->itemCountPerPage,
$xAxisPages->totalItemCount
); ?>"><?= $this->icon('prev_petrol.png'); ?></a>
<?php else: ?>
<?= $this->icon('prev.png'); ?>
<?php endif ?>
</td>
<td>&nbsp;</td>
<td>
<?php if ($nextXAxisPage): ?>
<a target="_self" href="<?= Url::fromRequest()->overwriteParams(array(
'page' => $nextXAxisPage . ',' . $currentYAxisPage
))->getAbsoluteUrl(); ?>" title="<?= sprintf(
$fromTo,
t('Services'),
$currentXAxisPage * $xAxisPages->itemCountPerPage + 1,
$nextXAxisPage * $xAxisPages->itemCountPerPage,
$xAxisPages->totalItemCount
); ?>"><?= $this->icon('next_petrol.png'); ?></a>
<?php else: ?>
<?= $this->icon('next.png'); ?>
<?php endif ?>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<?php if ($nextYAxisPage): ?>
<a target="_self" href="<?= Url::fromRequest()->overwriteParams(array(
'page' => $currentXAxisPage . ',' . $nextYAxisPage
))->getAbsoluteUrl(); ?>" title="<?= sprintf(
$fromTo,
t('Hosts'),
$currentYAxisPage * $yAxisPages->itemCountPerPage + 1,
$nextYAxisPage * $yAxisPages->itemCountPerPage,
$yAxisPages->totalItemCount
); ?>"><?= $this->icon('down_petrol.png'); ?></a>
<?php else: ?>
<?= $this->icon('down.png'); ?>
<?php endif ?>
</td>
<td>&nbsp;</td>
</tr>
</tbody>
</table>

View File

@ -1,6 +1,9 @@
#!/usr/bin/php #!/usr/bin/php
<?php
use Icinga\Application\Cli; <?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
require_once dirname(__DIR__) . '/library/Icinga/Application/Cli.php'; require_once dirname(__DIR__) . '/library/Icinga/Application/Cli.php';
Cli::start()->dispatch();
Icinga\Application\Cli::start()->dispatch();

View File

@ -74,8 +74,7 @@ create all database tables. You will find the installation guides for the differ
> >
> RPM packages install the schema into /usr/share/doc/icingaweb-&lt;version&gt;/schema > RPM packages install the schema into /usr/share/doc/icingaweb-&lt;version&gt;/schema
bash$ mysql -u root -p icingaweb < etc/schema/accounts.mysql.sql bash$ mysql -u root -p icingaweb < etc/schema/mysql.sql
bash$ mysql -u root -p icingaweb < etc/schema/preferences.mysql.sql
#### PostgreSQL #### PostgreSQL
@ -108,8 +107,7 @@ And restart your database ('service postgresql restart' or '/etc/init.d/postgres
> >
> RPM packages install the schema into /usr/share/doc/icingaweb-&lt;version&gt;/schema > RPM packages install the schema into /usr/share/doc/icingaweb-&lt;version&gt;/schema
bash$ psql -U icingaweb -a -f etc/schema/accounts.pgsql.sql bash$ psql -U icingaweb -a -f etc/schema/pgsql.sql
bash$ psql -U icingaweb -a -f etc/schema/preferences.pgsql.sql

View File

@ -1,24 +0,0 @@
create table account (
`username` varchar(255) COLLATE latin1_general_ci NOT NULL,
`salt` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
`active` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
/*
* user: icingaadmin
* password: icinga
*/
INSERT INTO account (
`username`,
`salt`,
`password`,
`active`
)
VALUES (
'icingaadmin',
'57cfd5746224be4f60c25d4e8514bec35ad2d01810723a138756b285898e71b2',
'43f8e0588eb39f1a41383b48def0b1fdc45e79b8f67194cccee4453eb3f4ea13',
1
);

View File

@ -1,28 +0,0 @@
create table "account" (
"username" character varying(255) NOT NULL,
"salt" character varying(255),
"password" character varying(255) NOT NULL,
"active" boolean
);
ALTER TABLE ONLY "account"
ADD CONSTRAINT account_pkey PRIMARY KEY ("username");
CREATE UNIQUE INDEX username_lower_unique_idx ON "account" USING btree (lower((username)::text));
/*
* user: icingaadmin
* password: icinga
*/
INSERT INTO "account" (
"username",
"salt",
"password",
"active"
)
VALUES (
'icingaadmin',
'57cfd5746224be4f60c25d4e8514bec35ad2d01810723a138756b285898e71b2',
'43f8e0588eb39f1a41383b48def0b1fdc45e79b8f67194cccee4453eb3f4ea13',
true
);

View File

@ -0,0 +1,96 @@
/**
* Table "icingaweb_group"
*/
CREATE TABLE "icingaweb_group" (
"name" character varying(64) NOT NULL,
"parent" character varying(64) NULL DEFAULT NULL,
"ctime" timestamp NULL DEFAULT NULL,
"mtime" timestamp NULL DEFAULT NULL
);
ALTER TABLE ONLY "icingaweb_group"
ADD CONSTRAINT pk_icingaweb_group
PRIMARY KEY (
"name"
);
CREATE UNIQUE INDEX idx_icingaweb_group
ON "icingaweb_group"
USING btree (
lower((name)::text)
);
/**
* Table "icingaweb_group_membership"
*/
CREATE TABLE "icingaweb_group_membership" (
"group_name" character varying(64) NOT NULL,
"username" character varying(64) NOT NULL,
"ctime" timestamp NULL DEFAULT NULL,
"mtime" timestamp NULL DEFAULT NULL
);
ALTER TABLE ONLY "icingaweb_group_membership"
ADD CONSTRAINT pk_icingaweb_group_membership
PRIMARY KEY (
"group_name",
"username"
);
CREATE UNIQUE INDEX idx_icingaweb_group_membership
ON "icingaweb_group_membership"
USING btree (
lower((group_name)::text),
lower((username)::text)
);
/**
* Table "icingaweb_user"
*/
CREATE TABLE "icingaweb_user" (
"name" character varying(64) NOT NULL,
"active" smallint NOT NULL,
"password_hash" bytea NOT NULL,
"ctime" timestamp NULL DEFAULT NULL,
"mtime" timestamp NULL DEFAULT NULL
);
ALTER TABLE ONLY "icingaweb_user"
ADD CONSTRAINT pk_icingaweb_user
PRIMARY KEY (
"name"
);
CREATE UNIQUE INDEX idx_icingaweb_user
ON "icingaweb_user"
USING btree (
lower((name)::text)
);
/**
* Table "icingaweb_user_preference"
*/
CREATE TABLE "icingaweb_user_preference" (
"username" character varying(64) NOT NULL,
"name" character varying(64) NOT NULL,
"value" character varying(255) NOT NULL,
"ctime" timestamp NULL DEFAULT NULL,
"mtime" timestamp NULL DEFAULT NULL
);
ALTER TABLE ONLY "icingaweb_user_preference"
ADD CONSTRAINT pk_icingaweb_user_preference
PRIMARY KEY (
"username",
"name"
);
CREATE UNIQUE INDEX idx_icingaweb_user_preference
ON "icingaweb_user_preference"
USING btree (
lower((username)::text),
lower((name)::text)
);

View File

@ -1,6 +0,0 @@
create table `preference`(
`username` VARCHAR(255) COLLATE latin1_general_ci NOT NULL,
`key` VARCHAR(100) COLLATE latin1_general_ci NOT NULL,
`value` VARCHAR(255) NOT NULL,
PRIMARY KEY (`username`, `key`)
) ENGINE=InnoDB;

View File

@ -1,10 +0,0 @@
create table "preference"(
"username" VARCHAR(255) NOT NULL,
"key" VARCHAR(100) NOT NULL,
"value" VARCHAR(255) NOT NULL
);
ALTER TABLE ONLY "preference"
ADD CONSTRAINT preference_pkey PRIMARY KEY ("username", "key");
CREATE UNIQUE INDEX username_and_key_lower_unique_idx ON "preference" USING btree (lower((username)::text), lower((key)::text));

View File

@ -13,6 +13,7 @@ use Icinga\Exception\NotReadableError;
use Icinga\Application\Logger; use Icinga\Application\Logger;
use Icinga\Util\DateTimeFactory; use Icinga\Util\DateTimeFactory;
use Icinga\Util\Translator; use Icinga\Util\Translator;
use Icinga\File\Ini\IniWriter;
use Icinga\Exception\IcingaException; use Icinga\Exception\IcingaException;
/** /**
@ -39,6 +40,15 @@ use Icinga\Exception\IcingaException;
*/ */
abstract class ApplicationBootstrap abstract class ApplicationBootstrap
{ {
/**
* Base directory
*
* Parent folder for at least application, bin, modules, library/vendor and public
*
* @var string
*/
protected $baseDir;
/** /**
* Icinga auto loader * Icinga auto loader
* *
@ -97,27 +107,25 @@ abstract class ApplicationBootstrap
/** /**
* Constructor * Constructor
*
* @param string $baseDir Icinga Web 2 base directory
* @param string $configDir Path to Icinga Web 2's configuration files
*/ */
protected function __construct($configDir = null) protected function __construct($baseDir = null, $configDir = null)
{ {
if ($baseDir === null) {
$baseDir = dirname($this->getBootstrapDirectory());
}
$this->baseDir = $baseDir;
if (! defined('ICINGAWEB_BASEDIR')) {
define('ICINGAWEB_BASEDIR', dirname($this->getBootstrapDirectory()));
}
define('ICINGAWEB_VENDORS', ICINGAWEB_BASEDIR . '/library/vendor');
define('ICINGAWEB_APPDIR', ICINGAWEB_BASEDIR . '/application');
$this->appDir = ICINGAWEB_APPDIR;
$this->libDir = realpath(__DIR__ . '/../..'); $this->libDir = realpath(__DIR__ . '/../..');
if (!defined('ICINGA_LIBDIR')) {
define('ICINGA_LIBDIR', $this->libDir);
}
if (defined('ICINGAWEB_APPDIR')) {
$this->appDir = ICINGAWEB_APPDIR;
} elseif (array_key_exists('ICINGAWEB_APPDIR', $_SERVER)) {
$this->appDir = $_SERVER['ICINGAWEB_APPDIR'];
} else {
$this->appDir = realpath($this->libDir. '/../application');
}
if (!defined('ICINGAWEB_APPDIR')) {
define('ICINGAWEB_APPDIR', $this->appDir);
}
if ($configDir === null) { if ($configDir === null) {
if (array_key_exists('ICINGAWEB_CONFIGDIR', $_SERVER)) { if (array_key_exists('ICINGAWEB_CONFIGDIR', $_SERVER)) {
$configDir = $_SERVER['ICINGAWEB_CONFIGDIR']; $configDir = $_SERVER['ICINGAWEB_CONFIGDIR'];
@ -195,6 +203,35 @@ abstract class ApplicationBootstrap
return $this->isWeb; return $this->isWeb;
} }
/**
* Helper to glue directories together
*
* @param string $dir
* @param string $subdir
*
* @return string
*/
private function getDirWithSubDir($dir, $subdir = null)
{
if ($subdir !== null) {
$dir .= '/' . ltrim($subdir, '/');
}
return $dir;
}
/**
* Get the base directory
*
* @param string $subDir Optional sub directory to get
*
* @return string
*/
public function getBaseDir($subDir = null)
{
return $this->getDirWithSubDir($subDir);
}
/** /**
* Getter for application dir * Getter for application dir
* *
@ -222,44 +259,28 @@ abstract class ApplicationBootstrap
} }
/** /**
* Get the path to the bootstrapping directory. * Get the path to the bootstrapping directory
* *
* This is usually /public for Web and EmbeddedWeb * This is usually /public for Web and EmbeddedWeb and /bin for the CLI
* *
* @return string * @return string
*/ */
public function getBootstrapDirecory() public function getBootstrapDirectory()
{ {
return dirname($_SERVER['SCRIPT_FILENAME']); return dirname(realpath($_SERVER['SCRIPT_FILENAME']));
} }
/** /**
* Helper to glue directories together * Start the bootstrap
* *
* @param string $dir * @param string $baseDir Icinga Web 2 base directory
* @param string $subdir * @param string $configDir Path to Icinga Web 2's configuration files
* *
* @return string * @return static
*/ */
private function getDirWithSubDir($dir, $subdir = null) public static function start($baseDir = null, $configDir = null)
{ {
if ($subdir !== null) { $application = new static($baseDir, $configDir);
$dir .= '/' . ltrim($subdir, '/');
}
return $dir;
}
/**
* Starting concrete bootstrap classes
*
* @param string $configDir
*
* @return ApplicationBootstrap
*/
public static function start($configDir = null)
{
$application = new static($configDir);
$application->bootstrap(); $application->bootstrap();
return $application; return $application;
} }
@ -317,6 +338,21 @@ abstract class ApplicationBootstrap
return $this; return $this;
} }
/**
* Load all core modules
*
* @return self
*/
protected function loadCoreModules()
{
try {
$this->moduleManager->loadCoreModules();
} catch (NotReadableError $e) {
Logger::error(new IcingaException('Cannot load core modules. An exception was thrown:', $e));
}
return $this;
}
/** /**
* Load all enabled modules * Load all enabled modules
* *
@ -357,12 +393,14 @@ abstract class ApplicationBootstrap
protected function loadConfig() protected function loadConfig()
{ {
Config::$configDir = $this->configDir; Config::$configDir = $this->configDir;
try { try {
$this->config = Config::app(); $this->config = Config::app();
} catch (NotReadableError $e) { } catch (NotReadableError $e) {
Logger::error(new IcingaException('Cannot load application configuration. An exception was thrown:', $e)); Logger::error(new IcingaException('Cannot load application configuration. An exception was thrown:', $e));
$this->config = new Config(); $this->config = new Config();
} }
return $this; return $this;
} }
@ -398,7 +436,7 @@ abstract class ApplicationBootstrap
*/ */
protected function setupLogger() protected function setupLogger()
{ {
if (($loggingConfig = $this->config->get('logging')) !== null) { if (($loggingConfig = $this->config->logging) !== null) {
try { try {
Logger::create($loggingConfig); Logger::create($loggingConfig);
} catch (ConfigurationError $e) { } catch (ConfigurationError $e) {

View File

@ -43,7 +43,8 @@ class Cli extends ApplicationBootstrap
->parseBasicParams() ->parseBasicParams()
->setupLogger() ->setupLogger()
->setupResourceFactory() ->setupResourceFactory()
->setupModuleManager(); ->setupModuleManager()
->loadCoreModules();
} }
protected function setupLogging() protected function setupLogging()
@ -52,8 +53,7 @@ class Cli extends ApplicationBootstrap
new Config( new Config(
array( array(
'level' => Logger::INFO, 'level' => Logger::INFO,
'log' => 'file', 'log' => 'stdout',
'file' => 'php://stderr'
) )
) )
); );

View File

@ -378,7 +378,7 @@ class Config implements Countable, Iterator, ArrayAccess
); );
} }
if ($default !== null) { if ($value === null && $default !== null) {
$value = $default; $value = $default;
} }

View File

@ -31,6 +31,7 @@ class EmbeddedWeb extends ApplicationBootstrap
->setupErrorHandling() ->setupErrorHandling()
->setupTimezone() ->setupTimezone()
->setupModuleManager() ->setupModuleManager()
->loadCoreModules()
->loadEnabledModules(); ->loadEnabledModules();
} }
} }

View File

@ -11,10 +11,18 @@ use Icinga\Application\Config;
*/ */
abstract class LogWriter abstract class LogWriter
{ {
/**
* @var Zend_Config
*/
protected $config;
/** /**
* Create a new log writer initialized with the given configuration * Create a new log writer initialized with the given configuration
*/ */
abstract public function __construct(Config $config); public function __construct(Config $config)
{
$this->config = $config;
}
/** /**
* Log a message with the given severity * Log a message with the given severity

View File

@ -0,0 +1,50 @@
<?php
namespace Icinga\Application\Logger\Writer;
use Icinga\Cli\Screen;
use Icinga\Application\Logger\Logger;
use Icinga\Application\Logger\LogWriter;
use Zend_Config;
/**
* Class to write log messages to STDOUT
*/
class StdoutWriter extends LogWriter
{
protected $screen;
protected function screen()
{
if ($this->screen === null) {
$this->screen = Screen::instance();
}
return $this->screen;
}
/**
* Log a message with the given severity
*
* @param int $severity The severity to use
* @param string $message The message to log
*/
public function log($severity, $message)
{
$color = 'black';
switch ($severity) {
case Logger::$ERROR:
$color = 'red';
break;
case Logger::$WARNING:
$color = 'orange';
break;
case Logger::$INFO:
$color = 'green';
break;
case Logger::$DEBUG:
$color = 'blue';
break;
}
file_put_contents('php://stderr', $this->screen()->colorize($message, $color) . "\n");
}
}

View File

@ -67,6 +67,18 @@ class Manager
*/ */
private $modulePaths = array(); private $modulePaths = array();
/**
* The core modules
*
* Core modules do not need to be enabled to load and cannot be disabled
* by the user. This must not be writable programmatically!
*
* @var array
*/
private $coreModules = array(
'setup'
);
/** /**
* Create a new instance of the module manager * Create a new instance of the module manager
* *
@ -157,7 +169,21 @@ class Manager
} }
/** /**
* Try to set all enabled modules in loaded sate * Try to set all core modules in loaded state
*
* @return self
* @see Manager::loadModule()
*/
public function loadCoreModules()
{
foreach ($this->coreModules as $name) {
$this->loadModule($name);
}
return $this;
}
/**
* Try to set all enabled modules in loaded state
* *
* @return self * @return self
* @see Manager::loadModule() * @see Manager::loadModule()
@ -211,6 +237,8 @@ class Manager
'Cannot enable module "%s". Module is not installed.', 'Cannot enable module "%s". Module is not installed.',
$name $name
); );
} elseif (in_array($name, $this->coreModules)) {
return $this;
} }
clearstatcache(true); clearstatcache(true);
@ -427,7 +455,7 @@ class Manager
} }
$installed = $this->listInstalledModules(); $installed = $this->listInstalledModules();
foreach ($installed as $name) { foreach (array_diff($installed, $this->coreModules) as $name) {
$info[$name] = (object) array( $info[$name] = (object) array(
'name' => $name, 'name' => $name,
'path' => $this->installedBaseDirs[$name], 'path' => $this->installedBaseDirs[$name],
@ -487,11 +515,14 @@ class Manager
/** /**
* Detect installed modules from every path provided in modulePaths * Detect installed modules from every path provided in modulePaths
* *
* @param array $availableDirs Installed modules location
*
* @return self * @return self
*/ */
public function detectInstalledModules() public function detectInstalledModules(array $availableDirs = null)
{ {
foreach ($this->modulePaths as $basedir) { $modulePaths = $availableDirs !== null ? $availableDirs : $this->modulePaths;
foreach ($modulePaths as $basedir) {
$canonical = realpath($basedir); $canonical = realpath($basedir);
if ($canonical === false) { if ($canonical === false) {
Logger::warning('Module path "%s" does not exist', $basedir); Logger::warning('Module path "%s" does not exist', $basedir);

View File

@ -17,6 +17,7 @@ use Icinga\Web\Hook;
use Icinga\Web\Menu; use Icinga\Web\Menu;
use Icinga\Web\Widget; use Icinga\Web\Widget;
use Icinga\Web\Widget\Dashboard\Pane; use Icinga\Web\Widget\Dashboard\Pane;
use Icinga\Module\Setup\SetupWizard;
use Icinga\Util\File; use Icinga\Util\File;
use Icinga\Exception\ProgrammingError; use Icinga\Exception\ProgrammingError;
use Icinga\Exception\IcingaException; use Icinga\Exception\IcingaException;
@ -133,6 +134,13 @@ class Module
*/ */
private $configTabs = array(); private $configTabs = array();
/**
* Provided setup wizard
*
* @var string
*/
private $setupWizard;
/** /**
* Icinga application * Icinga application
* *
@ -641,6 +649,31 @@ class Module
return $tabs; return $tabs;
} }
/**
* Whether this module provides a setup wizard
*
* @return bool
*/
public function providesSetupWizard()
{
$this->launchConfigScript();
if (class_exists($this->setupWizard)) {
$wizard = new $this->setupWizard;
return $wizard instanceof SetupWizard;
}
return false;
}
/**
* Return this module's setup wizard
*
* @return SetupWizard
*/
public function getSetupWizard()
{
return new $this->setupWizard;
}
/** /**
* Provide a named permission * Provide a named permission
@ -704,6 +737,19 @@ class Module
return $this; return $this;
} }
/**
* Provide a setup wizard
*
* @param string $className The name of the class
*
* @return self
*/
protected function provideSetupWizard($className)
{
$this->setupWizard = $className;
return $this;
}
/** /**
* Register new namespaces on the autoloader * Register new namespaces on the autoloader
* *

View File

@ -30,6 +30,16 @@ class Platform
*/ */
protected static $fqdn; protected static $fqdn;
/**
* Return the operating system's name
*
* @return string
*/
public static function getOperatingSystemName()
{
return php_uname('s');
}
/** /**
* Test of windows * Test of windows
* *
@ -37,7 +47,7 @@ class Platform
*/ */
public static function isWindows() public static function isWindows()
{ {
return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; return strtoupper(substr(self::getOperatingSystemName(), 0, 3)) === 'WIN';
} }
/** /**
@ -47,7 +57,7 @@ class Platform
*/ */
public static function isLinux() public static function isLinux()
{ {
return strtoupper(substr(PHP_OS, 0, 5)) === 'LINUX'; return strtoupper(substr(self::getOperatingSystemName(), 0, 5)) === 'LINUX';
} }
/** /**
@ -116,7 +126,35 @@ class Platform
if (substr(self::$fqdn, 0, strlen(self::$hostname)) === self::$hostname) { if (substr(self::$fqdn, 0, strlen(self::$hostname)) === self::$hostname) {
self::$domain = substr(self::$fqdn, strlen(self::$hostname) + 1); self::$domain = substr(self::$fqdn, strlen(self::$hostname) + 1);
} else { } else {
self::$domain = array_shift(preg_split('~\.~', self::$hostname, 2)); $parts = preg_split('~\.~', self::$hostname, 2);
self::$domain = array_shift($parts);
}
}
/**
* Return the version of PHP
*
* @return string
*/
public static function getPhpVersion()
{
return phpversion();
}
/**
* Return the username PHP is running as
*
* @return string
*/
public static function getPhpUser()
{
if (static::isWindows()) {
return get_current_user(); // http://php.net/manual/en/function.get-current-user.php#75059
}
if (function_exists('posix_geteuid')) {
$userInfo = posix_getpwuid(posix_geteuid());
return $userInfo['name'];
} }
} }
@ -131,4 +169,32 @@ class Platform
{ {
return extension_loaded($extensionName); return extension_loaded($extensionName);
} }
/**
* Return the value for the given PHP configuration option
*
* @param string $option The option name for which to return the value
*
* @return string|false
*/
public static function getPhpConfig($option)
{
return ini_get($option);
}
/**
* Return whether the given Zend framework class exists
*
* @param string $name The name of the class to check
*
* @return bool
*/
public static function zendClassExists($name)
{
if (class_exists($name)) {
return true;
}
return (@include str_replace('_', '/', $name) . '.php') !== false;
}
} }

View File

@ -92,6 +92,7 @@ class Web extends ApplicationBootstrap
->setupZendMvc() ->setupZendMvc()
->setupFormNamespace() ->setupFormNamespace()
->setupModuleManager() ->setupModuleManager()
->loadCoreModules()
->loadEnabledModules() ->loadEnabledModules()
->setupRoute() ->setupRoute()
->setupPagination(); ->setupPagination();

View File

@ -54,7 +54,7 @@ class AutoLoginBackend extends UserBackend
if (isset($_SERVER['REMOTE_USER'])) { if (isset($_SERVER['REMOTE_USER'])) {
$username = $_SERVER['REMOTE_USER']; $username = $_SERVER['REMOTE_USER'];
$user->setRemoteUserInformation($username, 'REMOTE_USER'); $user->setRemoteUserInformation($username, 'REMOTE_USER');
if ($this->stripUsernameRegexp !== null) { if ($this->stripUsernameRegexp) {
$stripped = preg_replace($this->stripUsernameRegexp, '', $username); $stripped = preg_replace($this->stripUsernameRegexp, '', $username);
if ($stripped !== false) { if ($stripped !== false) {
// TODO(el): PHP issues a warning when PHP cannot compile the regular expression. Should we log an // TODO(el): PHP issues a warning when PHP cannot compile the regular expression. Should we log an

View File

@ -4,6 +4,7 @@
namespace Icinga\Authentication\Backend; namespace Icinga\Authentication\Backend;
use PDO;
use Icinga\Authentication\UserBackend; use Icinga\Authentication\UserBackend;
use Icinga\Data\Db\DbConnection; use Icinga\Data\Db\DbConnection;
use Icinga\User; use Icinga\User;
@ -11,16 +12,29 @@ use Icinga\Exception\AuthenticationException;
use Exception; use Exception;
use Zend_Db_Expr; use Zend_Db_Expr;
use Zend_Db_Select; use Zend_Db_Select;
use Icinga\Exception\IcingaException;
class DbUserBackend extends UserBackend class DbUserBackend extends UserBackend
{ {
/**
* The algorithm to use when hashing passwords
*
* @var string
*/
const HASH_ALGORITHM = '$1$'; // MD5
/**
* The length of the salt to use when hashing a password
*
* @var int
*/
const SALT_LENGTH = 12; // 12 is required by MD5
/** /**
* Connection to the database * Connection to the database
* *
* @var DbConnection * @var DbConnection
*/ */
private $conn; protected $conn;
public function __construct(DbConnection $conn) public function __construct(DbConnection $conn)
{ {
@ -36,45 +50,69 @@ class DbUserBackend extends UserBackend
*/ */
public function hasUser(User $user) public function hasUser(User $user)
{ {
$select = new Zend_Db_Select($this->conn->getConnection()); $select = new Zend_Db_Select($this->conn->getDbAdapter());
$row = $select->from('account', array(new Zend_Db_Expr(1))) $row = $select->from('icingaweb_user', array(new Zend_Db_Expr(1)))
->where('username = ?', $user->getUsername()) ->where('name = ?', $user->getUsername())
->query()->fetchObject(); ->query()->fetchObject();
return ($row !== false) ? true : false; return ($row !== false) ? true : false;
} }
/** /**
* Authenticate the given user and return true on success, false on failure and null on error * Add a new user
*
* @param string $username The name of the new user
* @param string $password The new user's password
* @param bool $active Whether the user is active
*/
public function addUser($username, $password, $active = true)
{
$passwordHash = $this->hashPassword($password);
$stmt = $this->conn->getDbAdapter()->prepare(
'INSERT INTO icingaweb_user VALUES (:name, :active, :password_hash, now(), DEFAULT);'
);
$stmt->bindParam(':name', $username, PDO::PARAM_STR);
$stmt->bindParam(':active', $active, PDO::PARAM_INT);
$stmt->bindParam(':password_hash', $passwordHash, PDO::PARAM_LOB);
$stmt->execute();
}
/**
* Fetch the hashed password for the given user
*
* @param string $username The name of the user
*
* @return string
*/
protected function getPasswordHash($username)
{
$stmt = $this->conn->getDbAdapter()->prepare(
'SELECT password_hash FROM icingaweb_user WHERE name = :name AND active = 1'
);
$stmt->execute(array(':name' => $username));
$stmt->bindColumn(1, $lob, PDO::PARAM_LOB);
$stmt->fetch(PDO::FETCH_BOUND);
return is_resource($lob) ? stream_get_contents($lob) : $lob;
}
/**
* Authenticate the given user and return true on success, false on failure and throw an exception on error
* *
* @param User $user * @param User $user
* @param string $password * @param string $password
* *
* @return bool|null * @return bool
*
* @throws AuthenticationException * @throws AuthenticationException
*/ */
public function authenticate(User $user, $password) public function authenticate(User $user, $password)
{ {
try { try {
$salt = $this->getSalt($user->getUsername()); $passwordHash = $this->getPasswordHash($user->getUsername());
if ($salt === null) { $passwordSalt = $this->getSalt($passwordHash);
return false; $hashToCompare = $this->hashPassword($password, $passwordSalt);
} return $hashToCompare === $passwordHash;
if ($salt === '') {
throw new IcingaException(
'Cannot find salt for user %s',
$user->getUsername()
);
}
$select = new Zend_Db_Select($this->conn->getConnection());
$row = $select->from('account', array(new Zend_Db_Expr(1)))
->where('username = ?', $user->getUsername())
->where('active = ?', true)
->where('password = ?', $this->hashPassword($password, $salt))
->query()->fetchObject();
return ($row !== false) ? true : false;
} catch (Exception $e) { } catch (Exception $e) {
throw new AuthenticationException( throw new AuthenticationException(
'Failed to authenticate user "%s" against backend "%s". An exception was thrown:', 'Failed to authenticate user "%s" against backend "%s". An exception was thrown:',
@ -86,29 +124,40 @@ class DbUserBackend extends UserBackend
} }
/** /**
* Get salt by username * Extract salt from the given password hash
* *
* @param string $username * @param string $hash The hashed password
* *
* @return string|null * @return string
*/ */
private function getSalt($username) protected function getSalt($hash)
{ {
$select = new Zend_Db_Select($this->conn->getConnection()); return substr($hash, strlen(self::HASH_ALGORITHM), self::SALT_LENGTH);
$row = $select->from('account', array('salt'))->where('username = ?', $username)->query()->fetchObject(); }
return ($row !== false) ? $row->salt : null;
/**
* Return a random salt
*
* The returned salt is safe to be used for hashing a user's password
*
* @return string
*/
protected function generateSalt()
{
return openssl_random_pseudo_bytes(self::SALT_LENGTH);
} }
/** /**
* Hash a password * Hash a password
* *
* @param string $password * @param string $password
* @param string $salt * @param string $salt
* *
* @return string * @return string
*/ */
private function hashPassword($password, $salt) { protected function hashPassword($password, $salt = null)
return hash_hmac('sha256', $password, $salt); {
return crypt($password, self::HASH_ALGORITHM . ($salt !== null ? $salt : $this->generateSalt()));
} }
/** /**
@ -118,12 +167,29 @@ class DbUserBackend extends UserBackend
*/ */
public function count() public function count()
{ {
$select = new Zend_Db_Select($this->conn->getConnection()); $select = new Zend_Db_Select($this->conn->getDbAdapter());
$row = $select->from( $row = $select->from(
'account', 'icingaweb_user',
array('count' => 'COUNT(*)') array('count' => 'COUNT(*)')
)->query()->fetchObject(); )->query()->fetchObject();
return ($row !== false) ? $row->count : 0; return ($row !== false) ? $row->count : 0;
} }
/**
* Return the names of all available users
*
* @return array
*/
public function listUsers()
{
$query = $this->conn->select()->from('icingaweb_user', array('name'));
$users = array();
foreach ($query->fetchAll() as $row) {
$users[] = $row->name;
}
return $users;
}
} }

View File

@ -4,7 +4,6 @@
namespace Icinga\Authentication\Backend; namespace Icinga\Authentication\Backend;
use Icinga\Application\Logger;
use Icinga\User; use Icinga\User;
use Icinga\Authentication\UserBackend; use Icinga\Authentication\UserBackend;
use Icinga\Protocol\Ldap\Connection; use Icinga\Protocol\Ldap\Connection;
@ -20,20 +19,36 @@ class LdapUserBackend extends UserBackend
**/ **/
protected $conn; protected $conn;
protected $baseDn;
protected $userClass; protected $userClass;
protected $userNameAttribute; protected $userNameAttribute;
protected $groupOptions; protected $groupOptions;
public function __construct(Connection $conn, $userClass, $userNameAttribute, $groupOptions = null) public function __construct(Connection $conn, $userClass, $userNameAttribute, $baseDn, $groupOptions = null)
{ {
$this->conn = $conn; $this->conn = $conn;
$this->baseDn = trim($baseDn) !== '' ? $baseDn : $conn->getDN();
$this->userClass = $userClass; $this->userClass = $userClass;
$this->userNameAttribute = $userNameAttribute; $this->userNameAttribute = $userNameAttribute;
$this->groupOptions = $groupOptions; $this->groupOptions = $groupOptions;
} }
/**
* @return \Icinga\Protocol\Ldap\Query
*/
protected function selectUsers()
{
return $this->conn->select()->setBase($this->baseDn)->from(
$this->userClass,
array(
$this->userNameAttribute
)
);
}
/** /**
* Create query * Create query
* *
@ -41,14 +56,9 @@ class LdapUserBackend extends UserBackend
* *
* @return \Icinga\Protocol\Ldap\Query * @return \Icinga\Protocol\Ldap\Query
**/ **/
protected function createQuery($username) protected function selectUser($username)
{ {
return $this->conn->select() return $this->selectUsers()->where(
->from(
$this->userClass,
array($this->userNameAttribute)
)
->where(
$this->userNameAttribute, $this->userNameAttribute,
str_replace('*', '', $username) str_replace('*', '', $username)
); );
@ -69,7 +79,7 @@ class LdapUserBackend extends UserBackend
public function assertAuthenticationPossible() public function assertAuthenticationPossible()
{ {
try { try {
$q = $this->conn->select()->from($this->userClass); $q = $this->conn->select()->setBase($this->baseDn)->from($this->userClass);
$result = $q->fetchRow(); $result = $q->fetchRow();
} catch (LdapException $e) { } catch (LdapException $e) {
throw new AuthenticationException('Connection not possible.', $e); throw new AuthenticationException('Connection not possible.', $e);
@ -79,7 +89,7 @@ class LdapUserBackend extends UserBackend
throw new AuthenticationException( throw new AuthenticationException(
'No objects with objectClass="%s" in DN="%s" found.', 'No objects with objectClass="%s" in DN="%s" found.',
$this->userClass, $this->userClass,
$this->conn->getDN() $this->baseDn
); );
} }
@ -99,12 +109,12 @@ class LdapUserBackend extends UserBackend
* *
* @param string $dn * @param string $dn
* *
* @return array|null * @return array
*/ */
public function getGroups($dn) public function getGroups($dn)
{ {
if (empty($this->groupOptions) || ! isset($this->groupOptions['group_base_dn'])) { if (empty($this->groupOptions) || ! isset($this->groupOptions['group_base_dn'])) {
return null; return array();
} }
$q = $this->conn->select() $q = $this->conn->select()
@ -140,7 +150,7 @@ class LdapUserBackend extends UserBackend
public function hasUser(User $user) public function hasUser(User $user)
{ {
$username = $user->getUsername(); $username = $user->getUsername();
return $this->conn->fetchOne($this->createQuery($username)) === $username; return $this->conn->fetchOne($this->selectUser($username)) === $username;
} }
/** /**
@ -173,7 +183,7 @@ class LdapUserBackend extends UserBackend
return false; return false;
} }
try { try {
$userDn = $this->conn->fetchDN($this->createQuery($user->getUsername())); $userDn = $this->conn->fetchDN($this->selectUser($user->getUsername()));
$authenticated = $this->conn->testCredentials( $authenticated = $this->conn->testCredentials(
$userDn, $userDn,
$password $password
@ -203,14 +213,20 @@ class LdapUserBackend extends UserBackend
*/ */
public function count() public function count()
{ {
return $this->conn->count($this->selectUsers());
}
return $this->conn->count( /**
$this->conn->select()->from( * Return the names of all available users
$this->userClass, *
array( * @return array
$this->userNameAttribute */
) public function listUsers()
) {
); $users = array();
foreach ($this->selectUsers()->fetchAll() as $row) {
$users[] = $row->{$this->userNameAttribute};
}
return $users;
} }
} }

View File

@ -119,10 +119,7 @@ class Manager
*/ */
public function persistCurrentUser() public function persistCurrentUser()
{ {
$session = Session::getSession(); Session::getSession()->set('user', $this->user)->refreshId();
$session->set('user', $this->user);
$session->write();
$session->refreshId();
} }
/** /**

View File

@ -103,6 +103,7 @@ abstract class UserBackend implements Countable
$resource, $resource,
$backendConfig->get('user_class', 'user'), $backendConfig->get('user_class', 'user'),
$backendConfig->get('user_name_attribute', 'sAMAccountName'), $backendConfig->get('user_name_attribute', 'sAMAccountName'),
$backendConfig->get('base_dn', $resource->getDN()),
$groupOptions $groupOptions
); );
break; break;
@ -129,6 +130,7 @@ abstract class UserBackend implements Countable
$resource, $resource,
$backendConfig->user_class, $backendConfig->user_class,
$backendConfig->user_name_attribute, $backendConfig->user_name_attribute,
$backendConfig->get('base_dn', $resource->getDN()),
$groupOptions $groupOptions
); );
break; break;

View File

@ -216,7 +216,10 @@ class DbConnection implements Selectable
*/ */
public function fetchRow(DbQuery $query) public function fetchRow(DbQuery $query)
{ {
return $this->dbAdapter->fetchRow($query->getSelectQuery()); Benchmark::measure('DB is fetching row');
$result = $this->dbAdapter->fetchRow($query->getSelectQuery());
Benchmark::measure('DB row done');
return $result;
} }
/** /**

View File

@ -79,7 +79,7 @@ abstract class Filter
} }
} }
krsort($operators, SORT_NATURAL); krsort($operators, version_compare(PHP_VERSION, '5.4.0') >= 0 ? SORT_NATURAL : SORT_REGULAR);
foreach ($operators as $id => $operator) { foreach ($operators as $id => $operator) {
$f = $filter->getById($id); $f = $filter->getById($id);
if ($f->getOperatorName() !== $operator) { if ($f->getOperatorName() !== $operator) {

View File

@ -433,6 +433,9 @@ class IniEditor
*/ */
private function formatKey(array $key) private function formatKey(array $key)
{ {
foreach ($key as $i => $separator) {
$key[$i] = $this->sanitize($separator);
}
return implode($this->nestSeparator, $key); return implode($this->nestSeparator, $key);
} }
@ -599,7 +602,7 @@ class IniEditor
} }
/** /**
* Prepare a value for INe * Prepare a value for INI
* *
* @param $value The value of the string * @param $value The value of the string
* *
@ -613,10 +616,12 @@ class IniEditor
return $value; return $value;
} elseif (is_bool($value)) { } elseif (is_bool($value)) {
return ($value ? 'true' : 'false'); return ($value ? 'true' : 'false');
} elseif (strpos($value, '"') === false) {
return '"' . $value . '"';
} else {
return '"' . str_replace('"', '\"', $value) . '"';
} }
return '"' . str_replace('"', '\"', $this->sanitize($value)) . '"';
}
private function sanitize($value)
{
return str_replace('\n', '', $value);
} }
} }

View File

@ -6,6 +6,7 @@ namespace Icinga\File\Ini;
use Zend_Config; use Zend_Config;
use Zend_Config_Ini; use Zend_Config_Ini;
use Zend_Config_Exception;
use Zend_Config_Writer_FileAbstract; use Zend_Config_Writer_FileAbstract;
use Icinga\Application\Config; use Icinga\Application\Config;
@ -21,11 +22,18 @@ class IniWriter extends Zend_Config_Writer_FileAbstract
*/ */
protected $options; protected $options;
/**
* The mode to set on new files
*
* @var int
*/
public static $fileMode = 0664;
/** /**
* Create a new INI writer * Create a new INI writer
* *
* @param array $options Supports all options of Zend_Config_Writer and additional * @param array $options Supports all options of Zend_Config_Writer and additional options:
* options for the internal IniEditor: * * filemode: The mode to set on new files
* * valueIndentation: The indentation level of the values * * valueIndentation: The indentation level of the values
* * commentIndentation: The indentation level of the comments * * commentIndentation: The indentation level of the comments
* * sectionSeparators: The amount of newlines between sections * * sectionSeparators: The amount of newlines between sections
@ -44,45 +52,6 @@ class IniWriter extends Zend_Config_Writer_FileAbstract
parent::__construct($options); parent::__construct($options);
} }
/**
* Find all keys containing dots and convert it to a nested configuration
*
* Ensure that configurations with the same ini representation the have
* similarly nested Zend_Config objects. The configuration may be altered
* during that process.
*
* @param Zend_Config $config The configuration to normalize
* @return Zend_Config The normalized config
*/
private function normalizeKeys(Zend_Config $config)
{
foreach ($config as $key => $value) {
if (preg_match('/\./', $key) > 0) {
// remove old key
unset ($config->$key);
// insert new key
$nests = explode('.', $key);
$current = $config;
$i = 0;
for (; $i < count($nests) - 1; $i++) {
if (! isset($current->{$nests[$i]})) {
// configuration key doesn't exist, create a new nesting level
$current->{$nests[$i]} = new Zend_Config (array(), true);
}
// move to next nesting level
$current = $current->{$nests[$i]};
}
// reached last nesting level, insert value
$current->{$nests[$i]} = $value;
}
if ($value instanceof Zend_Config) {
$config->$key = $this->normalizeKeys ($value);
}
}
return $config;
}
/** /**
* Render the Zend_Config into a config file string * Render the Zend_Config into a config file string
* *
@ -96,23 +65,37 @@ class IniWriter extends Zend_Config_Writer_FileAbstract
$oldconfig = new Zend_Config(array()); $oldconfig = new Zend_Config(array());
} }
// create an internal copy of the given configuration, since the user of this class
// won't expect that a configuration will ever be altered during
// the rendering process.
$extends = $this->_config->getExtends();
$this->_config = new Zend_Config ($this->_config->toArray(), true);
foreach ($extends as $extending => $extended) {
$this->_config->setExtend($extending, $extended);
}
$this->_config = $this->normalizeKeys($this->_config);
$newconfig = $this->_config; $newconfig = $this->_config;
$editor = new IniEditor(file_get_contents($this->_filename), $this->options); $editor = new IniEditor(@file_get_contents($this->_filename), $this->options);
$this->diffConfigs($oldconfig, $newconfig, $editor); $this->diffConfigs($oldconfig, $newconfig, $editor);
$this->updateSectionOrder($newconfig, $editor); $this->updateSectionOrder($newconfig, $editor);
return $editor->getText(); return $editor->getText();
} }
/**
* Write configuration to file and set file mode in case it does not exist yet
*
* @param string $filename
* @param Zend_Config $config
* @param bool $exclusiveLock
*/
public function write($filename = null, Zend_Config $config = null, $exclusiveLock = null)
{
$filePath = $filename !== null ? $filename : $this->_filename;
$setMode = false === file_exists($filePath);
parent::write($filename, $config, $exclusiveLock);
if ($setMode) {
$mode = isset($this->options['filemode']) ? $this->options['filemode'] : static::$fileMode;
$old = umask(0); // Make sure that the mode we're going to set doesn't get mangled
if (is_int($mode) && false === @chmod($filePath, $mode)) {
throw new Zend_Config_Exception(sprintf('Failed to set file mode "%o" on file "%s"', $mode, $filePath));
}
umask($old);
}
}
/** /**
* Create a property diff and apply the changes to the editor * Create a property diff and apply the changes to the editor
* *

View File

@ -49,7 +49,7 @@ class Pdf extends DOMPDF
$layout->content = $controller->getResponse(); $layout->content = $controller->getResponse();
$html = $layout->render(); $html = $layout->render();
$imgDir = Url::fromPath('img'); $imgDir = Url::fromPath('img');
$html = preg_replace('~src="' . $imgDir . '/~', 'src="' . Icinga::app()->getBootstrapDirecory() . '/img/', $html); $html = preg_replace('~src="' . $imgDir . '/~', 'src="' . Icinga::app()->getBootstrapDirectory() . '/img/', $html);
$html = preg_replace('~src="/svg/chart.php(.*)"~', 'class="icon" src="http://master1.com/png/chart.php$1"', $html); $html = preg_replace('~src="/svg/chart.php(.*)"~', 'class="icon" src="http://master1.com/png/chart.php$1"', $html);
$this->load_html($html); $this->load_html($html);
$this->render(); $this->render();

View File

@ -9,7 +9,6 @@ namespace Icinga\Protocol;
*/ */
class Dns class Dns
{ {
/** /**
* Discover all service records on a given domain * Discover all service records on a given domain
* *
@ -17,21 +16,12 @@ class Dns
* @param string $service The type of the service, like for example 'ldaps' or 'ldap' * @param string $service The type of the service, like for example 'ldaps' or 'ldap'
* @param string $protocol The transport protocol used by the service, defaults to 'tcp' * @param string $protocol The transport protocol used by the service, defaults to 'tcp'
* *
* @return array|null An array of all service domains * @return array An array of all found service records
*/ */
public static function getSrvRecords($domain, $service, $protocol = 'tcp') public static function getSrvRecords($domain, $service, $protocol = 'tcp')
{ {
$records = dns_get_record('_' . $service . '._' . $protocol . '.' . $domain, DNS_SRV); $records = dns_get_record('_' . $service . '._' . $protocol . '.' . $domain, DNS_SRV);
if ($records === false) { return $records === false ? array() : $records;
return null;
}
$targets = array();
foreach ($records as $record) {
if (array_key_exists('target', $record)) {
$targets[] = $record['target'];
}
}
return $targets;
} }
/** /**

View File

@ -224,6 +224,7 @@ class Translator
} }
} }
} }
sort($codes);
return $codes; return $codes;
} }

View File

@ -18,8 +18,6 @@ use Icinga\File\Pdf;
use Icinga\Exception\ProgrammingError; use Icinga\Exception\ProgrammingError;
use Icinga\Web\Session; use Icinga\Web\Session;
use Icinga\Web\UrlParams; use Icinga\Web\UrlParams;
use Icinga\Session\SessionNamespace;
use Icinga\Exception\NotReadableError;
use Zend_Controller_Action; use Zend_Controller_Action;
use Zend_Controller_Action_HelperBroker as ActionHelperBroker; use Zend_Controller_Action_HelperBroker as ActionHelperBroker;
use Zend_Controller_Request_Abstract as Request; use Zend_Controller_Request_Abstract as Request;
@ -325,14 +323,24 @@ class ActionController extends Zend_Controller_Action
$this->getResponse()->setHeader('X-Icinga-Reload-Css', 'now'); $this->getResponse()->setHeader('X-Icinga-Reload-Css', 'now');
} }
$this->shutdownSession();
$this->getResponse() $this->getResponse()
->setHeader('X-Icinga-Redirect', rawurlencode($url->getAbsoluteUrl())) ->setHeader('X-Icinga-Redirect', rawurlencode($url->getAbsoluteUrl()))
->sendHeaders(); ->sendHeaders();
// TODO: Session shutdown?
exit; exit;
} }
protected function redirectHttp($url)
{
if (! $url instanceof Url) {
$url = Url::fromPath($url);
}
$this->shutdownSession();
$this->_helper->Redirector->gotoUrlAndExit($url->getRelativeUrl());
}
/** /**
* Redirect to a specific url, updating the browsers URL field * Redirect to a specific url, updating the browsers URL field
* *
@ -343,10 +351,7 @@ class ActionController extends Zend_Controller_Action
if ($this->isXhr()) { if ($this->isXhr()) {
$this->redirectXhr($url); $this->redirectXhr($url);
} else { } else {
if (! $url instanceof Url) { $this->redirectHttp($url);
$url = Url::fromPath($url);
}
$this->_helper->Redirector->gotoUrlAndExit($url->getRelativeUrl());
} }
} }
@ -374,6 +379,7 @@ class ActionController extends Zend_Controller_Action
if ($req->getParam('format') === 'pdf') { if ($req->getParam('format') === 'pdf') {
$layout->setLayout('pdf'); $layout->setLayout('pdf');
$this->shutdownSession();
$this->sendAsPdf(); $this->sendAsPdf();
exit; exit;
} }
@ -381,6 +387,8 @@ class ActionController extends Zend_Controller_Action
if ($this->isXhr()) { if ($this->isXhr()) {
$this->postDispatchXhr(); $this->postDispatchXhr();
} }
$this->shutdownSession();
} }
protected function postDispatchXhr() protected function postDispatchXhr()
@ -430,6 +438,14 @@ class ActionController extends Zend_Controller_Action
$pdf->renderControllerAction($this); $pdf->renderControllerAction($this);
} }
protected function shutdownSession()
{
$session = Session::getSession();
if ($session->hasChanged()) {
$session->write();
}
}
/** /**
* Render the benchmark * Render the benchmark
* *

View File

@ -100,11 +100,11 @@ class Form extends Zend_Form
* @var array * @var array
*/ */
public static $defaultElementDecorators = array( public static $defaultElementDecorators = array(
'ViewHelper', array('ViewHelper', array('separator' => '')),
'Errors', array('Errors', array('separator' => '')),
array('Description', array('tag' => 'span', 'class' => 'description')), array('Description', array('tag' => 'span', 'class' => 'description', 'separator' => '')),
'Label', array('Label', array('separator' => '')),
array('HtmlTag', array('tag' => 'div')) array('HtmlTag', array('tag' => 'div', 'class' => 'element'))
); );
/** /**
@ -463,7 +463,15 @@ class Form extends Zend_Form
$el = parent::createElement($type, $name, $options); $el = parent::createElement($type, $name, $options);
if ($el && $el->getAttrib('autosubmit')) { if ($el && $el->getAttrib('autosubmit')) {
$el->addDecorator(new NoScriptApply()); // Non-JS environments $noScript = new NoScriptApply(); // Non-JS environments
$decorators = $el->getDecorators();
$pos = array_search('Zend_Form_Decorator_ViewHelper', array_keys($decorators)) + 1;
$el->setDecorators(
array_slice($decorators, 0, $pos, true)
+ array(get_class($noScript) => $noScript)
+ array_slice($decorators, $pos, count($decorators) - $pos, true)
);
$class = $el->getAttrib('class'); $class = $el->getAttrib('class');
if (is_array($class)) { if (is_array($class)) {
$class[] = 'autosubmit'; $class[] = 'autosubmit';
@ -473,6 +481,7 @@ class Form extends Zend_Form
$class .= ' autosubmit'; $class .= ' autosubmit';
} }
$el->setAttrib('class', $class); // JS environments $el->setAttrib('class', $class); // JS environments
unset($el->autosubmit); unset($el->autosubmit);
} }

View File

@ -0,0 +1,64 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Web\Form\Decorator;
use Zend_Form_Element;
use Zend_Form_Decorator_Abstract;
/**
* A decorator that will double a single element of a display group
*
* The options `condition', `double' and `attributes' can be passed to the constructor and are used to affect whether
* the doubling should take effect, which element should be doubled and which HTML attributes should be applied to the
* doubled element, respectively.
*
* `condition' must be an element's name that when it's part of the display group causes the condition to be met.
* `double' must be an element's name and must be part of the display group.
* `attributes' is just an array of key-value pairs.
*
* You can also pass `placement' to control whether the doubled element is prepended or appended.
*/
class ElementDoubler extends Zend_Form_Decorator_Abstract
{
/**
* Return the display group's elements with an additional copy of an element being added if the condition is met
*
* @param string $content The HTML rendered so far
*
* @return string
*/
public function render($content)
{
$group = $this->getElement();
if ($group->getElement($this->getOption('condition')) !== null) {
if ($this->getPlacement() === static::APPEND) {
return $content . $this->applyAttributes($group->getElement($this->getOption('double')))->render();
} else { // $this->getPlacement() === static::PREPEND
return $this->applyAttributes($group->getElement($this->getOption('double')))->render() . $content;
}
}
return $content;
}
/**
* Apply all element attributes
*
* @param Zend_Form_Element $element The element to apply the attributes to
*
* @return Zend_Form_Element
*/
protected function applyAttributes(Zend_Form_Element $element)
{
$attributes = $this->getOption('attributes');
if ($attributes !== null) {
foreach ($attributes as $name => $value) {
$element->setAttrib($name, $value);
}
}
return $element;
}
}

View File

@ -5,7 +5,6 @@
namespace Icinga\Web\Form\Element; namespace Icinga\Web\Form\Element;
use Zend_Form_Element; use Zend_Form_Element;
use Icinga\Web\Form;
/** /**
* A note * A note
@ -32,7 +31,15 @@ class Note extends Zend_Form_Element
*/ */
public function init() public function init()
{ {
$this->setDecorators(Form::$defaultElementDecorators); if (count($this->getDecorators()) === 0) {
$this->setDecorators(array(
'ViewHelper',
array(
'HtmlTag',
array('tag' => 'p')
)
));
}
} }
/** /**

View File

@ -67,7 +67,7 @@ class JavaScript
public static function send($minified = false) public static function send($minified = false)
{ {
header('Content-Type: application/javascript'); header('Content-Type: application/javascript');
$basedir = Icinga::app()->getBootstrapDirecory(); $basedir = Icinga::app()->getBootstrapDirectory();
$js = $out = ''; $js = $out = '';
$min = $minified ? '.min' : ''; $min = $minified ? '.min' : '';

View File

@ -0,0 +1,90 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Web\Menu;
use Icinga\Module\Monitoring\Backend\MonitoringBackend;
use Icinga\Web\Menu;
use Icinga\Web\Url;
class MonitoringMenuItemRenderer implements MenuItemRenderer {
protected static $summary;
protected $columns = array();
protected static function summary($column = null)
{
if (self::$summary === null) {
self::$summary = MonitoringBackend::instance()->select()->from(
'statusSummary',
array(
'hosts_down_unhandled',
'services_critical_unhandled'
)
)->getQuery()->fetchRow();
}
if ($column === null) {
return self::$summary;
} elseif (isset(self::$summary->$column)) {
return self::$summary->$column;
} else {
return null;
}
}
protected function getBadgeTitle()
{
$translations = array(
'hosts_down_unhandled' => mt('monitoring', '%d unhandled hosts down'),
'services_critical_unhandled' => mt('monitoring', '%d unhandled services critical')
);
$titles = array();
$sum = $this->summary();
foreach ($this->columns as $col) {
if (isset($sum->$col) && $sum->$col > 0) {
$titles[] = sprintf($translations[$col], $sum->$col);
}
}
return implode(', ', $titles);
}
protected function countItems()
{
$sum = self::summary();
$count = 0;
foreach ($this->columns as $col) {
if (isset($sum->$col)) {
$count += $sum->$col;
}
}
return $count;
}
public function render(Menu $menu)
{
$count = $this->countItems();
$badge = '';
if ($count) {
$badge = sprintf(
'<div title="%s" class="badge-container"><span class="badge badge-critical">%s</span></div>',
$this->getBadgeTitle(),
$count
);
}
return sprintf(
'<a href="%s">%s%s%s</a>',
$menu->getUrl() ?: '#',
$menu->getIcon() ? '<img src="' . Url::fromPath($menu->getIcon()) . '" class="icon" /> ' : '',
htmlspecialchars($menu->getTitle()),
$badge
);
}
}

View File

@ -1,39 +1,11 @@
<?php <?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Web\Menu; namespace Icinga\Web\Menu;
use Icinga\Module\Monitoring\Backend; class ProblemMenuItemRenderer extends MonitoringMenuItemRenderer
use Icinga\Web\Menu; {
use Icinga\Web\Url; protected $columns = array(
'hosts_down_unhandled',
class ProblemMenuItemRenderer implements MenuItemRenderer { 'services_critical_unhandled'
);
public function render(Menu $menu)
{
$statusSummary = Backend::createBackend()
->select()->from(
'statusSummary',
array(
'hosts_down_unhandled',
'services_critical_unhandled'
)
)->getQuery()->fetchRow();
$unhandled = $statusSummary->hosts_down_unhandled + $statusSummary->services_critical_unhandled;
$badge = '';
if ($unhandled) {
$badge = sprintf(
'<div class="badge-container"><span class="badge badge-critical">%s</span></div>',
$unhandled
);
}
return sprintf(
'<a href="%s">%s%s %s</a>',
$menu->getUrl() ?: '#',
$menu->getIcon() ? '<img src="' . Url::fromPath($menu->getIcon()) . '" class="icon" /> ' : '',
htmlspecialchars($menu->getTitle()),
$badge
);
}
} }

View File

@ -1,38 +1,12 @@
<?php <?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Web\Menu; namespace Icinga\Web\Menu;
use Icinga\Module\Monitoring\Backend;
use Icinga\Web\Menu; use Icinga\Web\Menu;
use Icinga\Web\Url;
class UnhandledHostMenuItemRenderer implements MenuItemRenderer { class UnhandledHostMenuItemRenderer extends MonitoringMenuItemRenderer
{
public function render(Menu $menu) protected $columns = array(
{ 'hosts_down_unhandled',
$statusSummary = Backend::createBackend() );
->select()->from(
'statusSummary',
array(
'hosts_down_unhandled'
)
)->getQuery()->fetchRow();
$badge = '';
if ($statusSummary->hosts_down_unhandled) {
$badge = sprintf(
'<div title="%s" class="badge-container"><span class="badge badge-critical">%s</span></div>',
t(sprintf('%d unhandled host problems', $statusSummary->hosts_down_unhandled)),
$statusSummary->hosts_down_unhandled
);
}
return sprintf(
'<a href="%s">%s%s %s</a>',
$menu->getUrl() ?: '#',
$menu->getIcon() ? '<img src="' . Url::fromPath($menu->getIcon()) . '" class="icon" /> ' : '',
htmlspecialchars($menu->getTitle()),
$badge
);
}
} }

View File

@ -1,38 +1,12 @@
<?php <?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Web\Menu; namespace Icinga\Web\Menu;
use Icinga\Module\Monitoring\Backend;
use Icinga\Web\Menu; use Icinga\Web\Menu;
use Icinga\Web\Url;
class UnhandledServiceMenuItemRenderer implements MenuItemRenderer { class UnhandledServiceMenuItemRenderer extends MonitoringMenuItemRenderer
{
public function render(Menu $menu) protected $columns = array(
{ 'services_critical_unhandled'
$statusSummary = Backend::createBackend() );
->select()->from(
'statusSummary',
array(
'services_critical_unhandled'
)
)->getQuery()->fetchRow();
$badge = '';
if ($statusSummary->services_critical_unhandled) {
$badge = sprintf(
'<div title="%s" class="badge-container"><span class="badge badge-critical">%s</span></div>',
t(sprintf('%d unhandled service problems', $statusSummary->services_critical_unhandled)),
$statusSummary->services_critical_unhandled
);
}
return sprintf(
'<a href="%s">%s%s %s</a>',
$menu->getUrl() ?: '#',
$menu->getIcon() ? '<img src="' . Url::fromPath($menu->getIcon()) . '" class="icon" /> ' : '',
htmlspecialchars($menu->getTitle()),
$badge
);
}
} }

View File

@ -75,35 +75,28 @@ class Notification
return; return;
} }
$mo = (object) array( $messages = & Session::getSession()->getByRef('messages');
$messages[] = (object) array(
'type' => $type, 'type' => $type,
'message' => $message, 'message' => $message,
); );
// Get, change, set - just to be on the safe side:
$session = Session::getSession();
$msgs = $session->messages;
$msgs[] = $mo;
$session->messages = $msgs;
$session->write();
} }
public function hasMessages() public function hasMessages()
{ {
$session = Session::getSession(); $session = Session::getSession();
return !empty($session->messages); return false === empty($session->messages);
} }
public function getMessages() public function getMessages()
{ {
$session = Session::getSession(); $session = Session::getSession();
$msgs = $session->messages; $messages = $session->messages;
if (false === empty($msgs)) { if (false === empty($messages)) {
$session->messages = array(); $session->messages = array();
$session->write();
} }
return $msgs; return $messages;
} }
final private function __construct() final private function __construct()

View File

@ -21,6 +21,12 @@ class Response extends Zend_Controller_Response_Http
} else { } else {
$this->setRedirect($url->getAbsoluteUrl()); $this->setRedirect($url->getAbsoluteUrl());
} }
$session = Session::getSession();
if ($session->hasChanged()) {
$session->write();
}
$this->sendHeaders(); $this->sendHeaders();
exit; exit;
} }

View File

@ -121,7 +121,7 @@ class PhpSession extends Session
foreach ($_SESSION as $key => $value) { foreach ($_SESSION as $key => $value) {
if (strpos($key, self::NAMESPACE_PREFIX) === 0) { if (strpos($key, self::NAMESPACE_PREFIX) === 0) {
$namespace = new SessionNamespace($this); $namespace = new SessionNamespace();
$namespace->setAll($value); $namespace->setAll($value);
$this->namespaces[substr($key, strlen(self::NAMESPACE_PREFIX))] = $namespace; $this->namespaces[substr($key, strlen(self::NAMESPACE_PREFIX))] = $namespace;
} else { } else {

View File

@ -75,7 +75,7 @@ abstract class Session extends SessionNamespace
unset($this->removedNamespaces[array_search($identifier, $this->removedNamespaces)]); unset($this->removedNamespaces[array_search($identifier, $this->removedNamespaces)]);
} }
$this->namespaces[$identifier] = new SessionNamespace($this); $this->namespaces[$identifier] = new SessionNamespace();
} }
return $this->namespaces[$identifier]; return $this->namespaces[$identifier];
@ -104,13 +104,22 @@ abstract class Session extends SessionNamespace
$this->removedNamespaces[] = $identifier; $this->removedNamespaces[] = $identifier;
} }
/**
* Return whether the session has changed
*
* @return bool
*/
public function hasChanged()
{
return parent::hasChanged() || false === empty($this->namespaces) || false === empty($this->removedNamespaces);
}
/** /**
* Clear all values and namespaces from the session cache * Clear all values and namespaces from the session cache
*/ */
public function clear() public function clear()
{ {
$this->values = array(); parent::clear();
$this->removed = array();
$this->namespaces = array(); $this->namespaces = array();
$this->removedNamespaces = array(); $this->removedNamespaces = array();
} }

View File

@ -14,13 +14,6 @@ use IteratorAggregate;
*/ */
class SessionNamespace implements IteratorAggregate class SessionNamespace implements IteratorAggregate
{ {
/**
* The session this namespace is associated to
*
* @var Session
*/
protected $session;
/** /**
* The actual values stored in this container * The actual values stored in this container
* *
@ -35,16 +28,6 @@ class SessionNamespace implements IteratorAggregate
*/ */
protected $removed = array(); protected $removed = array();
/**
* Create a new session namespace
*
* @param Session $session The session this namespace is associated to
*/
public function __construct(Session $session = null)
{
$this->session = $session;
}
/** /**
* Return an iterator for all values in this namespace * Return an iterator for all values in this namespace
* *
@ -120,7 +103,18 @@ class SessionNamespace implements IteratorAggregate
$this->values[$key] = $value; $this->values[$key] = $value;
if (in_array($key, $this->removed)) { if (in_array($key, $this->removed)) {
unset($this->removed[array_search($key, $this->values)]); unset($this->removed[array_search($key, $this->removed)]);
}
return $this;
}
public function setByRef($key, &$value)
{
$this->values[$key] = & $value;
if (in_array($key, $this->removed)) {
unset($this->removed[array_search($key, $this->removed)]);
} }
return $this; return $this;
@ -139,6 +133,16 @@ class SessionNamespace implements IteratorAggregate
return isset($this->values[$key]) ? $this->values[$key] : $default; return isset($this->values[$key]) ? $this->values[$key] : $default;
} }
public function & getByRef($key, $default = null)
{
$value = $default;
if (isset($this->values[$key])) {
$value = & $this->values[$key];
}
return $value;
}
/** /**
* Delete the given value from the session * Delete the given value from the session
* *
@ -177,14 +181,21 @@ class SessionNamespace implements IteratorAggregate
} }
/** /**
* Save the session this namespace is associated to * Return whether the session namespace has been changed
*
* @return bool
*/ */
public function write() public function hasChanged()
{ {
if (!$this->session) { return false === empty($this->values) || false === empty($this->removed);
throw new IcingaException('Cannot save, session not set'); }
}
$this->session->write(); /**
* Clear all values from the session namespace
*/
public function clear()
{
$this->values = array();
$this->removed = array();
} }
} }

View File

@ -19,6 +19,7 @@ class StyleSheet
'css/icinga/main-content.less', 'css/icinga/main-content.less',
'css/icinga/tabs.less', 'css/icinga/tabs.less',
'css/icinga/forms.less', 'css/icinga/forms.less',
'css/icinga/setup.less',
'css/icinga/widgets.less', 'css/icinga/widgets.less',
'css/icinga/pagination.less', 'css/icinga/pagination.less',
'css/icinga/monitoring-colors.less', 'css/icinga/monitoring-colors.less',
@ -30,7 +31,7 @@ class StyleSheet
public static function compileForPdf() public static function compileForPdf()
{ {
$less = new LessCompiler(); $less = new LessCompiler();
$basedir = Icinga::app()->getBootstrapDirecory(); $basedir = Icinga::app()->getBootstrapDirectory();
foreach (self::$lessFiles as $file) { foreach (self::$lessFiles as $file) {
$less->addFile($basedir . '/' . $file); $less->addFile($basedir . '/' . $file);
} }
@ -56,7 +57,7 @@ class StyleSheet
public static function send($minified = false) public static function send($minified = false)
{ {
$app = Icinga::app(); $app = Icinga::app();
$basedir = $app->getBootstrapDirecory(); $basedir = $app->getBootstrapDirectory();
foreach (self::$lessFiles as $file) { foreach (self::$lessFiles as $file) {
$lessFiles[] = $basedir . '/' . $file; $lessFiles[] = $basedir . '/' . $file;
} }

View File

@ -201,7 +201,7 @@ class Tab extends AbstractWidget
$caption = $view->escape($this->title); $caption = $view->escape($this->title);
if ($this->icon !== null) { if ($this->icon !== null) {
$caption = $view->img($this->icon, array('class' => 'icon')) . ' ' . $caption; $caption = $view->img($this->icon, array('class' => 'icon')) . $caption;
} }
if ($this->url !== null) { if ($this->url !== null) {
$this->url->overwriteParams($this->urlParams); $this->url->overwriteParams($this->urlParams);

View File

@ -0,0 +1,522 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Web;
use LogicException;
use InvalidArgumentException;
use Icinga\Web\Session\SessionNamespace;
use Icinga\Web\Form\Decorator\ElementDoubler;
/**
* Container and controller for form based wizards
*/
class Wizard
{
/**
* An integer describing the wizard's forward direction
*/
const FORWARD = 0;
/**
* An integer describing the wizard's backward direction
*/
const BACKWARD = 1;
/**
* An integer describing that the wizard does not change its position
*/
const NO_CHANGE = 2;
/**
* The name of the button to advance the wizard's position
*/
const BTN_NEXT = 'btn_next';
/**
* The name of the button to rewind the wizard's position
*/
const BTN_PREV = 'btn_prev';
/**
* The name of the wizard's current page
*
* @var string
*/
protected $currentPage;
/**
* The pages being part of this wizard
*
* @var array
*/
protected $pages = array();
/**
* Initialize a new wizard
*/
public function __construct()
{
$this->init();
}
/**
* Run additional initialization routines
*
* Should be implemented by subclasses to add pages to the wizard.
*/
protected function init()
{
}
/**
* Return the pages being part of this wizard
*
* @return array
*/
public function getPages()
{
return $this->pages;
}
/**
* Return the page with the given name
*
* @param string $name The name of the page to return
*
* @return null|Form The page or null in case there is no page with the given name
*/
public function getPage($name)
{
foreach ($this->getPages() as $page) {
if ($name === $page->getName()) {
return $page;
}
}
}
/**
* Add a new page to this wizard
*
* @param Form $page The page to add to the wizard
*
* @return self
*/
public function addPage(Form $page)
{
$this->pages[] = $page;
return $this;
}
/**
* Add multiple pages to this wizard
*
* @param array $pages The pages to add to the wizard
*
* @return self
*/
public function addPages(array $pages)
{
foreach ($pages as $page) {
$this->addPage($page);
}
return $this;
}
/**
* Assert that this wizard has any pages
*
* @throws LogicException In case this wizard has no pages
*/
protected function assertHasPages()
{
$pages = $this->getPages();
if (count($pages) < 2) {
throw new LogicException("Although Chuck Norris can advance a wizard with less than two pages, you can't.");
}
}
/**
* Return the current page of this wizard
*
* @return Form
*
* @throws LogicException In case the name of the current page currently being set is invalid
*/
public function getCurrentPage()
{
if ($this->currentPage === null) {
$this->assertHasPages();
$pages = $this->getPages();
$this->currentPage = $this->getSession()->get('current_page', $pages[0]->getName());
}
if (($page = $this->getPage($this->currentPage)) === null) {
throw new LogicException(sprintf('No page found with name "%s"', $this->currentPage));
}
return $page;
}
/**
* Set the current page of this wizard
*
* @param Form $page The page to set as current page
*
* @return self
*/
public function setCurrentPage(Form $page)
{
$this->currentPage = $page->getName();
$this->getSession()->set('current_page', $this->currentPage);
return $this;
}
/**
* Setup the given page that is either going to be displayed or validated
*
* Implement this method in a subclass to populate default values and/or other data required to process the form.
*
* @param Form $page The page to setup
* @param Request $request The current request
*/
public function setupPage(Form $page, Request $request)
{
}
/**
* Process the given request using this wizard
*
* Validate the request data using the current page, update the wizard's
* position and redirect to the page's redirect url upon success.
*
* @param Request $request The request to be processed
*
* @return Request The request supposed to be processed
*/
public function handleRequest(Request $request = null)
{
$page = $this->getCurrentPage();
if ($request === null) {
$request = $page->getRequest();
}
$this->setupPage($page, $request);
$requestData = $page->getRequestData($request);
if ($page->wasSent($requestData)) {
if (($requestedPage = $this->getRequestedPage($requestData)) !== null) {
$isValid = false;
$direction = $this->getDirection($request);
if ($direction === static::FORWARD && $page->isValid($requestData)) {
$isValid = true;
if ($this->isLastPage($page)) {
$this->setIsFinished();
}
} elseif ($direction === static::BACKWARD) {
$page->populate($requestData);
$isValid = true;
}
if ($isValid) {
$pageData = & $this->getPageData();
$pageData[$page->getName()] = $page->getValues();
$this->setCurrentPage($this->getNewPage($requestedPage, $page));
$page->getResponse()->redirectAndExit($page->getRedirectUrl());
}
} else {
$page->isValidPartial($requestData);
}
} elseif (($pageData = $this->getPageData($page->getName())) !== null) {
$page->populate($pageData);
}
return $request;
}
/**
* Return the name of the requested page
*
* @param array $requestData The request's data
*
* @return null|string The name of the requested page or null in case no page has been requested
*/
protected function getRequestedPage(array $requestData)
{
if (isset($requestData[static::BTN_NEXT])) {
return $requestData[static::BTN_NEXT];
} elseif (isset($requestData[static::BTN_PREV])) {
return $requestData[static::BTN_PREV];
}
}
/**
* Return the direction of this wizard using the given request
*
* @param Request $request The request to use
*
* @return int The direction @see Wizard::FORWARD @see Wizard::BACKWARD @see Wizard::NO_CHANGE
*/
protected function getDirection(Request $request = null)
{
$currentPage = $this->getCurrentPage();
if ($request === null) {
$request = $currentPage->getRequest();
}
$requestData = $currentPage->getRequestData($request);
if (isset($requestData[static::BTN_NEXT])) {
return static::FORWARD;
} elseif (isset($requestData[static::BTN_PREV])) {
return static::BACKWARD;
}
return static::NO_CHANGE;
}
/**
* Return the new page to set as current page
*
* Permission is checked by verifying that the requested page's previous page has page data available.
* The requested page is automatically permitted without any checks if the origin page is its previous
* page or one that occurs later in order.
*
* @param string $requestedPage The name of the requested page
* @param Form $originPage The origin page
*
* @return Form The new page
*
* @throws InvalidArgumentException In case the requested page does not exist or is not permitted yet
*/
protected function getNewPage($requestedPage, Form $originPage)
{
if (($page = $this->getPage($requestedPage)) !== null) {
$permitted = true;
$pages = $this->getPages();
if (($index = array_search($page, $pages, true)) > 0) {
$previousPage = $pages[$index - 1];
if ($originPage === null || ($previousPage->getName() !== $originPage->getName()
&& array_search($originPage, $pages, true) < $index))
{
$permitted = $this->hasPageData($previousPage->getName());
}
}
if ($permitted) {
return $page;
}
}
throw new InvalidArgumentException(
sprintf('"%s" is either an unknown page or one you are not permitted to view', $requestedPage)
);
}
/**
* Return whether the given page is this wizard's last page
*
* @param Form $page The page to check
*
* @return bool
*/
protected function isLastPage(Form $page)
{
$pages = $this->getPages();
return $page->getName() === end($pages)->getName();
}
/**
* Set whether this wizard has been completed
*
* @param bool $state Whether this wizard has been completed
*
* @return self
*/
public function setIsFinished($state = true)
{
$this->getSession()->set('isFinished', $state);
return $this;
}
/**
* Return whether this wizard has been completed
*
* @return bool
*/
public function isFinished()
{
return $this->getSession()->get('isFinished', false);
}
/**
* Return the overall page data or one for a particular page
*
* Note that this method returns by reference so in order to update the
* returned array set this method's return value also by reference.
*
* @param string $pageName The page for which to return the data
*
* @return array
*/
public function & getPageData($pageName = null)
{
$session = $this->getSession();
if (false === isset($session->page_data)) {
$session->page_data = array();
}
$pageData = & $session->getByRef('page_data');
if ($pageName !== null) {
$data = null;
if (isset($pageData[$pageName])) {
$data = & $pageData[$pageName];
}
return $data;
}
return $pageData;
}
/**
* Return whether there is any data for the given page
*
* @param string $pageName The name of the page to check
*
* @return bool
*/
public function hasPageData($pageName)
{
return $this->getPageData($pageName) !== null;
}
/**
* Return a session to be used by this wizard
*
* @return SessionNamespace
*/
public function getSession()
{
return Session::getSession()->getNamespace(get_class($this));
}
/**
* Clear the session being used by this wizard
*/
public function clearSession()
{
$this->getSession()->clear();
}
/**
* Add buttons to the given page based on its position in the page-chain
*
* @param Form $page The page to add the buttons to
*/
protected function addButtons(Form $page)
{
$pages = $this->getPages();
$index = array_search($page, $pages, true);
if ($index === 0) {
$page->addElement(
'button',
static::BTN_NEXT,
array(
'type' => 'submit',
'value' => $pages[1]->getName(),
'label' => t('Next'),
'decorators' => array('ViewHelper')
)
);
} elseif ($index < count($pages) - 1) {
$page->addElement(
'button',
static::BTN_PREV,
array(
'type' => 'submit',
'value' => $pages[$index - 1]->getName(),
'label' => t('Back'),
'decorators' => array('ViewHelper')
)
);
$page->addElement(
'button',
static::BTN_NEXT,
array(
'type' => 'submit',
'value' => $pages[$index + 1]->getName(),
'label' => t('Next'),
'decorators' => array('ViewHelper')
)
);
} else {
$page->addElement(
'button',
static::BTN_PREV,
array(
'type' => 'submit',
'value' => $pages[$index - 1]->getName(),
'label' => t('Back'),
'decorators' => array('ViewHelper')
)
);
$page->addElement(
'button',
static::BTN_NEXT,
array(
'type' => 'submit',
'value' => $page->getName(),
'label' => t('Finish'),
'decorators' => array('ViewHelper')
)
);
}
$page->addDisplayGroup(
array(static::BTN_PREV, static::BTN_NEXT),
'buttons',
array(
'decorators' => array(
'FormElements',
new ElementDoubler(array(
'double' => static::BTN_NEXT,
'condition' => static::BTN_PREV,
'placement' => ElementDoubler::PREPEND,
'attributes' => array('tabindex' => -1, 'class' => 'double')
)),
array('HtmlTag', array('tag' => 'div', 'class' => 'buttons'))
)
)
);
}
/**
* Return the current page of this wizard with appropriate buttons being added
*
* @return Form
*/
public function getForm()
{
$form = $this->getCurrentPage();
$form->create(); // Make sure that buttons are displayed at the very bottom
$this->addButtons($form);
return $form;
}
/**
* Return the current page of this wizard rendered as HTML
*
* @return string
*/
public function __toString()
{
return (string) $this->getForm();
}
}

View File

@ -572,12 +572,12 @@ class Monitoring_ListController extends Controller
$this->view->history = $query->paginate(); $this->view->history = $query->paginate();
} }
public function servicematrixAction() public function servicegridAction()
{ {
if ($url = $this->hasBetterUrl()) { if ($url = $this->hasBetterUrl()) {
return $this->redirectNow($url); return $this->redirectNow($url);
} }
$this->addTitleTab('servicematrix'); $this->addTitleTab('servicegrid', $this->translate('Service Grid'));
$this->setAutorefreshInterval(15); $this->setAutorefreshInterval(15);
$query = $this->backend->select()->from('serviceStatus', array( $query = $this->backend->select()->from('serviceStatus', array(
'host_name', 'host_name',

View File

@ -4,7 +4,7 @@
namespace Icinga\Module\Monitoring\Form\Command; namespace Icinga\Module\Monitoring\Form\Command;
use Icinga\Module\Monitoring\Backend; use Icinga\Module\Monitoring\Backend\MonitoringBackend;
use Icinga\Module\Monitoring\Command\Transport\CommandTransport; use Icinga\Module\Monitoring\Command\Transport\CommandTransport;
use Icinga\Web\Form; use Icinga\Web\Form;
use Icinga\Web\Request; use Icinga\Web\Request;
@ -24,11 +24,11 @@ abstract class CommandForm extends Form
/** /**
* Set the monitoring backend * Set the monitoring backend
* *
* @param Backend $backend * @param MonitoringBackend $backend
* *
* @return $this * @return $this
*/ */
public function setBackend(Backend $backend) public function setBackend(MonitoringBackend $backend)
{ {
$this->backend = $backend; $this->backend = $backend;
return $this; return $this;

View File

@ -55,7 +55,8 @@ class SecurityConfigForm extends ConfigForm
'text', 'text',
'protected_customvars', 'protected_customvars',
array( array(
'required' => true, 'allowEmpty' => true,
'value' => '*pw*,*pass*,community',
'label' => mt('monitoring', 'Protected Custom Variables'), 'label' => mt('monitoring', 'Protected Custom Variables'),
'description' => mt('monitoring', 'description' => mt('monitoring',
'Comma separated case insensitive list of protected custom variables.' 'Comma separated case insensitive list of protected custom variables.'

View File

@ -0,0 +1,72 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Monitoring\Form\Setup;
use Icinga\Web\Form;
use Icinga\Web\Form\Element\Note;
use Icinga\Application\Platform;
class BackendPage extends Form
{
public function init()
{
$this->setName('setup_monitoring_backend');
}
public function createElements(array $formData)
{
$this->addElement(
new Note(
'title',
array(
'value' => mt('monitoring', 'Monitoring Backend', 'setup.page.title'),
'decorators' => array(
'ViewHelper',
array('HtmlTag', array('tag' => 'h2'))
)
)
)
);
$this->addElement(
new Note(
'description',
array(
'value' => mt(
'monitoring',
'Please configure below how Icinga Web 2 should retrieve monitoring information.'
)
)
)
);
$this->addElement(
'text',
'name',
array(
'required' => true,
'value' => 'icinga',
'label' => mt('monitoring', 'Backend Name'),
'description' => mt('monitoring', 'The identifier of this backend')
)
);
$resourceTypes = array();
if (Platform::extensionLoaded('mysql') || Platform::extensionLoaded('pgsql')) {
$resourceTypes['ido'] = 'IDO';
}
$resourceTypes['livestatus'] = 'Livestatus';
$this->addElement(
'select',
'type',
array(
'required' => true,
'label' => mt('monitoring', 'Backend Type'),
'description' => mt('monitoring', 'The data source used for retrieving monitoring information'),
'multiOptions' => $resourceTypes
)
);
}
}

View File

@ -0,0 +1,102 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Monitoring\Form\Setup;
use Icinga\Web\Form;
use Icinga\Web\Form\Element\Note;
use Icinga\Form\Config\Resource\DbResourceForm;
class IdoResourcePage extends Form
{
public function init()
{
$this->setName('setup_monitoring_ido');
}
public function createElements(array $formData)
{
$this->addElement(
'hidden',
'type',
array(
'required' => true,
'value' => 'db'
)
);
$this->addElement(
new Note(
'title',
array(
'value' => mt('monitoring', 'Monitoring IDO Resource', 'setup.page.title'),
'decorators' => array(
'ViewHelper',
array('HtmlTag', array('tag' => 'h2'))
)
)
)
);
$this->addElement(
new Note(
'description',
array(
'value' => mt(
'monitoring',
'Please fill out the connection details below to access'
. ' the IDO database of your monitoring environment.'
)
)
)
);
if (isset($formData['skip_validation']) && $formData['skip_validation']) {
$this->addSkipValidationCheckbox();
} else {
$this->addElement(
'hidden',
'skip_validation',
array(
'required' => true,
'value' => 0
)
);
}
$livestatusResourceForm = new DbResourceForm();
$this->addElements($livestatusResourceForm->createElements($formData)->getElements());
$this->getElement('name')->setValue('icinga_ido');
}
public function isValid($data)
{
if (false === parent::isValid($data)) {
return false;
}
if (false === isset($data['skip_validation']) || $data['skip_validation'] == 0) {
if (false === DbResourceForm::isValidResource($this)) {
$this->addSkipValidationCheckbox();
return false;
}
}
return true;
}
/**
* Add a checkbox to the form by which the user can skip the connection validation
*/
protected function addSkipValidationCheckbox()
{
$this->addElement(
'checkbox',
'skip_validation',
array(
'required' => true,
'label' => t('Skip Validation'),
'description' => t('Check this to not to validate connectivity with the given database server')
)
);
}
}

View File

@ -0,0 +1,53 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Monitoring\Form\Setup;
use Icinga\Web\Form;
use Icinga\Web\Form\Element\Note;
use Icinga\Module\Monitoring\Form\Config\InstanceConfigForm;
class InstancePage extends Form
{
public function init()
{
$this->setName('setup_monitoring_instance');
}
public function createElements(array $formData)
{
$this->addElement(
new Note(
'title',
array(
'value' => mt('monitoring', 'Monitoring Instance', 'setup.page.title'),
'decorators' => array(
'ViewHelper',
array('HtmlTag', array('tag' => 'h2'))
)
)
)
);
$this->addElement(
new Note(
'description',
array(
'value' => mt(
'monitoring',
'Please define the settings specific to your monitoring instance below.'
)
)
)
);
if (isset($formData['host'])) {
$formData['type'] = 'remote'; // This is necessary as the type element gets ignored by Form::getValues()
}
$instanceConfigForm = new InstanceConfigForm();
$instanceConfigForm->createElements($formData);
$this->addElements($instanceConfigForm->getElements());
$this->getElement('name')->setValue('icinga');
}
}

View File

@ -0,0 +1,102 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Monitoring\Form\Setup;
use Icinga\Web\Form;
use Icinga\Web\Form\Element\Note;
use Icinga\Form\Config\Resource\LivestatusResourceForm;
class LivestatusResourcePage extends Form
{
public function init()
{
$this->setName('setup_monitoring_livestatus');
}
public function createElements(array $formData)
{
$this->addElement(
'hidden',
'type',
array(
'required' => true,
'value' => 'livestatus'
)
);
$this->addElement(
new Note(
'title',
array(
'value' => mt('monitoring', 'Monitoring Livestatus Resource', 'setup.page.title'),
'decorators' => array(
'ViewHelper',
array('HtmlTag', array('tag' => 'h2'))
)
)
)
);
$this->addElement(
new Note(
'description',
array(
'value' => mt(
'monitoring',
'Please fill out the connection details below to access the Livestatus'
. ' socket interface for your monitoring environment.'
)
)
)
);
if (isset($formData['skip_validation']) && $formData['skip_validation']) {
$this->addSkipValidationCheckbox();
} else {
$this->addElement(
'hidden',
'skip_validation',
array(
'required' => true,
'value' => 0
)
);
}
$livestatusResourceForm = new LivestatusResourceForm();
$this->addElements($livestatusResourceForm->createElements($formData)->getElements());
$this->getElement('name')->setValue('icinga_livestatus');
}
public function isValid($data)
{
if (false === parent::isValid($data)) {
return false;
}
if (false === isset($data['skip_validation']) || $data['skip_validation'] == 0) {
if (false === LivestatusResourceForm::isValidResource($this)) {
$this->addSkipValidationCheckbox();
return false;
}
}
return true;
}
/**
* Add a checkbox to the form by which the user can skip the connection validation
*/
protected function addSkipValidationCheckbox()
{
$this->addElement(
'checkbox',
'skip_validation',
array(
'required' => true,
'label' => t('Skip Validation'),
'description' => t('Check this to not to validate connectivity with the given Livestatus socket')
)
);
}
}

View File

@ -0,0 +1,48 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Monitoring\Form\Setup;
use Icinga\Web\Form;
use Icinga\Web\Form\Element\Note;
use Icinga\Module\Monitoring\Form\Config\SecurityConfigForm;
class SecurityPage extends Form
{
public function init()
{
$this->setName('setup_monitoring_security');
}
public function createElements(array $formData)
{
$this->addElement(
new Note(
'title',
array(
'value' => mt('monitoring', 'Monitoring Security', 'setup.page.title'),
'decorators' => array(
'ViewHelper',
array('HtmlTag', array('tag' => 'h2'))
)
)
)
);
$this->addElement(
new Note(
'description',
array(
'value' => mt(
'monitoring',
'To protect your monitoring environment against prying eyes please fill out the settings below.'
)
)
)
);
$securityConfigForm = new SecurityConfigForm();
$securityConfigForm->createElements($formData);
$this->addElements($securityConfigForm->getElements());
}
}

View File

@ -0,0 +1,68 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Monitoring\Form\Setup;
use Icinga\Web\Form;
use Icinga\Web\Form\Element\Note;
class WelcomePage extends Form
{
public function init()
{
$this->setName('setup_monitoring_welcome');
}
public function createElements(array $formData)
{
$this->addElement(
new Note(
'welcome',
array(
'value' => mt(
'monitoring',
'Welcome to the configuration of the monitoring module for Icinga Web 2!'
),
'decorators' => array(
'ViewHelper',
array('HtmlTag', array('tag' => 'h2'))
)
)
)
);
$this->addElement(
new Note(
'core_hint',
array(
'value' => mt('monitoring', 'This is the core module for Icinga Web 2.')
)
)
);
$this->addElement(
new Note(
'description',
array(
'value' => mt(
'monitoring',
'It offers various status and reporting views with powerful filter capabilities that allow'
. ' you to keep track of the most important events in your monitoring environment.'
)
)
)
);
$this->addDisplayGroup(
array('core_hint', 'description'),
'info',
array(
'decorators' => array(
'FormElements',
array('HtmlTag', array('tag' => 'div', 'class' => 'info'))
)
)
);
}
}

View File

@ -95,8 +95,11 @@ class Zend_View_Helper_MonitoringState extends Zend_View_Helper_Abstract
*/ */
public function getStateTitle($object, $type) public function getStateTitle($object, $type)
{ {
return strtoupper($this->monitoringState($object, $type)) return sprintf(
. ' since ' '%s %s %s',
. date('Y-m-d H:i:s', $object->{$type.'_last_state_change'}); $this->view->translate(strtoupper($this->monitoringState($object, $type))),
$this->view->translate('since'),
date('Y-m-d H:i:s', $object->{$type.'_last_state_change'})
);
} }
} }

View File

@ -0,0 +1,77 @@
<?php if (!$this->compact): ?>
<div class="controls">
<?= $this->tabs; ?>
<div style="margin: 1em;" class="dontprint">
<?= $this->translate('Sort by'); ?> <?= $this->sortControl; ?>
</div>
</div>
<?php endif ?>
<div class="content" data-base-target="_next">
<table class="pivot servicestates">
<?php
$hasHeader = false;
$pivotData = $this->pivot->toArray();
$hostFilter = '(' . implode('|', array_keys($pivotData)) . ')';
?>
<?php if (count($pivotData) === 0): ?>
<?= $this->translate('No Services matching the filter'); ?>
<?php endif ?>
<?php foreach ($pivotData as $host_name => $serviceStates): ?>
<?php if (!$hasHeader): ?>
<thead>
<tr>
<th><?= $this->partial(
'joystickPagination.phtml',
'default',
array(
'xAxisPaginator' => $horizontalPaginator,
'yAxisPaginator' => $verticalPaginator
)
); ?></th>
<th colspan="<?= count($serviceStates); ?>">
<div>
<?php foreach (array_keys($serviceStates) as $service_description): ?>
<span>
<a href="<?= $this->href(
'monitoring/list/services',
array(
'service_description' => $service_description,
'host_name' => $hostFilter
)
); ?>">
<abbr title="<?= $service_description; ?>">
<?= strlen($service_description) > 18 ? substr($service_description, 0, 18) . '...' : $service_description; ?>
</abbr>
</a>
</span>
<?php endforeach ?>
</div>
</th>
</tr>
</thead>
<tbody>
<?php $hasHeader = true; ?>
<?php endif ?>
<tr>
<th>
<a href="<?= $this->href('monitoring/show/services', array('host' => $host_name)); ?>"><?= $host_name; ?></a>
</th>
<?php foreach (array_values($serviceStates) as $service): ?>
<?php if ($service !== null): ?>
<td>
<a href="<?= $this->href('monitoring/show/service', array(
'host' => $service->host_name,
'service' => $service->service_description
)); ?>" title="<?= $this->escape($service->service_output); ?>" class="state_<?= $this->monitoringState($service, 'service'); ?> <?= $service->service_handled ? 'handled' : ''; ?>"></a>
</td>
<?php else: ?>
<td>&middot;</td>
<?php endif ?>
<?php endforeach ?>
</tr>
<?php endforeach ?>
</tbody>
</table>
</div>

View File

@ -1,84 +0,0 @@
<?php if (!$this->compact): ?>
<div class="controls">
<?= $this->tabs; ?>
<div style="margin: 1em;" class="dontprint">
<?= $this->translate('Sort by') ?> <?= $this->sortControl ?>
</div>
<?= $this->partial(
'pivottablePagination.phtml',
'default',
array(
'xAxisPaginator' => $this->horizontalPaginator,
'yAxisPaginator' => $this->verticalPaginator
)
); ?>
</div>
<?php endif ?>
<div class="content" data-base-target="_next">
<table class="pivot servicestates">
<?php
$hasHeader = false;
$pivotData = $this->pivot->toArray();
$hostFilter = '(' . implode('|', array_keys($pivotData)) . ')';
?>
<?php if (count($pivotData) === 0): ?>
<?= $this->translate('No Services matching the filter'); ?>
<?php endif ?>
<?php foreach ($pivotData as $host_name => $serviceStates): ?>
<?php if (!$hasHeader): ?>
<thead>
<tr>
<th>&nbsp;</th>
<th colspan="<?= count($serviceStates); ?>">
<div>
<?php foreach (array_keys($serviceStates) as $service_description): ?>
<span>
<a href="<?= $this->href(
'monitoring/list/services',
array(
'service_description' => $service_description,
'host_name' => $hostFilter
)
); ?>">
<abbr title="<?= $service_description; ?>"><?=
strlen($service_description) > 18 ?
substr($service_description, 0, 18) . '...' :
$service_description
?></abbr>
</a>
</span>
<?php endforeach ?>
</div>
</th>
</tr>
</thead>
<tbody>
<?php $hasHeader = true; ?>
<?php endif ?>
<tr>
<th>
<a href="<?=
$this->href('monitoring/show/services', array('host' => $host_name))
?>"><?= $host_name ?></a>
</th>
<?php foreach (array_values($serviceStates) as $service): ?>
<?php if ($service !== null): ?>
<td>
<a href="<?= $this->href('monitoring/show/service', array(
'host' => $service->host_name,
'service' => $service->service_description
)) ?>" title="<?= $this->escape($service->service_output)
?>" class="state_<?= $this->monitoringState($service, 'service') ?> <?= $service->service_handled ? 'handled' : '' ?>"></a>
</td>
<?php else: ?>
<td>&middot;</td>
<?php endif ?>
<?php endforeach ?>
</tr>
<?php endforeach ?>
</tbody>
</table>
</div>

View File

@ -1,123 +0,0 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
use Icinga\Module\Monitoring\Backend;
use Icinga\Util\Format;
$backend = Backend::getInstance($params->shift('backend'));
$query = $backend->select()->from('status', array(
'host_name',
'host_state',
'host_output',
'host_acknowledged',
'host_in_downtime',
'service_description',
'service_state',
'service_acknowledged',
'service_in_downtime',
'service_handled',
'service_output',
'service_last_state_change'
))->order('service_last_state_change ASC');
$endless = $params->shift('endless');
$query->applyFilters($params->getParams());
$host_colors = array(
0 => '2', // UP
1 => '1', // DOWN
2 => '3', // UNREACH (brown)
99 => '0', // PEND
);
$host_states = array(
0 => 'UP', // UP
1 => 'DOWN', // DOWN
2 => 'UNREACHABLE', // UNREACH (brown)
99 => 'PENDING', // PEND
);
$service_colors = array(
0 => '2', // OK
1 => '3', // WARN
2 => '1', // CRIT
3 => '5', // UNKN
99 => '0', // PEND
);
$service_states = array(
0 => 'OK', // OK
1 => 'WARNING', // WARN
2 => 'CRITICAL', // CRIT
3 => 'UNKNOWN', // UNKN
99 => 'PENDING', // PEND
);
$finished = false;
while (! $finished) {
$out = '';
$last_host = null;
foreach ($query->fetchAll() as $key => $row) {
$host_extra = array();
if ($row->host_in_downtime) {
$host_extra[] = 'DOWNTIME';
}
if ($row->host_acknowledged) {
$host_extra[] = 'ACK';
}
if (empty($host_extra)) {
$host_extra = '';
} else {
$host_extra = " \033[34;1m[" . implode(',', $host_extra) . "]\033[0m";
}
$service_extra = array();
if ($row->service_in_downtime) {
$service_extra[] = 'DOWNTIME';
}
if ($row->service_acknowledged) {
$service_extra[] = 'ACK';
}
if (empty($service_extra)) {
$service_extra = '';
} else {
$service_extra = " \033[34;52;1m[" . implode(',', $service_extra) . "]\033[0m";
}
if ($row->host_name !== $last_host) {
$out .= sprintf(
"\n\033[01;37;4%dm %-5s \033[0m \033[30;1m%s\033[0m%s: %s\n",
$host_colors[$row->host_state],
substr($host_states[$row->host_state], 0, 5),
$row->host_name,
$host_extra,
$row->host_output
);
}
$last_host = $row->host_name;
$out .= sprintf(
"\033[01;37;4%dm \033[01;37;4%dm %4s \033[0m %s%s since %s: %s\n",
$host_colors[$row->host_state],
$service_colors[$row->service_state],
substr($service_states[$row->service_state] . ' ', 0, 4),
$row->service_description,
$service_extra,
Format::timeSince($row->service_last_state_change),
preg_replace('/\n/', sprintf(
"\n\033[01;37;4%dm \033[01;37;4%dm \033[0m ",
$host_colors[$row->host_state],
$service_colors[$row->service_state]
), substr(wordwrap(str_repeat(' ', 30) . preg_replace('~\@{3,}~', '@@@', $row->service_output), 72), 30))
);
}
$out .= "\n";
if ($endless) {
echo "\033[2J\033[1;1H\033[1S" . $out;
sleep(3);
} else {
echo $out;
$finished = true;
}
}

View File

@ -18,6 +18,7 @@ $this->provideConfigTab('security', array(
'title' => 'Security', 'title' => 'Security',
'url' => 'config/security' 'url' => 'config/security'
)); ));
$this->provideSetupWizard('Icinga\Module\Monitoring\MonitoringWizard');
/* /*
* Available Search Urls * Available Search Urls
@ -74,8 +75,8 @@ $section->add($this->translate('Services'), array(
'url' => 'monitoring/list/services', 'url' => 'monitoring/list/services',
'priority' => 50 'priority' => 50
)); ));
$section->add($this->translate('Servicematrix'), array( $section->add($this->translate('Service Grid'), array(
'url' => 'monitoring/list/servicematrix?service_problem=1', 'url' => 'monitoring/list/servicegrid?service_problem=1',
'priority' => 51 'priority' => 51
)); ));
$section->add($this->translate('Servicegroups'), array( $section->add($this->translate('Servicegroups'), array(

View File

@ -1,186 +1,11 @@
<?php <?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}} // TODO: obsolete, remove once MonitoringBackend is in use everywhere
namespace Icinga\Module\Monitoring; namespace Icinga\Module\Monitoring;
use Icinga\Exception\ProgrammingError; use Icinga\Module\Monitoring\Backend\MonitoringBackend;
use Icinga\Data\Selectable;
use Icinga\Data\Queryable;
use Icinga\Data\ConnectionInterface;
use Icinga\Application\Config; class Backend extends MonitoringBackend
use Icinga\Data\ResourceFactory;
use Icinga\Exception\ConfigurationError;
/**
* Data view and query loader tied to a backend type
*/
class Backend implements Selectable, Queryable, ConnectionInterface
{ {
/**
* Resource
*
* @var mixed
*/
protected $resource;
/**
* Type
*
* @var string
*/
protected $type;
protected $name;
/**
* Create a new backend
*
* @param mixed $resource
* @param string $type
*/
public function __construct($resource, $type)
{
$this->resource = $resource;
$this->type = $type;
}
// Temporary workaround, we have no way to know our name
protected function setName($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
/**
* Create a backend
*
* @param string $backendName Name of the backend or null for creating the default backend which is the first INI
* configuration entry not being disabled
*
* @return Backend
* @throws ConfigurationError When no backend has been configured or all backends are disabled or the
* configuration for the requested backend does either not exist or it's disabled
*/
public static function createBackend($backendName = null)
{
$config = Config::module('monitoring', 'backends');
if ($config->count() === 0) {
throw new ConfigurationError(mt('monitoring', 'No backend has been configured'));
}
if ($backendName !== null) {
$backendConfig = $config->get($backendName);
if ($backendConfig === null) {
throw new ConfigurationError('No configuration for backend %s', $backendName);
}
if ((bool) $backendConfig->get('disabled', false) === true) {
throw new ConfigurationError(
mt('monitoring', 'Configuration for backend %s available but backend is disabled'),
$backendName
);
}
} else {
foreach ($config as $name => $backendConfig) {
if ((bool) $backendConfig->get('disabled', false) === false) {
$backendName = $name;
break;
}
}
if ($backendName === null) {
throw new ConfigurationError(mt('monitoring', 'All backends are disabled'));
}
}
$resource = ResourceFactory::create($backendConfig->resource);
if ($backendConfig->type === 'ido' && $resource->getDbType() !== 'oracle') {
// TODO(el): The resource should set the table prefix
$resource->setTablePrefix('icinga_');
}
$backend = new Backend($resource, $backendConfig->type);
$backend->setName($backendName);
return $backend;
}
public function getResource()
{
return $this->resource;
}
/**
* Backend entry point
*
* @return self
*/
public function select()
{
return $this;
}
/**
* Create a data view to fetch data from
*
* @param string $viewName
* @param array $columns
*
* @return DataView
*/
public function from($viewName, array $columns = null)
{
$viewClass = $this->resolveDataViewName($viewName);
return new $viewClass($this, $columns);
}
/**
* View name to class name resolution
*
* @param string $viewName
*
* @return string
* @throws ProgrammingError When the view does not exist
*/
protected function resolveDataViewName($viewName)
{
$viewClass = '\\Icinga\\Module\\Monitoring\\DataView\\' . ucfirst($viewName);
if (!class_exists($viewClass)) {
throw new ProgrammingError(
'DataView %s does not exist',
ucfirst($viewName)
);
}
return $viewClass;
}
public function getQueryClass($name)
{
return $this->resolveQueryName($name);
}
/**
* Query name to class name resolution
*
* @param string $queryName
*
* @return string
* @throws ProgrammingError When the query does not exist for this backend
*/
protected function resolveQueryName($queryName)
{
$queryClass = '\\Icinga\\Module\\Monitoring\\Backend\\'
. ucfirst($this->type)
. '\\Query\\'
. ucfirst($queryName)
. 'Query';
if (!class_exists($queryClass)) {
throw new ProgrammingError(
'Query "%s" does not exist for backend %s',
ucfirst($queryName),
ucfirst($this->type)
);
}
return $queryClass;
}
} }

View File

@ -0,0 +1,9 @@
<?php
namespace Icinga\Module\Monitoring\Backend\Ido;
use Icinga\Module\Monitoring\Backend\MonitoringBackend;
class IdoBackend extends MonitoringBackend
{
}

View File

@ -655,7 +655,6 @@ abstract class IdoQuery extends DbQuery
); );
if ($session !== null) { if ($session !== null) {
$session->version = self::$idoVersion; $session->version = self::$idoVersion;
$session->write(); // <- WHY? I don't want to care about this!
} }
} }
return self::$idoVersion; return self::$idoVersion;

View File

@ -0,0 +1,331 @@
<?php
namespace Icinga\Module\Monitoring\Backend;
use Icinga\Application\Config;
use Icinga\Data\ResourceFactory;
use Icinga\Data\ConnectionInterface;
use Icinga\Data\Queryable;
use Icinga\Data\Selectable;
use Icinga\Exception\ConfigurationError;
use Icinga\Exception\ProgrammingError;
class MonitoringBackend implements Selectable, Queryable, ConnectionInterface
{
/**
* Backend configuration
*
* @var Config
*/
protected $config;
/**
* Resource
*
* @var mixed
*/
protected $resource;
/**
* Type
*
* @var string
*/
protected $type;
/**
* The configured name of this backend
*
* @var string
*/
protected $name;
/**
* Already created instances
*
* @var array
*/
protected static $instances = array();
/**
* Create a new backend
*
* @param string $name
* @param mixed $config
*/
protected function __construct($name, $config)
{
$this->name = $name;
$this->config = $config;
}
/**
* Get a backend instance
*
* You may ask for a specific backend name or get the default one otherwise
*
* @param string $name Backend name
*
* @return MonitoringBackend
*/
public static function instance($name = null)
{
if (! array_key_exists($name, self::$instances)) {
list($foundName, $config) = static::loadConfig($name);
$type = $config->get('type');
$class = implode(
'\\',
array(
__NAMESPACE__,
ucfirst($type),
ucfirst($type) . 'Backend'
)
);
if (!class_exists($class)) {
throw new ConfigurationError(
mt('monitoring', 'There is no "%s" monitoring backend'),
$class
);
}
self::$instances[$name] = new $class($foundName, $config);
if ($name === null) {
self::$instances[$foundName] = self::$instances[$name];
}
}
return self::$instances[$name];
}
/**
* Clear all cached instances. Mostly for testing purposes.
*/
public static function clearInstances()
{
self::$instances = array();
}
/**
* Whether this backend is of a specific type
*
* @param string $type Backend type
*
* @return boolean
*/
public function is($type)
{
return $this->getType() === $type;
}
/**
* Get the configured name of this backend
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Get the backend type name
*
* @return string
*/
public function getType()
{
if ($this->type === null) {
$parts = preg_split('~\\\~', get_class($this));
$class = array_pop($parts);
if (substr($class, -7) === 'Backend') {
$this->type = lcfirst(substr($class, 0, -7));
} else {
throw new ProgrammingError(
'%s is not a valid monitoring backend class name',
$class
);
}
}
return $this->type;
}
/**
* Return the configuration for the first enabled or the given backend
*/
protected static function loadConfig($name = null)
{
$backends = Config::module('monitoring', 'backends');
if ($name === null) {
$count = 0;
foreach ($backends as $name => $config) {
$count++;
if ((bool) $config->get('disabled', false) === false) {
return array($name, $config);
}
}
if ($count === 0) {
$message = mt('monitoring', 'No backend has been configured');
} else {
$message = mt('monitoring', 'All backends are disabled');
}
throw new ConfigurationError($message);
} else {
$config = $backends->get($name);
if ($config === null) {
throw new ConfigurationError(
mt('monitoring', 'No configuration for backend %s'),
$name
);
}
if ((bool) $config->get('disabled', false) === true) {
throw new ConfigurationError(
mt('monitoring', 'Configuration for backend %s is disabled'),
$name
);
}
return array($name, $config);
}
}
/**
* Create a backend
*
* @deprecated
*
* @param string $backendName Name of the backend or null for creating the default backend which is the first INI
* configuration entry not being disabled
*
* @return Backend
* @throws ConfigurationError When no backend has been configured or all backends are disabled or the
* configuration for the requested backend does either not exist or it's disabled
*/
public static function createBackend($name = null)
{
return self::instance($name);
}
/**
* Get this backend's internal resource
*
* @return mixed
*/
public function getResource()
{
if ($this->resource === null) {
$this->resource = ResourceFactory::create($this->config->get('resource'));
if ($this->is('ido') && $this->resource->getDbType() !== 'oracle') {
// TODO(el): The resource should set the table prefix
$this->resource->setTablePrefix('icinga_');
}
}
return $this->resource;
}
/**
* Backend entry point
*
* @return self
*/
public function select()
{
return $this;
}
/**
* Create a data view to fetch data from
*
* @param string $name
* @param array $columns
*
* @return DataView
*/
public function from($name, array $columns = null)
{
$class = $this->buildViewClassName($name);
return new $class($this, $columns);
}
/**
* View name to class name resolution
*
* @param string $viewName
*
* @return string
* @throws ProgrammingError When the view does not exist
*/
protected function buildViewClassName($view)
{
$class = '\\Icinga\\Module\\Monitoring\\DataView\\' . ucfirst($view);
if (!class_exists($class)) {
throw new ProgrammingError(
'DataView %s does not exist',
ucfirst($view)
);
}
return $class;
}
/**
* Get a specific query class instance
*
* @param string $name Query name
* @param array $columns Optional column list
*
* @return Icinga\Data\QueryInterface
*/
public function query($name, $columns = null)
{
$class = $this->buildQueryClassName($name);
if (!class_exists($class)) {
throw new ProgrammingError(
'Query "%s" does not exist for backend %s',
$name,
$this->getType()
);
}
return new $class($this->getResource(), $columns);
}
/**
* Whether this backend supports the given query
*
* @param string $name Query name to check for
*
* @return bool
*/
public function hasQuery($name)
{
return class_exists($this->buildQueryClassName($name));
}
/**
* Query name to class name resolution
*
* @param string $query
*
* @return string
* @throws ProgrammingError When the query does not exist for this backend
*/
protected function buildQueryClassName($query)
{
$parts = preg_split('~\\\~', get_class($this));
array_pop($parts);
array_push($parts, 'Query', ucfirst($query) . 'Query');
return implode('\\', $parts);
}
}

View File

@ -0,0 +1,175 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Monitoring;
use Exception;
use Icinga\Module\Setup\Step;
use Icinga\Application\Config;
use Icinga\File\Ini\IniWriter;
class BackendStep extends Step
{
protected $data;
protected $backendIniError;
protected $resourcesIniError;
public function __construct(array $data)
{
$this->data = $data;
}
public function apply()
{
$success = $this->createBackendsIni();
$success &= $this->createResourcesIni();
return $success;
}
protected function createBackendsIni()
{
$config = array();
$config[$this->data['backendConfig']['name']] = array(
'type' => $this->data['backendConfig']['type'],
'resource' => $this->data['resourceConfig']['name']
);
try {
$writer = new IniWriter(array(
'config' => new Config($config),
'filename' => Config::resolvePath('modules/monitoring/backends.ini')
));
$writer->write();
} catch (Exception $e) {
$this->backendIniError = $e;
return false;
}
$this->backendIniError = false;
return true;
}
protected function createResourcesIni()
{
$resourceConfig = $this->data['resourceConfig'];
$resourceName = $resourceConfig['name'];
unset($resourceConfig['name']);
try {
$config = Config::app('resources', true);
$config->merge(new Config(array($resourceName => $resourceConfig)));
$writer = new IniWriter(array(
'config' => $config,
'filename' => Config::resolvePath('resources.ini'),
'filemode' => 0660
));
$writer->write();
} catch (Exception $e) {
$this->resourcesIniError = $e;
return false;
}
$this->resourcesIniError = false;
return true;
}
public function getSummary()
{
$pageTitle = '<h2>' . mt('monitoring', 'Monitoring Backend', 'setup.page.title') . '</h2>';
$backendDescription = '<p>' . sprintf(
mt(
'monitoring',
'Icinga Web 2 will retrieve information from your monitoring environment'
. ' using a backend called "%s" and the specified resource below:'
),
$this->data['backendConfig']['name']
) . '</p>';
if ($this->data['resourceConfig']['type'] === 'db') {
$resourceTitle = '<h3>' . mt('monitoring', 'Database Resource') . '</h3>';
$resourceHtml = ''
. '<table>'
. '<tbody>'
. '<tr>'
. '<td><strong>' . t('Resource Name') . '</strong></td>'
. '<td>' . $this->data['resourceConfig']['name'] . '</td>'
. '</tr>'
. '<tr>'
. '<td><strong>' . t('Database Type') . '</strong></td>'
. '<td>' . $this->data['resourceConfig']['db'] . '</td>'
. '</tr>'
. '<tr>'
. '<td><strong>' . t('Host') . '</strong></td>'
. '<td>' . $this->data['resourceConfig']['host'] . '</td>'
. '</tr>'
. '<tr>'
. '<td><strong>' . t('Port') . '</strong></td>'
. '<td>' . $this->data['resourceConfig']['port'] . '</td>'
. '</tr>'
. '<tr>'
. '<td><strong>' . t('Database Name') . '</strong></td>'
. '<td>' . $this->data['resourceConfig']['dbname'] . '</td>'
. '</tr>'
. '<tr>'
. '<td><strong>' . t('Username') . '</strong></td>'
. '<td>' . $this->data['resourceConfig']['username'] . '</td>'
. '</tr>'
. '<tr>'
. '<td><strong>' . t('Password') . '</strong></td>'
. '<td>' . str_repeat('*', strlen($this->data['resourceConfig']['password'])) . '</td>'
. '</tr>'
. '</tbody>'
. '</table>';
} else { // $this->data['resourceConfig']['type'] === 'livestatus'
$resourceTitle = '<h3>' . mt('monitoring', 'Livestatus Resource') . '</h3>';
$resourceHtml = ''
. '<table>'
. '<tbody>'
. '<tr>'
. '<td><strong>' . t('Resource Name') . '</strong></td>'
. '<td>' . $this->data['resourceConfig']['name'] . '</td>'
. '</tr>'
. '<tr>'
. '<td><strong>' . t('Socket') . '</strong></td>'
. '<td>' . $this->data['resourceConfig']['socket'] . '</td>'
. '</tr>'
. '</tbody>'
. '</table>';
}
return $pageTitle . '<div class="topic">' . $backendDescription . $resourceTitle . $resourceHtml . '</div>';
}
public function getReport()
{
$report = '';
if ($this->backendIniError === false) {
$message = mt('monitoring', 'Monitoring backend configuration has been successfully written to: %s');
$report .= '<p>' . sprintf($message, Config::resolvePath('modules/monitoring/backends.ini')) . '</p>';
} elseif ($this->backendIniError !== null) {
$message = mt(
'monitoring',
'Monitoring backend configuration could not be written to: %s; An error occured:'
);
$report .= '<p class="error">' . sprintf(
$message,
Config::resolvePath('modules/monitoring/backends.ini')
) . '</p><p>' . $this->backendIniError->getMessage() . '</p>';
}
if ($this->resourcesIniError === false) {
$message = mt('monitoring', 'Resource configuration has been successfully updated: %s');
$report .= '<p>' . sprintf($message, Config::resolvePath('resources.ini')) . '</p>';
} elseif ($this->resourcesIniError !== null) {
$message = mt('monitoring', 'Resource configuration could not be udpated: %s; An error occured:');
$report .= '<p class="error">' . sprintf($message, Config::resolvePath('resources.ini')) . '</p>'
. '<p>' . $this->resourcesIniError->getMessage() . '</p>';
}
return $report;
}
}

View File

@ -15,7 +15,7 @@ use Icinga\Data\Filterable;
use Icinga\Exception\QueryException; use Icinga\Exception\QueryException;
use Icinga\Web\Request; use Icinga\Web\Request;
use Icinga\Web\Url; use Icinga\Web\Url;
use Icinga\Module\Monitoring\Backend; use Icinga\Module\Monitoring\Backend\MonitoringBackend;
/** /**
* A read-only view of an underlying query * A read-only view of an underlying query
@ -44,8 +44,7 @@ abstract class DataView implements Browsable, Countable, Filterable, Sortable
public function __construct(ConnectionInterface $connection, array $columns = null) public function __construct(ConnectionInterface $connection, array $columns = null)
{ {
$this->connection = $connection; $this->connection = $connection;
$queryClass = $connection->getQueryClass($this->getQueryName()); $this->query = $connection->query($this->getQueryName(), $columns);
$this->query = new $queryClass($this->connection->getResource(), $columns);
$this->filter = Filter::matchAll(); $this->filter = Filter::matchAll();
$this->init(); $this->init();
} }
@ -105,7 +104,7 @@ abstract class DataView implements Browsable, Countable, Filterable, Sortable
*/ */
public static function fromRequest($request, array $columns = null) public static function fromRequest($request, array $columns = null)
{ {
$view = new static(Backend::createBackend($request->getParam('backend')), $columns); $view = new static(MonitoringBackend::instance($request->getParam('backend')), $columns);
$view->applyUrlFilter($request); $view->applyUrlFilter($request);
return $view; return $view;
@ -141,7 +140,7 @@ abstract class DataView implements Browsable, Countable, Filterable, Sortable
*/ */
public static function fromParams(array $params, array $columns = null) public static function fromParams(array $params, array $columns = null)
{ {
$view = new static(Backend::createBackend($params['backend']), $columns); $view = new static(MonitoringBackend::instance($params['backend']), $columns);
foreach ($params as $key => $value) { foreach ($params as $key => $value) {
if ($view->isValidFilterTarget($key)) { if ($view->isValidFilterTarget($key)) {

View File

@ -0,0 +1,103 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Monitoring;
use Exception;
use Icinga\Module\Setup\Step;
use Icinga\Application\Config;
use Icinga\File\Ini\IniWriter;
class InstanceStep extends Step
{
protected $data;
protected $error;
public function __construct(array $data)
{
$this->data = $data;
}
public function apply()
{
$instanceConfig = $this->data['instanceConfig'];
$instanceName = $instanceConfig['name'];
unset($instanceConfig['name']);
try {
$writer = new IniWriter(array(
'config' => new Config(array($instanceName => $instanceConfig)),
'filename' => Config::resolvePath('modules/monitoring/instances.ini')
));
$writer->write();
} catch (Exception $e) {
$this->error = $e;
return false;
}
$this->error = false;
return true;
}
public function getSummary()
{
$pageTitle = '<h2>' . mt('monitoring', 'Monitoring Instance', 'setup.page.title') . '</h2>';
if (isset($this->data['instanceConfig']['host'])) {
$pipeHtml = '<p>' . sprintf(
mt(
'monitoring',
'Icinga Web 2 will use the named pipe located on a remote machine at "%s" to send commands'
. ' to your monitoring instance by using the connection details listed below:'
),
$this->data['instanceConfig']['path']
) . '</p>';
$pipeHtml .= ''
. '<table>'
. '<tbody>'
. '<tr>'
. '<td><strong>' . mt('monitoring', 'Remote Host') . '</strong></td>'
. '<td>' . $this->data['instanceConfig']['host'] . '</td>'
. '</tr>'
. '<tr>'
. '<td><strong>' . mt('monitoring', 'Remote SSH Port') . '</strong></td>'
. '<td>' . $this->data['instanceConfig']['port'] . '</td>'
. '</tr>'
. '<tr>'
. '<td><strong>' . mt('monitoring', 'Remote SSH User') . '</strong></td>'
. '<td>' . $this->data['instanceConfig']['user'] . '</td>'
. '</tr>'
. '</tbody>'
. '</table>';
} else {
$pipeHtml = '<p>' . sprintf(
mt(
'monitoring',
'Icinga Web 2 will use the named pipe located at "%s"'
. ' to send commands to your monitoring instance.'
),
$this->data['instanceConfig']['path']
) . '</p>';
}
return $pageTitle . '<div class="topic">' . $pipeHtml . '</div>';
}
public function getReport()
{
if ($this->error === false) {
$message = mt('monitoring', 'Monitoring instance configuration has been successfully created: %s');
return '<p>' . sprintf($message, Config::resolvePath('modules/monitoring/instances.ini')) . '</p>';
} elseif ($this->error !== null) {
$message = mt(
'monitoring',
'Monitoring instance configuration could not be written to: %s; An error occured:'
);
return '<p class="error">' . sprintf($message, Config::resolvePath('modules/monitoring/instances.ini'))
. '</p><p>' . $this->error->getMessage() . '</p>';
}
}
}

View File

@ -0,0 +1,179 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Monitoring;
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\Utils\EnableModuleStep;
use Icinga\Module\Setup\Form\SummaryPage;
use Icinga\Module\Monitoring\Form\Setup\WelcomePage;
use Icinga\Module\Monitoring\Form\Setup\BackendPage;
use Icinga\Module\Monitoring\Form\Setup\InstancePage;
use Icinga\Module\Monitoring\Form\Setup\SecurityPage;
use Icinga\Module\Monitoring\Form\Setup\IdoResourcePage;
use Icinga\Module\Monitoring\Form\Setup\LivestatusResourcePage;
/**
* Monitoring Module Setup Wizard
*/
class MonitoringWizard extends Wizard implements SetupWizard
{
/**
* @see Wizard::init()
*/
public function init()
{
$this->addPage(new WelcomePage());
$this->addPage(new BackendPage());
$this->addPage(new IdoResourcePage());
$this->addPage(new LivestatusResourcePage());
$this->addPage(new InstancePage());
$this->addPage(new SecurityPage());
$this->addPage(new SummaryPage());
}
/**
* @see Wizard::setupPage()
*/
public function setupPage(Form $page, Request $request)
{
if ($page->getName() === 'setup_requirements') {
$page->setRequirements($this->getRequirements());
} elseif ($page->getName() === 'setup_summary') {
$page->setSummary($this->getSetup()->getSummary());
$page->setSubjectTitle(mt('monitoring', 'the monitoring module', 'setup.summary.subject'));
} elseif (
$this->getDirection() === static::FORWARD
&& ($page->getName() === 'setup_monitoring_ido' || $page->getName() === 'setup_monitoring_livestatus')
) {
if ((($dbResourceData = $this->getPageData('setup_db_resource')) !== null
&& $dbResourceData['name'] === $request->getPost('name'))
|| (($ldapResourceData = $this->getPageData('setup_ldap_resource')) !== null
&& $ldapResourceData['name'] === $request->getPost('name'))
) {
$page->addError(mt('monitoring', 'The given resource name is already in use.'));
}
}
}
/**
* @see Wizard::getNewPage()
*/
protected function getNewPage($requestedPage, Form $originPage)
{
$skip = false;
$newPage = parent::getNewPage($requestedPage, $originPage);
if ($newPage->getName() === 'setup_monitoring_ido') {
$backendData = $this->getPageData('setup_monitoring_backend');
$skip = $backendData['type'] !== 'ido';
} elseif ($newPage->getName() === 'setup_monitoring_livestatus') {
$backendData = $this->getPageData('setup_monitoring_backend');
$skip = $backendData['type'] !== 'livestatus';
}
if ($skip) {
if ($this->hasPageData($newPage->getName())) {
$pageData = & $this->getPageData();
unset($pageData[$newPage->getName()]);
}
$pages = $this->getPages();
if ($this->getDirection() === static::FORWARD) {
$nextPage = $pages[array_search($newPage, $pages, true) + 1];
$newPage = $this->getNewPage($nextPage->getName(), $newPage);
} else { // $this->getDirection() === static::BACKWARD
$previousPage = $pages[array_search($newPage, $pages, true) - 1];
$newPage = $this->getNewPage($previousPage->getName(), $newPage);
}
}
return $newPage;
}
/**
* @see Wizard::addButtons()
*/
protected function addButtons(Form $page)
{
parent::addButtons($page);
$pages = $this->getPages();
$index = array_search($page, $pages, true);
if ($index === 0) {
// Used t() here as "Start" is too generic and already translated in the icinga domain
$page->getElement(static::BTN_NEXT)->setLabel(t('Start', 'setup.welcome.btn.next'));
} elseif ($index === count($pages) - 1) {
$page->getElement(static::BTN_NEXT)->setLabel(
mt('monitoring', 'Setup the monitoring module for Icinga Web 2', 'setup.summary.btn.finish')
);
}
}
/**
* @see SetupWizard::getSetup()
*/
public function getSetup()
{
$pageData = $this->getPageData();
$setup = new Setup();
$setup->addStep(new MakeDirStep(array($this->getConfigDir() . '/modules/monitoring'), 0775));
$setup->addStep(
new BackendStep(array(
'backendConfig' => $pageData['setup_monitoring_backend'],
'resourceConfig' => isset($pageData['setup_monitoring_ido'])
? array_diff_key($pageData['setup_monitoring_ido'], array('skip_validation' => null))
: array_diff_key($pageData['setup_monitoring_livestatus'], array('skip_validation' => null))
))
);
$setup->addStep(
new InstanceStep(array(
'instanceConfig' => $pageData['setup_monitoring_instance']
))
);
$setup->addStep(
new SecurityStep(array(
'securityConfig' => $pageData['setup_monitoring_security']
))
);
$setup->addStep(new EnableModuleStep('monitoring'));
return $setup;
}
/**
* @see SetupWizard::getRequirements()
*/
public function getRequirements()
{
return new Requirements();
}
/**
* Return the configuration directory of Icinga Web 2
*
* @return string
*/
protected function getConfigDir()
{
if (array_key_exists('ICINGAWEB_CONFIGDIR', $_SERVER)) {
$configDir = $_SERVER['ICINGAWEB_CONFIGDIR'];
} else {
$configDir = '/etc/icingaweb';
}
$canonical = realpath($configDir);
return $canonical ? $canonical : $configDir;
}
}

View File

@ -5,7 +5,7 @@
namespace Icinga\Module\Monitoring\Object; namespace Icinga\Module\Monitoring\Object;
use InvalidArgumentException; use InvalidArgumentException;
use Icinga\Module\Monitoring\Backend; use Icinga\Module\Monitoring\Backend\MonitoringBackend;
/** /**
* A Icinga host * A Icinga host
@ -63,10 +63,10 @@ class Host extends MonitoredObject
/** /**
* Create a new host * Create a new host
* *
* @param Backend $backend Backend to fetch host information from * @param MonitoringBackend $backend Backend to fetch host information from
* @param string $host Host name * @param string $host Host name
*/ */
public function __construct(Backend $backend, $host) public function __construct(MonitoringBackend $backend, $host)
{ {
parent::__construct($backend); parent::__construct($backend);
$this->host = $host; $this->host = $host;

View File

@ -7,7 +7,7 @@ namespace Icinga\Module\Monitoring\Object;
use InvalidArgumentException; use InvalidArgumentException;
use Icinga\Application\Config; use Icinga\Application\Config;
use Icinga\Exception\InvalidPropertyException; use Icinga\Exception\InvalidPropertyException;
use Icinga\Module\Monitoring\Backend; use Icinga\Module\Monitoring\Backend\MonitoringBackend;
use Icinga\Web\UrlParams; use Icinga\Web\UrlParams;
/** /**
@ -28,7 +28,7 @@ abstract class MonitoredObject
/** /**
* Backend to fetch object information from * Backend to fetch object information from
* *
* @var Backend * @var MonitoringBackend
*/ */
protected $backend; protected $backend;
@ -119,9 +119,9 @@ abstract class MonitoredObject
/** /**
* Create a monitored object, i.e. host or service * Create a monitored object, i.e. host or service
* *
* @param Backend $backend Backend to fetch object information from * @param MonitoringBackend $backend Backend to fetch object information from
*/ */
public function __construct(Backend $backend) public function __construct(MonitoringBackend $backend)
{ {
$this->backend = $backend; $this->backend = $backend;
} }
@ -480,9 +480,9 @@ abstract class MonitoredObject
public static function fromParams(UrlParams $params) public static function fromParams(UrlParams $params)
{ {
if ($params->has('service') && $params->has('host')) { if ($params->has('service') && $params->has('host')) {
return new Service(Backend::createBackend(), $params->get('host'), $params->get('service')); return new Service(MonitoringBackend::instance(), $params->get('host'), $params->get('service'));
} elseif ($params->has('host')) { } elseif ($params->has('host')) {
return new Host(Backend::createBackend(), $params->get('host')); return new Host(MonitoringBackend::instance(), $params->get('host'));
} }
return null; return null;
} }

View File

@ -5,7 +5,7 @@ namespace Icinga\Module\Monitoring\Object;
use ArrayIterator; use ArrayIterator;
use Countable; use Countable;
use IteratorAggregate; use IteratorAggregate;
use Icinga\Module\Monitoring\Backend; use Icinga\Module\Monitoring\Backend\MonitoringBackend;
abstract class ObjectList implements Countable, IteratorAggregate abstract class ObjectList implements Countable, IteratorAggregate
{ {
@ -21,7 +21,7 @@ abstract class ObjectList implements Countable, IteratorAggregate
protected $count; protected $count;
public function __construct(Backend $backend) public function __construct(MonitoringBackend $backend)
{ {
$this->backend = $backend; $this->backend = $backend;
} }

View File

@ -5,7 +5,7 @@
namespace Icinga\Module\Monitoring\Object; namespace Icinga\Module\Monitoring\Object;
use InvalidArgumentException; use InvalidArgumentException;
use Icinga\Module\Monitoring\Backend; use Icinga\Module\Monitoring\Backend\MonitoringBackend;
/** /**
* A Icinga service * A Icinga service
@ -68,11 +68,11 @@ class Service extends MonitoredObject
/** /**
* Create a new service * Create a new service
* *
* @param Backend $backend Backend to fetch service information from * @param MonitoringBackend $backend Backend to fetch service information from
* @param string $host Host name the service is running on * @param string $host Host name the service is running on
* @param string $service Service name * @param string $service Service name
*/ */
public function __construct(Backend $backend, $host, $service) public function __construct(MonitoringBackend $backend, $host, $service)
{ {
parent::__construct($backend); parent::__construct($backend);
$this->host = new Host($backend, $host); $this->host = new Host($backend, $host);

View File

@ -0,0 +1,81 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Monitoring;
use Exception;
use Icinga\Module\Setup\Step;
use Icinga\Application\Config;
use Icinga\File\Ini\IniWriter;
class SecurityStep extends Step
{
protected $data;
protected $error;
public function __construct(array $data)
{
$this->data = $data;
}
public function apply()
{
$config = array();
$config['security'] = $this->data['securityConfig'];
try {
$writer = new IniWriter(array(
'config' => new Config($config),
'filename' => Config::resolvePath('modules/monitoring/config.ini')
));
$writer->write();
} catch (Exception $e) {
$this->error = $e;
return false;
}
$this->error = false;
return true;
}
public function getSummary()
{
$pageTitle = '<h2>' . mt('monitoring', 'Monitoring Security', 'setup.page.title') . '</h2>';
$pageDescription = '<p>' . mt(
'monitoring',
'Icinga Web 2 will protect your monitoring environment against'
. ' prying eyes using the configuration specified below:'
) . '</p>';
$pageHtml = ''
. '<table>'
. '<tbody>'
. '<tr>'
. '<td><strong>' . mt('monitoring', 'Protected Custom Variables') . '</strong></td>'
. '<td>' . ($this->data['securityConfig']['protected_customvars'] ? (
$this->data['securityConfig']['protected_customvars']
) : mt('monitoring', 'None', 'monitoring.protected_customvars')) . '</td>'
. '</tr>'
. '</tbody>'
. '</table>';
return $pageTitle . '<div class="topic">' . $pageDescription . $pageHtml . '</div>';
}
public function getReport()
{
if ($this->error === false) {
$message = mt('monitoring', 'Monitoring security configuration has been successfully created: %s');
return '<p>' . sprintf($message, Config::resolvePath('modules/monitoring/config.ini')) . '</p>';
} elseif ($this->error !== null) {
$message = mt(
'monitoring',
'Monitoring security configuration could not be written to: %s; An error occured:'
);
return '<p class="error">' . sprintf($message, Config::resolvePath('modules/monitoring/config.ini'))
. '</p><p>' . $this->error->getMessage() . '</p>';
}
}
}

View File

@ -288,7 +288,6 @@ class TimeLine implements IteratorAggregate
if ($this->session !== null) { if ($this->session !== null) {
$this->session->calculationBase = $new; $this->session->calculationBase = $new;
$this->session->write();
} }
} else { } else {
$this->calculationBase = $calculationBase; $this->calculationBase = $calculationBase;

View File

@ -0,0 +1,63 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Setup\Clicommands;
use Icinga\Cli\Command;
class ConfigCommand extends Command
{
/**
* Create the configuration directory
*
* This command creates the configuration directory for Icinga Web 2. The `group' argument
* is mandatory and should be the groupname of the user your web server is running as.
*
* USAGE:
*
* icingacli setup config createDirectory <group> [options]
*
* OPTIONS:
*
* --mode The access mode to use. Default is: 2770
* --path The path to the configuration directory. If omitted the default is used.
*
* EXAMPLES:
*
* icingacli setup config createDirectory apache
* icingacli setup config createDirectory apache --mode 2775
* icingacli setup config createDirectory apache --path /some/path
*/
public function createDirectoryAction()
{
$group = $this->params->getStandalone();
if ($group === null) {
$this->fail($this->translate('The `group\' argument is mandatory.'));
return false;
}
$path = $this->params->get('path', $this->app->getConfigDir());
if (file_exists($path)) {
printf($this->translate("Configuration directory already exists at: %s\n"), $path);
return true;
}
$mode = octdec($this->params->get('mode', '2770'));
if (false === mkdir($path)) {
$this->fail(sprintf($this->translate('Unable to create path: %s'), $path));
return false;
}
$old = umask(0); // Prevent $mode from being mangled by the system's umask ($old)
chmod($path, $mode);
umask($old);
if (chgrp($path, $group) === false) {
$this->fail(sprintf($this->translate('Unable to change the group of "%s" to "%s".'), $path, $group));
return false;
}
printf($this->translate("Successfully created configuration directory at: %s\n"), $path);
}
}

View File

@ -0,0 +1,68 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Setup\Clicommands;
use Icinga\Cli\Command;
/**
* Maintain the setup wizard's authentication
*
* The token command allows you to display the current setup token or to create a new one.
*
* Usage: icingacli setup token <action>
*/
class TokenCommand extends Command
{
/**
* Display the current setup token
*
* Shows you the current setup token used to authenticate when setting up Icinga Web 2 using the web-based wizard.
*
* USAGE:
*
* icingacli setup token show
*/
public function showAction()
{
$token = file_get_contents($this->app->getConfigDir() . '/setup.token');
if (! $token) {
$this->fail(
$this->translate('Nothing to show. Please create a new setup token using the generateToken action.')
);
}
printf($this->translate("The current setup token is: %s\n"), $token);
}
/**
* Create a new setup token
*
* Re-generates the setup token used to authenticate when setting up Icinga Web 2 using the web-based wizard.
*
* USAGE:
*
* icingacli setup token create
*/
public function createAction()
{
if (function_exists('openssl_random_pseudo_bytes')) {
$token = bin2hex(openssl_random_pseudo_bytes(8));
} else {
$token = substr(md5(mt_rand()), 16);
}
$filepath = $this->app->getConfigDir() . '/setup.token';
if (false === file_put_contents($filepath, $token)) {
$this->fail(sprintf($this->translate('Cannot write setup token "%s" to disk.'), $filepath));
}
if (false === chmod($filepath, 0660)) {
$this->fail(sprintf($this->translate('Cannot change access mode of "%s" to %o.'), $filepath, 0660));
}
printf($this->translate("The newly generated setup token is: %s\n"), $token);
}
}

View File

@ -0,0 +1,43 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
use Icinga\Web\Controller\ModuleActionController;
use Icinga\Module\Setup\WebWizard;
class Setup_IndexController extends ModuleActionController
{
/**
* Whether the controller requires the user to be authenticated
*
* FALSE as the wizard uses token authentication
*
* @var bool
*/
protected $requiresAuthentication = false;
/**
* Show the web wizard and run the configuration once finished
*/
public function indexAction()
{
$wizard = new WebWizard();
if ($wizard->isFinished()) {
$setup = $wizard->getSetup();
$success = $setup->run();
if ($success) {
$wizard->clearSession();
} else {
$wizard->setIsFinished(false);
}
$this->view->success = $success;
$this->view->report = $setup->getReport();
} else {
$wizard->handleRequest();
}
$this->view->wizard = $wizard;
}
}

View File

@ -0,0 +1,283 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Setup\Form;
use Exception;
use LogicException;
use Icinga\Application\Config;
use Icinga\Web\Form;
use Icinga\Data\ResourceFactory;
use Icinga\Web\Form\Element\Note;
use Icinga\Authentication\Backend\DbUserBackend;
use Icinga\Authentication\Backend\LdapUserBackend;
/**
* Wizard page to define the initial administrative account
*/
class AdminAccountPage extends Form
{
/**
* The resource configuration to use
*
* @var array
*/
protected $resourceConfig;
/**
* The backend configuration to use
*
* @var array
*/
protected $backendConfig;
/**
* Initialize this page
*/
public function init()
{
$this->setName('setup_admin_account');
$this->setViewScript('form/setup-admin-account.phtml');
}
/**
* Set the resource configuration to use
*
* @param array $config
*
* @return self
*/
public function setResourceConfig(array $config)
{
$this->resourceConfig = $config;
return $this;
}
/**
* Set the backend configuration to use
*
* @param array $config
*
* @return self
*/
public function setBackendConfig(array $config)
{
$this->backendConfig = $config;
return $this;
}
/**
* @see Form::createElements()
*/
public function createElements(array $formData)
{
$choices = array();
if ($this->backendConfig['backend'] !== 'db') {
$choices['by_name'] = mt('setup', 'By Name', 'setup.admin');
$this->addElement(
'text',
'by_name',
array(
'required' => isset($formData['user_type']) && $formData['user_type'] === 'by_name',
'value' => $this->getUsername(),
'label' => mt('setup', 'Username'),
'description' => mt(
'setup',
'Define the initial administrative account by providing a username that reflects'
. ' a user created later or one that is authenticated using external mechanisms'
)
)
);
}
if ($this->backendConfig['backend'] === 'db' || $this->backendConfig['backend'] === 'ldap') {
$users = $this->fetchUsers();
if (false === empty($users)) {
$choices['existing_user'] = mt('setup', 'Existing User');
$this->addElement(
'select',
'existing_user',
array(
'required' => isset($formData['user_type']) && $formData['user_type'] === 'existing_user',
'label' => mt('setup', 'Username'),
'description' => sprintf(
mt(
'setup',
'Choose a user reported by the %s backend as the initial administrative account',
'setup.admin'
),
$this->backendConfig['backend'] === 'db'
? mt('setup', 'database', 'setup.admin.authbackend')
: 'LDAP'
),
'multiOptions' => array_combine($users, $users)
)
);
}
}
if ($this->backendConfig['backend'] === 'db') {
$choices['new_user'] = mt('setup', 'New User');
$required = isset($formData['user_type']) && $formData['user_type'] === 'new_user';
$this->addElement(
'text',
'new_user',
array(
'required' => $required,
'label' => mt('setup', 'Username'),
'description' => mt(
'setup',
'Enter the username to be used when creating an initial administrative account'
)
)
);
$this->addElement(
'password',
'new_user_password',
array(
'required' => $required,
'label' => mt('setup', 'Password'),
'description' => mt('setup', 'Enter the password to assign to the newly created account')
)
);
$this->addElement(
'password',
'new_user_2ndpass',
array(
'required' => $required,
'label' => mt('setup', 'Repeat password'),
'description' => mt('setup', 'Please repeat the password given above to avoid typing errors'),
'validators' => array(
array('identical', false, array('new_user_password'))
)
)
);
}
if (count($choices) > 1) {
$this->addElement(
'radio',
'user_type',
array(
'required' => true,
'multiOptions' => $choices
)
);
} else {
$this->addElement(
'hidden',
'user_type',
array(
'required' => true,
'value' => key($choices)
)
);
}
$this->addElement(
new Note(
'title',
array(
'value' => mt('setup', 'Administration', 'setup.page.title'),
'decorators' => array(
'ViewHelper',
array('HtmlTag', array('tag' => 'h2'))
)
)
)
);
$this->addElement(
new Note(
'description',
array(
'value' => tp(
'Now it\'s time to configure your first administrative account for Icinga Web 2.'
. ' Please follow the instructions below:',
'Now it\'s time to configure your first administrative account for Icinga Web 2.'
. ' Below are several options you can choose from. Select one and follow its instructions:',
count($choices)
)
)
)
);
}
/**
* Validate the given request data and ensure that any new user does not already exist
*
* @param array $data The request data to validate
*
* @return bool
*/
public function isValid($data)
{
if (false === parent::isValid($data)) {
return false;
}
if ($data['user_type'] === 'new_user' && array_search($data['new_user'], $this->fetchUsers()) !== false) {
$this->getElement('new_user')->addError(mt('setup', 'Username already exists.'));
return false;
}
return true;
}
/**
* Return the name of the externally authenticated user
*
* @return string
*/
protected function getUsername()
{
if (false === isset($_SERVER['REMOTE_USER'])) {
return '';
}
$name = $_SERVER['REMOTE_USER'];
if (isset($this->backendConfig['strip_username_regexp']) && $this->backendConfig['strip_username_regexp']) {
// No need to silence or log anything here because the pattern has
// already been successfully compiled during backend configuration
$name = preg_replace($this->backendConfig['strip_username_regexp'], '', $name);
}
return $name;
}
/**
* Return the names of all users this backend currently provides
*
* @return array
*
* @throws LogicException In case the backend to fetch users from is not supported
*/
protected function fetchUsers()
{
if ($this->backendConfig['backend'] === 'db') {
$backend = new DbUserBackend(ResourceFactory::createResource(new Config($this->resourceConfig)));
} elseif ($this->backendConfig['backend'] === 'ldap') {
$backend = new LdapUserBackend(
ResourceFactory::createResource(new Config($this->resourceConfig)),
$this->backendConfig['user_class'],
$this->backendConfig['user_name_attribute'],
$this->backendConfig['base_dn']
);
} else {
throw new LogicException(
sprintf(
'Tried to fetch users from an unsupported authentication backend: %s',
$this->backendConfig['backend']
)
);
}
try {
return $backend->listUsers();
} catch (Exception $e) {
// No need to handle anything special here. Error means no users found.
return array();
}
}
}

View File

@ -0,0 +1,161 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Setup\Form;
use Icinga\Application\Config;
use Icinga\Web\Form;
use Icinga\Web\Form\Element\Note;
use Icinga\Form\Config\Authentication\DbBackendForm;
use Icinga\Form\Config\Authentication\LdapBackendForm;
use Icinga\Form\Config\Authentication\AutologinBackendForm;
/**
* Wizard page to define authentication backend specific details
*/
class AuthBackendPage extends Form
{
/**
* The resource configuration to use
*
* @var array
*/
protected $config;
/**
* Initialize this page
*/
public function init()
{
$this->setName('setup_authentication_backend');
}
/**
* Set the resource configuration to use
*
* @param array $config
*
* @return self
*/
public function setResourceConfig(array $config)
{
$this->config = $config;
return $this;
}
/**
* Return the resource configuration as Config object
*
* @return Config
*/
public function getResourceConfig()
{
return new Config($this->config);
}
/**
* @see Form::createElements()
*/
public function createElements(array $formData)
{
$this->addElement(
new Note(
'title',
array(
'value' => mt('setup', 'Authentication Backend', 'setup.page.title'),
'decorators' => array(
'ViewHelper',
array('HtmlTag', array('tag' => 'h2'))
)
)
)
);
if ($this->config['type'] === 'db') {
$note = mt(
'setup',
'As you\'ve chosen to use a database for authentication all you need '
. 'to do now is defining a name for your first authentication backend.'
);
} elseif ($this->config['type'] === 'ldap') {
$note = mt(
'setup',
'Before you are able to authenticate using the LDAP connection defined earlier you need to'
. ' provide some more information so that Icinga Web 2 is able to locate account details.'
);
} else { // if ($this->config['type'] === 'autologin'
$note = mt(
'setup',
'You\'ve chosen to authenticate using a web server\'s mechanism so it may be necessary'
. ' to adjust usernames before any permissions, restrictions, etc. are being applied.'
);
}
$this->addElement(
new Note(
'description',
array('value' => $note)
)
);
if (isset($formData['skip_validation']) && $formData['skip_validation']) {
$this->addSkipValidationCheckbox();
}
if ($this->config['type'] === 'db') {
$backendForm = new DbBackendForm();
$backendForm->createElements($formData)->removeElement('resource');
} elseif ($this->config['type'] === 'ldap') {
$backendForm = new LdapBackendForm();
$backendForm->createElements($formData)->removeElement('resource');
} else { // $this->config['type'] === 'autologin'
$backendForm = new AutologinBackendForm();
$backendForm->createElements($formData);
}
$this->addElements($backendForm->getElements());
$this->getElement('name')->setValue('icingaweb');
}
/**
* Validate the given form data and check whether it's possible to authenticate using the configured backend
*
* @param array $data The data to validate
*
* @return bool
*/
public function isValid($data)
{
if (false === parent::isValid($data)) {
return false;
}
if (false === isset($data['skip_validation']) || $data['skip_validation'] == 0) {
if ($this->config['type'] === 'ldap' && false === LdapBackendForm::isValidAuthenticationBackend($this)) {
$this->addSkipValidationCheckbox();
return false;
}
}
return true;
}
/**
* Add a checkbox to this form by which the user can skip the authentication validation
*/
protected function addSkipValidationCheckbox()
{
$this->addElement(
'checkbox',
'skip_validation',
array(
'order' => 2,
'ignore' => true,
'required' => true,
'label' => mt('setup', 'Skip Validation'),
'description' => mt('setup', 'Check this to not to validate authentication using this backend')
)
);
}
}

View File

@ -0,0 +1,74 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Setup\Form;
use Icinga\Web\Form;
use Icinga\Application\Platform;
use Icinga\Web\Form\Element\Note;
/**
* Wizard page to choose an authentication backend
*/
class AuthenticationPage extends Form
{
/**
* Initialize this page
*/
public function init()
{
$this->setName('setup_authentication_type');
}
/**
* @see Form::createElements()
*/
public function createElements(array $formData)
{
$this->addElement(
new Note(
'title',
array(
'value' => mt('setup', 'Authentication', 'setup.page.title'),
'decorators' => array(
'ViewHelper',
array('HtmlTag', array('tag' => 'h2'))
)
)
)
);
$this->addElement(
new Note(
'description',
array(
'value' => mt(
'setup',
'Please choose how you want to authenticate when accessing Icinga Web 2.'
. ' Configuring backend specific details follows in a later step.'
)
)
)
);
$backendTypes = array();
if (Platform::extensionLoaded('mysql') || Platform::extensionLoaded('pgsql')) {
$backendTypes['db'] = t('Database');
}
if (Platform::extensionLoaded('ldap')) {
$backendTypes['ldap'] = 'LDAP';
}
$backendTypes['autologin'] = t('Autologin');
$this->addElement(
'select',
'type',
array(
'required' => true,
'label' => mt('setup', 'Authentication Type'),
'description' => mt('setup', 'The type of authentication to use when accessing Icinga Web 2'),
'multiOptions' => $backendTypes
)
);
}
}

View File

@ -0,0 +1,229 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Setup\Form;
use PDOException;
use Icinga\Web\Form;
use Icinga\Web\Form\Element\Note;
use Icinga\Module\Setup\Utils\DbTool;
/**
* Wizard page to define a database user that is able to create databases and tables
*/
class DatabaseCreationPage extends Form
{
/**
* The resource configuration to use
*
* @var array
*/
protected $config;
/**
* The required privileges to setup the database
*
* @var array
*/
protected $databaseSetupPrivileges;
/**
* The required privileges to operate the database
*
* @var array
*/
protected $databaseUsagePrivileges;
/**
* Initialize this page
*/
public function init()
{
$this->setName('setup_database_creation');
}
/**
* Set the resource configuration to use
*
* @param array $config
*
* @return self
*/
public function setResourceConfig(array $config)
{
$this->config = $config;
return $this;
}
/**
* Set the required privileges to setup the database
*
* @param array $privileges The privileges
*
* @return self
*/
public function setDatabaseSetupPrivileges(array $privileges)
{
$this->databaseSetupPrivileges = $privileges;
return $this;
}
/**
* Set the required privileges to operate the database
*
* @param array $privileges The privileges
*
* @return self
*/
public function setDatabaseUsagePrivileges(array $privileges)
{
$this->databaseUsagePrivileges = $privileges;
return $this;
}
/**
* @see Form::createElements()
*/
public function createElements(array $formData)
{
$this->addElement(
new Note(
'title',
array(
'value' => mt('setup', 'Database Setup', 'setup.page.title'),
'decorators' => array(
'ViewHelper',
array('HtmlTag', array('tag' => 'h2'))
)
)
)
);
$this->addElement(
new Note(
'description',
array(
'value' => mt(
'setup',
'It seems that either the database you defined earlier does not yet exist and cannot be created'
. ' using the provided access credentials or the database does not have the required schema to '
. 'be operated by Icinga Web 2. Please provide appropriate access credentials to solve this.'
)
)
)
);
$skipValidation = isset($formData['skip_validation']) && $formData['skip_validation'];
$this->addElement(
'text',
'username',
array(
'required' => false === $skipValidation,
'label' => mt('setup', 'Username'),
'description' => mt('setup', 'A user which is able to create databases and/or touch the database schema')
)
);
$this->addElement(
'password',
'password',
array(
'label' => mt('setup', 'Password'),
'description' => mt('setup', 'The password for the database user defined above')
)
);
if ($skipValidation) {
$this->addSkipValidationCheckbox();
} else {
$this->addElement(
'hidden',
'skip_validation',
array(
'required' => true,
'value' => 0
)
);
}
}
/**
* Validate the given form data and check whether the defined user has sufficient access rights
*
* @param array $data The data to validate
*
* @return bool
*/
public function isValid($data)
{
if (false === parent::isValid($data)) {
return false;
}
if (isset($data['skip_validation']) && $data['skip_validation']) {
return true;
}
$config = $this->config;
$config['username'] = $this->getValue('username');
$config['password'] = $this->getValue('password');
$db = new DbTool($config);
try {
$db->connectToDb(); // Are we able to login on the database?
} catch (PDOException $_) {
try {
$db->connectToHost(); // Are we able to login on the server?
} catch (PDOException $e) {
// We are NOT able to login on the server..
$this->addError($e->getMessage());
$this->addSkipValidationCheckbox();
return false;
}
}
// In case we are connected the credentials filled into this
// form need to be granted to create databases, users...
if (false === $db->checkPrivileges($this->databaseSetupPrivileges)) {
$this->addError(
mt('setup', 'The provided credentials cannot be used to create the database and/or the user.')
);
$this->addSkipValidationCheckbox();
return false;
}
// ...and to grant all required usage privileges to others
if (false === $db->isGrantable($this->databaseUsagePrivileges)) {
$this->addError(sprintf(
mt(
'setup',
'The provided credentials cannot be used to grant all required privileges to the login "%s".'
),
$this->config['username']
));
$this->addSkipValidationCheckbox();
return false;
}
return true;
}
/**
* Add a checkbox to the form by which the user can skip the login and privilege validation
*/
protected function addSkipValidationCheckbox()
{
$this->addElement(
'checkbox',
'skip_validation',
array(
'order' => 2,
'required' => true,
'label' => mt('setup', 'Skip Validation'),
'description' => mt(
'setup',
'Check this to not to validate the ability to login and required privileges'
)
)
);
}
}

View File

@ -0,0 +1,132 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Setup\Form;
use PDOException;
use Icinga\Web\Form;
use Icinga\Web\Form\Element\Note;
use Icinga\Form\Config\Resource\DbResourceForm;
use Icinga\Module\Setup\Utils\DbTool;
/**
* Wizard page to define connection details for a database resource
*/
class DbResourcePage extends Form
{
/**
* Initialize this page
*/
public function init()
{
$this->setName('setup_db_resource');
}
/**
* @see Form::createElements()
*/
public function createElements(array $formData)
{
$this->addElement(
'hidden',
'type',
array(
'required' => true,
'value' => 'db'
)
);
$this->addElement(
new Note(
'title',
array(
'value' => mt('setup', 'Database Resource', 'setup.page.title'),
'decorators' => array(
'ViewHelper',
array('HtmlTag', array('tag' => 'h2'))
)
)
)
);
$this->addElement(
new Note(
'description',
array(
'value' => mt(
'setup',
'Now please configure your database resource. Note that the database itself does not need to'
. ' exist at this time as it is going to be created once the wizard is about to be finished.'
)
)
)
);
if (isset($formData['skip_validation']) && $formData['skip_validation']) {
$this->addSkipValidationCheckbox();
} else {
$this->addElement(
'hidden',
'skip_validation',
array(
'required' => true,
'value' => 0
)
);
}
$resourceForm = new DbResourceForm();
$this->addElements($resourceForm->createElements($formData)->getElements());
$this->getElement('name')->setValue('icingaweb_db');
$this->addElement(
'hidden',
'prefix',
array(
'required' => true,
'value' => 'icingaweb_'
)
);
}
/**
* Validate the given form data and check whether it's possible to connect to the database server
*
* @param array $data The data to validate
*
* @return bool
*/
public function isValid($data)
{
if (false === parent::isValid($data)) {
return false;
}
if (false === isset($data['skip_validation']) || $data['skip_validation'] == 0) {
try {
$db = new DbTool($this->getValues());
$db->checkConnectivity();
} catch (PDOException $e) {
$this->addError($e->getMessage());
$this->addSkipValidationCheckbox();
return false;
}
}
return true;
}
/**
* Add a checkbox to the form by which the user can skip the connection validation
*/
protected function addSkipValidationCheckbox()
{
$this->addElement(
'checkbox',
'skip_validation',
array(
'required' => true,
'label' => mt('setup', 'Skip Validation'),
'description' => mt('setup', 'Check this to not to validate connectivity with the given database server')
)
);
}
}

View File

@ -0,0 +1,56 @@
<?php
// {{{ICINGA_LICENSE_HEADER}}}
// {{{ICINGA_LICENSE_HEADER}}}
namespace Icinga\Module\Setup\Form;
use Icinga\Web\Form;
use Icinga\Web\Form\Element\Note;
use Icinga\Form\Config\General\LoggingConfigForm;
/**
* Wizard page to define the application and logging configuration
*/
class GeneralConfigPage extends Form
{
/**
* Initialize this page
*/
public function init()
{
$this->setName('setup_general_config');
}
/**
* @see Form::createElements()
*/
public function createElements(array $formData)
{
$this->addElement(
new Note(
'title',
array(
'value' => mt('setup', 'Application Configuration', 'setup.page.title'),
'decorators' => array(
'ViewHelper',
array('HtmlTag', array('tag' => 'h2'))
)
)
)
);
$this->addElement(
new Note(
'description',
array(
'value' => mt(
'setup',
'Now please adjust all application and logging related configuration options to fit your needs.'
)
)
)
);
$loggingForm = new LoggingConfigForm();
$this->addElements($loggingForm->createElements($formData)->getElements());
}
}

Some files were not shown because too many files have changed in this diff Show More