Merge branch 'feature/more-intuitive-module-installation-8191'

resolves #8191
This commit is contained in:
Johannes Meyer 2015-01-22 13:34:51 +01:00
commit 13c2a3a967
15 changed files with 434 additions and 354 deletions

View File

@ -512,7 +512,7 @@ class Form extends Zend_Form
$el = parent::createElement($type, $name, $options);
if (($description = $el->getDescription()) !== null && ($label = $el->getDecorator('label')) !== null) {
if (($description = $el->getDescription()) !== null && ($label = $el->getDecorator('label')) !== false) {
$label->setOptions(array(
'title' => $description,
'class' => 'has-feedback'

View File

@ -39,6 +39,13 @@ class Wizard
*/
const BTN_PREV = 'btn_prev';
/**
* This wizard's parent
*
* @var Wizard
*/
protected $parent;
/**
* The name of the wizard's current page
*
@ -71,19 +78,55 @@ class Wizard
}
/**
* Return this wizard's parent or null in case it has none
*
* @return Wizard|null
*/
public function getParent()
{
return $this->parent;
}
/**
* Set this wizard's parent
*
* @param Wizard $wizard The parent wizard
*
* @return self
*/
public function setParent(Wizard $wizard)
{
$this->parent = $wizard;
return $this;
}
/**
* Return the pages being part of this wizard
*
* In case this is a nested wizard a flattened array of all contained pages is returned.
*
* @return array
*/
public function getPages()
{
return $this->pages;
$pages = array();
foreach ($this->pages as $page) {
if ($page instanceof self) {
$pages = array_merge($pages, $page->getPages());
} else {
$pages[] = $page;
}
}
return $pages;
}
/**
* Return the page with the given name
*
* Note that it's also possible to retrieve a nested wizard's page by using this method.
*
* @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
@ -98,22 +141,31 @@ class Wizard
}
/**
* Add a new page to this wizard
* Add a new page or wizard to this wizard
*
* @param Form $page The page to add to the wizard
* @param Form|Wizard $page The page or wizard to add to the wizard
*
* @return self
*/
public function addPage(Form $page)
public function addPage($page)
{
if (! $page instanceof Form && ! $page instanceof self) {
throw InvalidArgumentException(
'The $page argument must be an instance of Icinga\Web\Form '
. 'or Icinga\Web\Wizard but is of type: ' . get_class($page)
);
} elseif ($page instanceof self) {
$page->setParent($this);
}
$this->pages[] = $page;
return $this;
}
/**
* Add multiple pages to this wizard
* Add multiple pages or wizards to this wizard
*
* @param array $pages The pages to add to the wizard
* @param array $pages The pages or wizards to add to the wizard
*
* @return self
*/
@ -148,6 +200,10 @@ class Wizard
*/
public function getCurrentPage()
{
if ($this->parent) {
return $this->parent->getCurrentPage();
}
if ($this->currentPage === null) {
$this->assertHasPages();
$pages = $this->getPages();
@ -202,6 +258,10 @@ class Wizard
{
$page = $this->getCurrentPage();
if (($wizard = $this->findWizard($page)) !== null) {
return $wizard->handleRequest($request);
}
if ($request === null) {
$request = $page->getRequest();
}
@ -238,6 +298,39 @@ class Wizard
return $request;
}
/**
* Return the wizard for the given page or null if its not part of a wizard
*
* @param Form $page The page to return its wizard for
*
* @return Wizard|null
*/
protected function findWizard(Form $page)
{
foreach ($this->getWizards() as $wizard) {
if ($wizard->getPage($page->getName()) === $page) {
return $wizard;
}
}
}
/**
* Return this wizard's child wizards
*
* @return array
*/
protected function getWizards()
{
$wizards = array();
foreach ($this->pages as $pageOrWizard) {
if ($pageOrWizard instanceof self) {
$wizards[] = $pageOrWizard;
}
}
return $wizards;
}
/**
* Return the request data based on given form's request method
*
@ -264,6 +357,10 @@ class Wizard
*/
protected function getRequestedPage(array $requestData)
{
if ($this->parent) {
return $this->parent->getRequestedPage($requestData);
}
if (isset($requestData[static::BTN_NEXT])) {
return $requestData[static::BTN_NEXT];
} elseif (isset($requestData[static::BTN_PREV])) {
@ -280,6 +377,10 @@ class Wizard
*/
protected function getDirection(Request $request = null)
{
if ($this->parent) {
return $this->parent->getDirection($request);
}
$currentPage = $this->getCurrentPage();
if ($request === null) {
@ -299,7 +400,7 @@ class Wizard
/**
* 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.
* Permission is checked by verifying that the requested page or its 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.
*
@ -312,11 +413,15 @@ class Wizard
*/
protected function getNewPage($requestedPage, Form $originPage)
{
if ($this->parent) {
return $this->parent->getNewPage($requestedPage, $originPage);
}
if (($page = $this->getPage($requestedPage)) !== null) {
$permitted = true;
$pages = $this->getPages();
if (($index = array_search($page, $pages, true)) > 0) {
if (! $this->hasPageData($requestedPage) && ($index = array_search($page, $pages, true)) > 0) {
$previousPage = $pages[$index - 1];
if ($originPage === null || ($previousPage->getName() !== $originPage->getName()
&& array_search($originPage, $pages, true) < $index))
@ -335,6 +440,36 @@ class Wizard
);
}
/**
* Return the next or previous page based on the given one
*
* @param Form $page The page to skip
*
* @return Form
*/
protected function skipPage(Form $page)
{
if ($this->parent) {
return $this->parent->skipPage($page);
}
if ($this->hasPageData($page->getName())) {
$pageData = & $this->getPageData();
unset($pageData[$page->getName()]);
}
$pages = $this->getPages();
if ($this->getDirection() === static::FORWARD) {
$nextPage = $pages[array_search($page, $pages, true) + 1];
$newPage = $this->getNewPage($nextPage->getName(), $page);
} else { // $this->getDirection() === static::BACKWARD
$previousPage = $pages[array_search($page, $pages, true) - 1];
$newPage = $this->getNewPage($previousPage->getName(), $page);
}
return $newPage;
}
/**
* Return whether the given page is this wizard's last page
*
@ -344,10 +479,27 @@ class Wizard
*/
protected function isLastPage(Form $page)
{
if ($this->parent) {
return $this->parent->isLastPage($page);
}
$pages = $this->getPages();
return $page->getName() === end($pages)->getName();
}
/**
* Return whether all of this wizard's pages were visited by the user
*
* The base implementation just verifies that the very last page has page data available.
*
* @return bool
*/
public function isComplete()
{
$pages = $this->getPages();
return $this->hasPageData($pages[count($pages) - 1]->getName());
}
/**
* Set whether this wizard has been completed
*
@ -421,6 +573,10 @@ class Wizard
*/
public function getSession()
{
if ($this->parent) {
return $this->parent->getSession();
}
return Session::getSession()->getNamespace(get_class($this));
}

View File

@ -4,15 +4,12 @@
namespace Icinga\Module\Monitoring;
use Icinga\Application\Icinga;
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\Forms\SummaryPage;
use Icinga\Module\Monitoring\Forms\Setup\WelcomePage;
use Icinga\Module\Monitoring\Forms\Setup\BackendPage;
@ -37,7 +34,7 @@ class MonitoringWizard extends Wizard implements SetupWizard
$this->addPage(new LivestatusResourcePage());
$this->addPage(new InstancePage());
$this->addPage(new SecurityPage());
$this->addPage(new SummaryPage());
$this->addPage(new SummaryPage(array('name' => 'setup_monitoring_summary')));
}
/**
@ -47,7 +44,7 @@ class MonitoringWizard extends Wizard implements SetupWizard
{
if ($page->getName() === 'setup_requirements') {
$page->setRequirements($this->getRequirements());
} elseif ($page->getName() === 'setup_summary') {
} elseif ($page->getName() === 'setup_monitoring_summary') {
$page->setSummary($this->getSetup()->getSummary());
$page->setSubjectTitle(mt('monitoring', 'the monitoring module', 'setup.summary.subject'));
} elseif (
@ -79,23 +76,7 @@ class MonitoringWizard extends Wizard implements SetupWizard
$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;
return $skip ? $this->skipPage($newPage) : $newPage;
}
/**
@ -125,8 +106,6 @@ class MonitoringWizard extends Wizard implements SetupWizard
$pageData = $this->getPageData();
$setup = new Setup();
$setup->addStep(new MakeDirStep(array(Icinga::app()->getConfigDir() . '/modules/monitoring'), 2770));
$setup->addStep(
new BackendStep(array(
'backendConfig' => $pageData['setup_monitoring_backend'],
@ -148,8 +127,6 @@ class MonitoringWizard extends Wizard implements SetupWizard
))
);
$setup->addStep(new EnableModuleStep('monitoring'));
return $setup;
}

View File

@ -4,22 +4,13 @@
namespace Icinga\Module\Setup\Forms;
use InvalidArgumentException;
use Icinga\Application\Icinga;
use Icinga\Web\Form;
use Icinga\Web\Session;
use Icinga\Web\Request;
class ModulePage extends Form
{
protected $session;
protected $wizards;
protected $modules;
protected $pageData;
protected $modulePaths;
/**
@ -29,7 +20,6 @@ class ModulePage extends Form
{
$this->setName('setup_modules');
$this->setViewScript('form/setup-modules.phtml');
$this->session = Session::getSession()->getNamespace(get_class($this));
$this->modulePaths = array();
if (($appModulePath = realpath(Icinga::app()->getApplicationDir() . '/../modules')) !== false) {
@ -37,84 +27,24 @@ class ModulePage extends Form
}
}
public function setPageData(array $pageData)
public function createElements(array $formData)
{
$this->pageData = $pageData;
return $this;
}
public function handleRequest(Request $request = null)
{
$isPost = strtolower($request->getMethod()) === 'post';
if ($isPost && $this->wasSent($request->getPost())) {
if (($newModule = $request->getPost('module')) !== null) {
$this->setCurrentModule($newModule);
$this->getResponse()->redirectAndExit($this->getRedirectUrl());
} else {
// The user submitted this form but with the parent wizard's navigation
// buttons so it's now up to the parent wizard to handle the request..
}
} else {
$wizard = $this->getCurrentWizard();
$wizardPage = $wizard->getCurrentPage();
$wizard->handleRequest($request);
if ($isPost && $wizard->isFinished() && $wizardPage->wasSent($request->getPost())) {
$wizards = $this->getWizards();
$newModule = null;
foreach ($wizards as $moduleName => $moduleWizard) {
if (false === $moduleWizard->isFinished()) {
$newModule = $moduleName;
}
}
if ($newModule === null) {
// In case all module wizards were completed just pick the first one again
reset($wizards);
$newModule = key($wizards);
}
$this->setCurrentModule($newModule);
}
foreach ($this->getModules() as $module) {
$this->addElement(
'checkbox',
$module->getName(),
array(
'required' => true,
'description' => $module->getDescription(),
'label' => ucfirst($module->getName()),
'value' => $module->getName() === 'monitoring' ? 1 : 0,
'decorators' => array('ViewHelper')
)
);
}
}
public function clearSession()
{
$this->session->clear();
foreach ($this->getWizards() as $wizard) {
$wizard->clearSession();
}
}
public function setCurrentModule($moduleName)
{
if (false === array_key_exists($moduleName, $this->getWizards())) {
throw new InvalidArgumentException(sprintf('Module "%s" does not provide a setup wizard', $moduleName));
}
$this->session->currentModule = $moduleName;
}
public function getCurrentModule()
{
$moduleName = $this->session->get('currentModule');
if ($moduleName === null) {
$moduleName = key($this->getWizards());
$this->setCurrentModule($moduleName);
}
return $moduleName;
}
public function getCurrentWizard()
{
$wizards = $this->getWizards();
return $wizards[$this->getCurrentModule()];
}
public function getModules()
protected function getModules()
{
if ($this->modules !== null) {
return $this->modules;
@ -125,37 +55,39 @@ class ModulePage extends Form
$moduleManager = Icinga::app()->getModuleManager();
$moduleManager->detectInstalledModules($this->modulePaths);
foreach ($moduleManager->listInstalledModules() as $moduleName) {
$this->modules[] = $moduleManager->loadModule($moduleName)->getModule($moduleName);
if ($moduleName !== 'setup') {
$this->modules[$moduleName] = $moduleManager->loadModule($moduleName)->getModule($moduleName);
}
}
return $this->modules;
}
public function getWizards()
public function getCheckedModules()
{
if ($this->wizards !== null) {
return $this->wizards;
} else {
$this->wizards = array();
}
$modules = $this->getModules();
foreach ($this->getModules() as $module) {
if ($module->providesSetupWizard()) {
$this->wizards[$module->getName()] = $module->getSetupWizard();
$checked = array();
foreach ($this->getElements() as $name => $element) {
if (array_key_exists($name, $modules) && $element->isChecked()) {
$checked[$name] = $modules[$name];
}
}
$this->mergePageData($this->wizards);
return $this->wizards;
return $checked;
}
protected function mergePageData(array $wizards)
public function getModuleWizards()
{
foreach ($wizards as $wizard) {
$wizardPageData = & $wizard->getPageData();
foreach ($this->pageData as $pageName => $pageData) {
$wizardPageData[$pageName] = $pageData;
$checked = $this->getCheckedModules();
$wizards = array();
foreach ($checked as $name => $module) {
if ($module->providesSetupWizard()) {
$wizards[$name] = $module->getSetupWizard();
}
}
return $wizards;
}
}

View File

@ -4,6 +4,7 @@
namespace Icinga\Module\Setup\Forms;
use LogicException;
use Icinga\Web\Form;
/**
@ -30,7 +31,12 @@ class SummaryPage extends Form
*/
public function init()
{
$this->setName('setup_summary');
if ($this->getName() === $this->filterName(get_class($this))) {
throw new LogicException(
'When utilizing ' . get_class($this) . ' it is required to set a unique name by using the form options'
);
}
$this->setViewScript('form/setup-summary.phtml');
}

View File

@ -3,41 +3,18 @@
use Icinga\Web\Wizard;
?>
<div class="module-menu">
<p><?= mt('setup', 'The following modules can be set up by using a web-based wizard as well. To setup a module, just complete its wizard and advance to the summary!'); ?></p>
<p><?= mt('setup', 'You can freely switch to a module\'s wizard by clicking its name below. The wizard you are currently looking at is written in bold. A small tick is shown on the right once a wizard has been completed.'); ?></p>
<form name="<?= $form->getName(); ?>" enctype="<?= $form->getEncType(); ?>" method="<?= $form->getMethod(); ?>" action="<?= $form->getAction(); ?>">
<?= $form->getElement($form->getTokenElementName()); ?>
<?= $form->getElement($form->getUidElementName()); ?>
<ul>
<?php $allFinished = true; ?>
<?php foreach ($form->getModules() as $module): ?>
<?php if ($module->providesSetupWizard()): ?>
<li>
<?php $isActive = $module->getName() === $form->getCurrentModule(); ?>
<button type="submit" name="module" value="<?= $module->getName(); ?>">
<?= $isActive ? '<strong>' : '' ?><?= $module->getTitle(); ?><?= $isActive ? '</strong>' : '' ?>
</button>
<?php if ($module->getSetupWizard()->isFinished()): ?>
<?= $this->icon('ok', mt('setup', 'Completed', 'setup.modules.wizard.state')); ?>
<?php else: ?>
<?php $allFinished = false; ?>
<?php endif ?>
</li>
<?php endif ?>
<?php endforeach ?>
</ul>
</form>
<?php if ($allFinished): ?>
<p class="all-completed"><?= mt('setup', 'You\'ve completed all module wizards!'); ?></p>
<?php else: ?>
<p style="font-size: 80%;"><?= mt('setup', 'Note that you can skip a specific module by just not completing its wizard.'); ?></p>
<form id="<?= $form->getName(); ?>" name="<?= $form->getName(); ?>" enctype="<?= $form->getEncType(); ?>" method="<?= $form->getMethod(); ?>" action="<?= $form->getAction(); ?>">
<h2><?= $this->translate('Modules', 'setup.page.title'); ?></h2>
<p><?= $this->translate('The following modules were found in your Icinga Web 2 installation. To enable and configure a module, just tick it and click "Next".'); ?></p>
<?php foreach ($form->getElements() as $element): ?>
<?php if (! in_array($element->getName(), array(Wizard::BTN_PREV, Wizard::BTN_NEXT, $form->getTokenElementName(), $form->getUidElementName()))): ?>
<div class="module">
<h3><label for="<?= $element->getId(); ?>"><strong><?= $element->getLabel(); ?></strong></label></h3>
<label for="<?= $element->getId(); ?>"><?= $element->getDescription(); ?></label>
<?= $element; ?>
</div>
<?php endif ?>
</div>
<div class="module-wizard">
<?= $form->getCurrentWizard()->getForm()->render(); ?>
</div>
<form name="<?= $form->getName(); ?>" enctype="<?= $form->getEncType(); ?>" method="<?= $form->getMethod(); ?>" action="<?= $form->getAction(); ?>">
<?php endforeach ?>
<?= $form->getElement($form->getTokenElementName()); ?>
<?= $form->getElement($form->getUidElementName()); ?>
<div class="buttons">

View File

@ -11,7 +11,17 @@ $requirements = $form->getRequirements();
<?php foreach ($requirements as $requirement): ?>
<tr>
<td><h2><?= $requirement->title; ?></h2></td>
<td style="width: 50%"><?= $requirement->description; ?></td>
<td style="width: 50%">
<?php if (is_array($requirement->description)): ?>
<ul>
<?php foreach ($requirement->description as $desc): ?>
<li><?= $desc; ?></li>
<?php endforeach ?>
</ul>
<?php else: ?>
<?= $requirement->description; ?>
<?php endif ?>
</td>
<td class="state <?= $requirement->state === Requirements::STATE_OK ? 'fulfilled' : (
$requirement->state === Requirements::STATE_OPTIONAL ? 'not-available' : 'missing'
); ?>"><?= $requirement->message; ?></td>
@ -22,7 +32,7 @@ $requirements = $form->getRequirements();
<td></td>
<td class="btn-update">
<div class="buttons">
<a title="<?= $this->translate('You may also need to restart the web-server for the changes to take effect!'); ?>" href="<?= $this->href(); ?>" class="button-like"><?= mt('setup', 'Refresh'); ?></a>
<a title="<?= $this->translate('You may also need to restart the web-server for the changes to take effect!'); ?>" href="<?= $this->href(); ?>" class="button-like"><?= $this->translate('Refresh'); ?></a>
</div>
</td>
</tr>

View File

@ -4,9 +4,8 @@ use Icinga\Web\Wizard;
?>
<p><?= sprintf(
mt(
'setup',
'The wizard is now complete. You can review the changes supposed to be made before setting up %1$s.'
$this->translate(
'You\'ve configured %1$s successfully. You can review the changes supposed to be made before setting it up.'
. ' Make sure that everything is correct (Feel free to navigate back to make any corrections!) so'
. ' that you can start using %1$s right after it has successfully been set up.'
),
@ -21,7 +20,7 @@ use Icinga\Web\Wizard;
<?php endif ?>
<?php endforeach ?>
</div>
<form id="<?= $form->getName(); ?>" name="<?= $form->getName(); ?>" enctype="<?= $form->getEncType(); ?>" method="<?= $form->getMethod(); ?>" action="<?= $form->getAction(); ?>">
<form id="<?= $form->getName(); ?>" name="<?= $form->getName(); ?>" enctype="<?= $form->getEncType(); ?>" method="<?= $form->getMethod(); ?>" action="<?= $form->getAction(); ?>" class="summary">
<?= $form->getElement($form->getTokenElementName()); ?>
<?= $form->getElement($form->getUidElementName()); ?>
<div class="buttons">

View File

@ -11,16 +11,14 @@ $cliPath = realpath(Icinga::app()->getApplicationDir() . '/../bin/icingacli');
?>
<div class="welcome-page">
<h2><?= mt('setup', 'Welcome to the configuration of Icinga Web 2!') ?></h2>
<h2><?= $this->translate('Welcome to the configuration of Icinga Web 2!') ?></h2>
<?php if (false === file_exists($setupTokenPath) && file_exists(Config::resolvePath('config.ini'))): ?>
<p class="restart-warning"><?= mt(
'setup',
<p class="restart-warning"><?= $this->translate(
'You\'ve already completed the configuration of Icinga Web 2. Note that most of your configuration'
. ' files will be overwritten in case you\'ll re-configure Icinga Web 2 using this wizard!'
); ?></p>
<?php else: ?>
<p><?= mt(
'setup',
<p><?= $this->translate(
'This wizard will guide you through the configuration of Icinga Web 2. Once completed and successfully'
. ' finished you are able to log in and to explore all the new and stunning features!'
); ?></p>
@ -39,23 +37,22 @@ $cliPath = realpath(Icinga::app()->getApplicationDir() . '/../bin/icingacli');
</div>
<div>
<p><?=
mt(
'setup',
$this->translate(
'To run this wizard a user needs to authenticate using a token which is usually'
. ' provided to him by an administrator who\'d followed the instructions below.'
); ?></p>
<p><?= mt('setup', 'If you\'ve got the IcingaCLI installed you can do the following:'); ?></p>
<p><?= $this->translate('If you\'ve got the IcingaCLI installed you can do the following:'); ?></p>
<div class="code">
<span><?= $cliPath ? $cliPath : 'icingacli'; ?> setup config directory --group <?= ($user = Platform::getPhpUser()) !== null ? $user : 'your_webserver_group'; ?><?= $configDir !== '/etc/icingaweb2' ? ' --config ' . $configDir : ''; ?>;</span>
<span><?= $cliPath ? $cliPath : 'icingacli'; ?> setup token create;</span>
</div>
<p><?= mt('setup', 'In case the IcingaCLI is missing you can create the token manually:'); ?></p>
<p><?= $this->translate('In case the IcingaCLI is missing you can create the token manually:'); ?></p>
<div class="code">
<span>su <?= ($user = Platform::getPhpUser()) !== null ? $user : 'your_webserver_user'; ?> -c "mkdir -m 2770 <?= dirname($setupTokenPath); ?>; head -c 12 /dev/urandom | base64 | tee <?= $setupTokenPath; ?>; chmod 0660 <?= $setupTokenPath; ?>;";</span>
</div>
<p><?= sprintf(
mt('setup', 'Please see the %s for an extensive description on how to access and use this wizard.'),
'<a href="http://docs.icinga.org/">' . mt('setup', 'Icinga Web 2 documentation') . '</a>' // TODO: Add link to iw2 docs which points to the installation topic
$this->translate('Please see the %s for an extensive description on how to access and use this wizard.'),
'<a href="http://docs.icinga.org/">' . $this->translate('Icinga Web 2 documentation') . '</a>' // TODO: Add link to iw2 docs which points to the installation topic
); ?></p>
</div>
</div>

View File

@ -4,7 +4,7 @@ use Icinga\Web\Notification;
$pages = $wizard->getPages();
$finished = isset($success);
$configPages = array_slice($pages, 2, count($pages) - 4, true);
$configPages = array_slice($pages, 3, count($pages) - 1, true);
$currentPos = array_search($wizard->getCurrentPage(), $pages, true);
list($configPagesLeft, $configPagesRight) = array_chunk($configPages, count($configPages) / 2, true);
@ -30,7 +30,7 @@ if ($notifications->hasMessages()) {
<?= $this->img('img/logo_icinga_big.png'); ?>
<div class="progress-bar">
<div class="step" style="width: 10%;">
<h1><?= mt('setup', 'Welcome', 'setup.progress'); ?></h1>
<h1><?= $this->translate('Welcome', 'setup.progress'); ?></h1>
<?php $stateClass = $finished || $currentPos > 0 ? 'complete' : (
$maxProgress > 0 ? 'visited' : 'active'
); ?>
@ -41,30 +41,43 @@ if ($notifications->hasMessages()) {
</tr></tbody></table>
</div>
<div class="step" style="width: 10%;">
<h1><?= mt('setup', 'Requirements', 'setup.progress'); ?></h1>
<h1><?= $this->translate('Modules', 'setup.progress'); ?></h1>
<?php $stateClass = $finished || $currentPos > 1 ? ' complete' : (
$maxProgress > 1 ? ' visited' : (
$currentPos === 1 ? ' active' : ''
)
); ?>
<table><tbody><tr>
<td class="left"><div class="line left<?= $stateClass; ?>"></div></td>
<td class="middle"><div class="bubble <?= $stateClass; ?>"></div></td>
<td class="right"><div class="line right <?= $stateClass; ?>"></div></td>
</tr></tbody></table>
</div>
<div class="step" style="width: 10%;">
<h1><?= $this->translate('Requirements', 'setup.progress'); ?></h1>
<?php $stateClass = $finished || $currentPos > 2 ? ' complete' : (
$maxProgress > 2 ? ' visited' : (
$currentPos === 2 ? ' active' : ''
)
); ?>
<table><tbody><tr>
<td class="left"><div class="line left<?= $stateClass; ?>"></div></td>
<td class="middle"><div class="bubble<?= $stateClass; ?>"></div></td>
<td class="right"><div class="line right<?= $stateClass; ?>"></div></td>
</tr></tbody></table>
</div>
<div class="step" style="width: 50%;">
<h1><?= mt('setup', 'Configuration', 'setup.progress'); ?></h1>
<div class="step" style="width: 60%;">
<h1><?= $this->translate('Configuration', 'setup.progress'); ?></h1>
<table><tbody><tr>
<td class="left">
<?php
$firstPage = current($configPagesLeft);
$lastPage = end($configPagesLeft);
$lineWidth = round(100 / count($configPagesLeft), 2, PHP_ROUND_HALF_DOWN);
$lineWidth = sprintf('%.2F', round(100 / count($configPagesLeft), 2, PHP_ROUND_HALF_DOWN));
?>
<?php foreach ($configPagesLeft as $pos => $page): ?>
<?php $stateClass = $finished || $pos < $currentPos ? ' complete' : (
$pos < $maxProgress ? ' visited' : ($currentPos > 1 ? ' active' : '')
$pos < $maxProgress ? ' visited' : ($currentPos > 2 ? ' active' : '')
); ?>
<?php if ($page === $firstPage): ?>
<div class="line left<?= $stateClass; ?>" style="float: left; width: <?= $lineWidth; ?>%; margin-right: 0"></div>
@ -78,7 +91,7 @@ if ($notifications->hasMessages()) {
<td class="middle">
<div class="bubble<?= array_key_exists($currentPos, $configPagesLeft) ? (
key($configPagesRight) <= $maxProgress ? ' visited' : ' active') : (
$finished || $currentPos > 1 ? ' complete' : (
$finished || $currentPos > 2 ? ' complete' : (
key($configPagesRight) < $maxProgress ? ' visited' : ''
)
); ?>"></div>
@ -87,11 +100,11 @@ if ($notifications->hasMessages()) {
<?php
$firstPage = current($configPagesRight);
$lastPage = end($configPagesRight);
$lineWidth = round(100 / count($configPagesRight), 2, PHP_ROUND_HALF_DOWN);
$lineWidth = sprintf('%.2F', round(100 / count($configPagesRight), 2, PHP_ROUND_HALF_DOWN));
?>
<?php foreach ($configPagesRight as $pos => $page): ?>
<?php $stateClass = $finished || $pos < $currentPos ? ' complete' : (
$pos < $maxProgress ? ' visited' : ($currentPos > 1 ? ' active' : '')
$pos < $maxProgress ? ' visited' : ($currentPos > 2 ? ' active' : '')
); ?>
<?php if ($page === $firstPage): ?>
<div class="line<?= $stateClass; ?>" style="float: left; width: <?= $lineWidth; ?>%; margin-left: -0.1em;"></div>
@ -105,27 +118,7 @@ if ($notifications->hasMessages()) {
</tr></tbody></table>
</div>
<div class="step" style="width: 10%;">
<h1><?= mt('setup', 'Modules', 'setup.progress'); ?></h1>
<?php $stateClass = $finished || $currentPos > count($pages) - 2 ? ' complete' : (
$maxProgress > count($pages) - 2 ? ' visited' : ($currentPos === count($pages) - 2 ? ' active' : '')
); ?>
<table><tbody><tr>
<td class="left"><div class="line left<?= $stateClass; ?>"></div></td>
<td class="middle"><div class="bubble<?= $stateClass; ?>"></div></td>
<td class="right"><div class="line right<?= $stateClass; ?>"></div></td>
</tr></tbody></table>
</div>
<div class="step" style="width: 10%;">
<h1><?= mt('setup', 'Summary', 'setup.progress'); ?></h1>
<?php $stateClass = $finished ? ' complete' : ($currentPos === count($pages) - 1 ? ' active' : ''); ?>
<table><tbody><tr>
<td class="left"><div class="line left<?= $stateClass; ?>"></div></td>
<td class="middle"><div class="bubble<?= $stateClass; ?>"></div></td>
<td class="right"><div class="line right<?= $stateClass; ?>"></div></td>
</tr></tbody></table>
</div>
<div class="step" style="width: 10%;">
<h1><?= mt('setup', 'Finish', 'setup.progress'); ?></h1>
<h1><?= $this->translate('Finish', 'setup.progress'); ?></h1>
<?php $stateClass = $finished ? ' active' : ''; ?>
<table><tbody><tr>
<td class="left"><div class="line left<?= $stateClass; ?>"></div></td>

View File

@ -11,16 +11,16 @@
<?php endif ?>
<?php endforeach ?>
<?php if ($success): ?>
<p class="success"><?= mt('setup', 'Congratulations! Icinga Web 2 has been successfully set up.'); ?></p>
<p class="success"><?= $this->translate('Congratulations! Icinga Web 2 has been successfully set up.'); ?></p>
<?php else: ?>
<p class="failure"><?= mt('setup', 'Sorry! Failed to set up Icinga Web 2 successfully.'); ?></p>
<p class="failure"><?= $this->translate('Sorry! Failed to set up Icinga Web 2 successfully.'); ?></p>
<?php endif ?>
</div>
<div class="buttons">
<?php if ($success): ?>
<a href="<?= $this->href('authentication/login'); ?>" class="button-like login"><?= mt('setup', 'Login to Icinga Web 2'); ?></a>
<a href="<?= $this->href('authentication/login'); ?>" class="button-like login"><?= $this->translate('Login to Icinga Web 2'); ?></a>
<?php else: ?>
<a href="<?= $this->href(); ?>" class="button-like"><?= mt('setup', 'Back'); ?></a>
<a href="<?= $this->href(); ?>" class="button-like"><?= $this->translate('Back'); ?></a>
<?php endif ?>
</div>
</div>

View File

@ -9,6 +9,9 @@ use IteratorAggregate;
/**
* Container to store and handle requirements
*
* TODO: Requirements should be registered as objects with a specific purpose (PhpModRequirement, PhpIniRequirement, ..)
* so that it's not necessary to define unique identifiers which may differ between different modules.
*/
class Requirements implements IteratorAggregate
{
@ -41,12 +44,40 @@ class Requirements implements IteratorAggregate
*
* @return self
*/
public function add($requirement)
public function add($name, $requirement)
{
$this->requirements[] = $requirement;
$this->requirements[$name] = array_key_exists($name, $this->requirements)
? $this->combine($this->requirements[$name], $requirement)
: $requirement;
return $this;
}
/**
* Combine the two given requirements
*
* Returns the most important requirement with the description from the other one being added.
*
* @param object $oldRequirement
* @param object $newRequirement
*
* @return object
*/
protected function combine($oldRequirement, $newRequirement)
{
if ($newRequirement->state === static::STATE_MANDATORY && $oldRequirement->state === static::STATE_OPTIONAL) {
$tempRequirement = $oldRequirement;
$oldRequirement = $newRequirement;
$newRequirement = $tempRequirement;
}
if (! is_array($oldRequirement->description)) {
$oldRequirement->description = array($oldRequirement->description);
}
$oldRequirement->description[] = $newRequirement->description;
return $oldRequirement;
}
/**
* Return all registered requirements
*
@ -70,6 +101,7 @@ class Requirements implements IteratorAggregate
/**
* Register an optional requirement
*
* @param string $name
* @param string $title
* @param string $description
* @param bool $state
@ -77,20 +109,24 @@ class Requirements implements IteratorAggregate
*
* @return self
*/
public function addOptional($title, $description, $state, $message)
public function addOptional($name, $title, $description, $state, $message)
{
$this->add((object) array(
'title' => $title,
'message' => $message,
'description' => $description,
'state' => (bool) $state ? static::STATE_OK : static::STATE_OPTIONAL
));
$this->add(
$name,
(object) array(
'title' => $title,
'message' => $message,
'description' => $description,
'state' => (bool) $state ? static::STATE_OK : static::STATE_OPTIONAL
)
);
return $this;
}
/**
* Register a mandatory requirement
*
* @param string $name
* @param string $title
* @param string $description
* @param bool $state
@ -98,14 +134,17 @@ class Requirements implements IteratorAggregate
*
* @return self
*/
public function addMandatory($title, $description, $state, $message)
public function addMandatory($name, $title, $description, $state, $message)
{
$this->add((object) array(
'title' => $title,
'message' => $message,
'description' => $description,
'state' => (bool) $state ? static::STATE_OK : static::STATE_MANDATORY
));
$this->add(
$name,
(object) array(
'title' => $title,
'message' => $message,
'description' => $description,
'state' => (bool) $state ? static::STATE_OK : static::STATE_MANDATORY
)
);
return $this;
}
@ -118,8 +157,8 @@ class Requirements implements IteratorAggregate
*/
public function merge(Requirements $requirements)
{
foreach ($requirements->getAll() as $requirement) {
$this->add($requirement);
foreach ($requirements->getAll() as $name => $requirement) {
$this->add($name, $requirement);
}
return $this;

View File

@ -12,13 +12,13 @@ class EnableModuleStep extends Step
{
protected $modulePaths;
protected $moduleName;
protected $moduleNames;
protected $error;
protected $errors;
public function __construct($moduleName)
public function __construct(array $moduleNames)
{
$this->moduleName = $moduleName;
$this->moduleNames = $moduleNames;
$this->modulePaths = array();
if (($appModulePath = realpath(Icinga::app()->getApplicationDir() . '/../modules')) !== false) {
@ -28,17 +28,20 @@ class EnableModuleStep extends Step
public function apply()
{
try {
$moduleManager = Icinga::app()->getModuleManager();
$moduleManager->detectInstalledModules($this->modulePaths);
$moduleManager->enableModule($this->moduleName);
} catch (Exception $e) {
$this->error = $e;
return false;
$moduleManager = Icinga::app()->getModuleManager();
$moduleManager->detectInstalledModules($this->modulePaths);
$success = true;
foreach ($this->moduleNames as $moduleName) {
try {
$moduleManager->enableModule($moduleName);
} catch (Exception $e) {
$this->errors[$moduleName] = $e;
$success = false;
}
}
$this->error = false;
return true;
return $success;
}
public function getSummary()
@ -48,12 +51,19 @@ class EnableModuleStep extends Step
public function getReport()
{
if ($this->error === false) {
return '<p>' . sprintf(mt('setup', 'Module "%s" has been successfully enabled.'), $this->moduleName) . '</p>';
} elseif ($this->error !== null) {
$message = mt('setup', 'Module "%s" could not be enabled. An error occured:');
return '<p class="error">' . sprintf($message, $this->moduleName) . '</p>'
. '<p>' . $this->error->getMessage() . '</p>';
$okMessage = mt('setup', 'Module "%s" has been successfully enabled.');
$failMessage = mt('setup', 'Module "%s" could not be enabled. An error occured:');
$report = '';
foreach ($this->moduleNames as $moduleName) {
if (isset($this->errors[$moduleName])) {
$report .= '<p class="error">' . sprintf($failMessage, $moduleName) . '</p>'
. '<p>' . $this->errors[$moduleName]->getMessage() . '</p>';
} else {
$report .= '<p>' . sprintf($okMessage, $moduleName) . '</p>';
}
}
return $report;
}
}

View File

@ -29,6 +29,7 @@ use Icinga\Module\Setup\Steps\DatabaseStep;
use Icinga\Module\Setup\Steps\GeneralConfigStep;
use Icinga\Module\Setup\Steps\ResourceStep;
use Icinga\Module\Setup\Steps\AuthenticationStep;
use Icinga\Module\Setup\Utils\EnableModuleStep;
use Icinga\Module\Setup\Utils\MakeDirStep;
use Icinga\Module\Setup\Utils\DbTool;
@ -83,6 +84,7 @@ class WebWizard extends Wizard implements SetupWizard
protected function init()
{
$this->addPage(new WelcomePage());
$this->addPage(new ModulePage());
$this->addPage(new RequirementsPage());
$this->addPage(new AuthenticationPage());
$this->addPage(new PreferencesPage());
@ -94,8 +96,14 @@ class WebWizard extends Wizard implements SetupWizard
$this->addPage(new AdminAccountPage());
$this->addPage(new GeneralConfigPage());
$this->addPage(new DatabaseCreationPage());
$this->addPage(new ModulePage());
$this->addPage(new SummaryPage());
$this->addPage(new SummaryPage(array('name' => 'setup_summary')));
if (($modulePageData = $this->getPageData('setup_modules')) !== null) {
$modulePage = $this->getPage('setup_modules')->populate($modulePageData);
foreach ($modulePage->getModuleWizards() as $moduleWizard) {
$this->addPage($moduleWizard);
}
}
}
/**
@ -167,9 +175,6 @@ class WebWizard extends Wizard implements SetupWizard
unset($pageData['setup_admin_account']);
unset($pageData['setup_authentication_backend']);
}
} elseif ($page->getName() === 'setup_modules') {
$page->setPageData($this->getPageData());
$page->handleRequest($request);
}
}
@ -222,23 +227,7 @@ class WebWizard extends Wizard implements SetupWizard
}
}
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;
return $skip ? $this->skipPage($newPage) : $newPage;
}
/**
@ -263,7 +252,6 @@ class WebWizard extends Wizard implements SetupWizard
public function clearSession()
{
parent::clearSession();
$this->getPage('setup_modules')->clearSession();
$tokenPath = Config::resolvePath('setup.token');
if (file_exists($tokenPath)) {
@ -297,7 +285,7 @@ class WebWizard extends Wizard implements SetupWizard
? $pageData['setup_database_creation']['password']
: null,
'schemaPath' => Config::module('setup')
->get('schema', 'path', Icinga::app()->getBaseDir('etc/schema'))
->get('schema', 'path', Icinga::app()->getBaseDir('etc' . DIRECTORY_SEPARATOR . 'schema'))
))
);
}
@ -350,20 +338,22 @@ class WebWizard extends Wizard implements SetupWizard
$setup->addStep(
new MakeDirStep(
array(
$configDir . '/modules',
$configDir . '/preferences',
$configDir . '/enabledModules'
$configDir . DIRECTORY_SEPARATOR . 'modules',
$configDir . DIRECTORY_SEPARATOR . 'preferences',
$configDir . DIRECTORY_SEPARATOR . 'enabledModules'
),
2770
)
);
foreach ($this->getPage('setup_modules')->setPageData($this->getPageData())->getWizards() as $wizard) {
if ($wizard->isFinished()) {
foreach ($this->getWizards() as $wizard) {
if ($wizard->isComplete()) {
$setup->addSteps($wizard->getSetup()->getSteps());
}
}
$setup->addStep(new EnableModuleStep(array_keys($this->getPage('setup_modules')->getCheckedModules())));
return $setup;
}
@ -376,6 +366,7 @@ class WebWizard extends Wizard implements SetupWizard
$phpVersion = Platform::getPhpVersion();
$requirements->addMandatory(
'php_version_>=_5_3_2',
mt('setup', 'PHP Version'),
mt(
'setup',
@ -388,6 +379,7 @@ class WebWizard extends Wizard implements SetupWizard
$defaultTimezone = Platform::getPhpConfig('date.timezone');
$requirements->addMandatory(
'existing_default_timezone',
mt('setup', 'Default Timezone'),
sprintf(
mt('setup', 'It is required that a default timezone has been set using date.timezone in %s.'),
@ -400,6 +392,7 @@ class WebWizard extends Wizard implements SetupWizard
);
$requirements->addOptional(
'platform=linux',
mt('setup', 'Linux Platform'),
mt(
'setup',
@ -411,6 +404,7 @@ class WebWizard extends Wizard implements SetupWizard
);
$requirements->addMandatory(
'existing_php_mod_openssl',
mt('setup', 'PHP Module: OpenSSL'),
mt('setup', 'The PHP module for OpenSSL is required to generate cryptographically safe password salts.'),
Platform::extensionLoaded('openssl'),
@ -420,6 +414,7 @@ class WebWizard extends Wizard implements SetupWizard
);
$requirements->addOptional(
'existing_php_mod_json',
mt('setup', 'PHP Module: JSON'),
mt('setup', 'The JSON module for PHP is required for various export functionalities as well as APIs.'),
Platform::extensionLoaded('json'),
@ -429,6 +424,7 @@ class WebWizard extends Wizard implements SetupWizard
);
$requirements->addOptional(
'existing_php_mod_ldap',
mt('setup', 'PHP Module: LDAP'),
mt('setup', 'If you\'d like to authenticate users using LDAP the corresponding PHP module is required'),
Platform::extensionLoaded('ldap'),
@ -438,6 +434,7 @@ class WebWizard extends Wizard implements SetupWizard
);
$requirements->addOptional(
'existing_php_mod_intl',
mt('setup', 'PHP Module: INTL'),
mt(
'setup',
@ -452,6 +449,7 @@ class WebWizard extends Wizard implements SetupWizard
// TODO(6172): Remove this requirement once we do not ship dompdf with Icinga Web 2 anymore
$requirements->addOptional(
'existing_php_mod_dom',
mt('setup', 'PHP Module: DOM'),
mt('setup', 'To be able to export views and reports to PDF, the DOM module for PHP is required.'),
Platform::extensionLoaded('dom'),
@ -461,6 +459,7 @@ class WebWizard extends Wizard implements SetupWizard
);
$requirements->addOptional(
'existing_php_mod_gd',
mt('setup', 'PHP Module: GD'),
mt(
'setup',
@ -474,6 +473,7 @@ class WebWizard extends Wizard implements SetupWizard
);
$requirements->addOptional(
'existing_php_mod_imagick',
mt('setup', 'PHP Module: Imagick'),
mt(
'setup',
@ -487,6 +487,7 @@ class WebWizard extends Wizard implements SetupWizard
);
$requirements->addOptional(
'existing_php_mod_pdo_mysql',
mt('setup', 'PHP Module: PDO-MySQL'),
mt(
'setup',
@ -499,6 +500,7 @@ class WebWizard extends Wizard implements SetupWizard
);
$requirements->addOptional(
'existing_php_mod_pdo_pgsql',
mt('setup', 'PHP Module: PDO-PostgreSQL'),
mt(
'setup',
@ -513,6 +515,7 @@ class WebWizard extends Wizard implements SetupWizard
$mysqlAdapterFound = Platform::zendClassExists('Zend_Db_Adapter_Pdo_Mysql');
$requirements->addOptional(
'existing_class_Zend_Db_Adapter_Pdo_Mysql',
mt('setup', 'Zend Database Adapter For MySQL'),
mt('setup', 'The Zend database adapter for MySQL is required to access a MySQL database.'),
$mysqlAdapterFound,
@ -523,6 +526,7 @@ class WebWizard extends Wizard implements SetupWizard
$pgsqlAdapterFound = Platform::zendClassExists('Zend_Db_Adapter_Pdo_Pgsql');
$requirements->addOptional(
'existing_class_Zend_Db_Adapter_Pdo_Pgsql',
mt('setup', 'Zend Database Adapter For PostgreSQL'),
mt('setup', 'The Zend database adapter for PostgreSQL is required to access a PostgreSQL database.'),
$pgsqlAdapterFound,
@ -533,6 +537,7 @@ class WebWizard extends Wizard implements SetupWizard
$configDir = Icinga::app()->getConfigDir();
$requirements->addMandatory(
'writable_directory_' . $configDir,
mt('setup', 'Writable Config Directory'),
mt(
'setup',
@ -548,8 +553,8 @@ class WebWizard extends Wizard implements SetupWizard
)
);
foreach ($this->getPage('setup_modules')->setPageData($this->getPageData())->getWizards() as $wizard) {
$requirements->merge($wizard->getRequirements()->allOptional());
foreach ($this->getWizards() as $wizard) {
$requirements->merge($wizard->getRequirements());
}
return $requirements;

View File

@ -176,6 +176,12 @@
margin: 0 1em 0 0;
}
ul {
margin: 0;
padding-left: 1em;
list-style-type: square;
}
&.state {
color: white;
padding: 0.4em;
@ -270,7 +276,7 @@
}
}
form#setup_summary {
form.summary {
clear: left;
}
}
@ -395,71 +401,44 @@
}
}
#setup {
div.module-wizard {
width: auto;
padding: 1em;
overflow: hidden;
border: solid 1px lightgrey;
#setup_modules {
div.module {
float: left;
width: 15em;
height: 15em;
margin: 1em;
padding: 0.3em;
border: 1px solid #ccc;
background-color: snow;
div.buttons {
margin: 1.5em 0 0;
h3 {
border: none;
margin: 0.5em 0;
text-align: center;
label {
margin: 0;
width: 15em;
cursor: pointer;
}
}
h3 + label {
width: 13.5em;
height: 13.9em;
overflow: auto;
cursor: pointer;
font-weight: normal;
}
input[type=checkbox] {
height: 10em;
float: right;
button[type=submit] {
padding: 0.5em;
line-height: 0.5em;
background-color: @colorPetrol;
&:hover, &:focus, &:active {
background-color: #666;
}
}
}
}
div.module-menu {
font-size: 0.9em;
width: 25%;
float: right;
margin-left: 1.5em;
p {
margin-top: 0;
&.all-completed {
.conspicuous-state-notification;
text-align: center;
font-size: 90%;
background-color: @colorOk;
}
}
ul {
padding-left: 1.2em;
button {
margin: 0 0 0.8em;
padding: 0;
color: black;
border: none;
outline: none;
font-size: 90%;
background-color: white;
&:hover {
color: #666;
cursor: pointer;
}
&:focus, &:active {
color: #666;
}
}
img {
margin: 0 0.5em 0.2em;
}
}
div.buttons {
padding-top: 1em;
clear: both;
}
}
}