Merge branch 'master' into feature/add-ssl-support-to-mysql-database-resources-11115

This commit is contained in:
Alexander A. Klimov 2016-12-08 16:50:41 +01:00
commit dce6b4eb08
575 changed files with 89006 additions and 110030 deletions

View File

@ -6,6 +6,16 @@
/* The directory which contains the plugins from the Monitoring Plugins project. */ /* The directory which contains the plugins from the Monitoring Plugins project. */
const PluginDir = "/usr/lib64/nagios/plugins" const PluginDir = "/usr/lib64/nagios/plugins"
/* The directory which contains the Manubulon plugins.
* Check the documentation, chapter "SNMP Manubulon Plugin Check Commands", for details.
*/
const ManubulonPluginDir = "/usr/lib64/nagios/plugins"
/* The directory which you use to store additional plugins which ITL provides user contributed command definitions for.
* Check the documentation, chapter "Plugins Contribution", for details.
*/
const PluginContribDir = "/usr/lib64/nagios/plugins"
/* Our local instance name. By default this is the server's hostname as returned by `hostname --fqdn`. /* Our local instance name. By default this is the server's hostname as returned by `hostname --fqdn`.
* This should be the common name from the API certificate. * This should be the common name from the API certificate.
*/ */
@ -13,3 +23,4 @@ const NodeName = "localhost"
/* Our local zone name. */ /* Our local zone name. */
const ZoneName = NodeName const ZoneName = NodeName
const TicketSalt = ""

View File

@ -1,5 +1,25 @@
# Icinga Web 2 Changelog # Icinga Web 2 Changelog
## What's New
### What's New in Version 2.3.4/2.3.3
#### Bugfixes
* Bug 11267: Links in plugin output don't behave as expected
* Bug 11348: Host aliases are not shown in detail area
* Bug 11728: First non whitespace character after comma stripped from plugin output
* Bug 11729: Sort by severity depends on state type
* Bug 11737: Zero width space characters destroy state highlighting in plugin output
* Bug 11796: Zero width space characters may destroy links in plugin output
* Bug 11831: module.info parsing fails in case it contains newlines that are not part of the module's description
* Bug 11850: "Add to menu" tab unnecessarily appears in command forms
* Bug 11871: Colors used in the timeline are not accessible
* Bug 11883: Delete action on comments and downtimes in list views not accessible because they lack context
* Bug 11885: Database: Asterisk filters ignored when combined w/ other filters
* Bug 11910: Web 2 lacks mobile meta tags
* Fix remote code execution via remote command transport
### What's New in Version 2.3.2 ### What's New in Version 2.3.2
#### Feature #### Feature
@ -14,8 +34,6 @@
* Bug 10848: Can't change items per page if filter is in modify state * Bug 10848: Can't change items per page if filter is in modify state
* Bug 11392: Can't configure monitoring backend via the web interface when no monitoring backend was configured * Bug 11392: Can't configure monitoring backend via the web interface when no monitoring backend was configured
## What's New
### What's New in Version 2.3.1 ### What's New in Version 2.3.1
#### Bugfixes #### Bugfixes

View File

@ -1 +1 @@
v2.3.2 v2.3.4

View File

@ -0,0 +1,73 @@
<?php
/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
namespace Icinga\Controllers;
use Icinga\Application\Config;
use Icinga\Authentication\User\UserBackend;
use Icinga\Data\ConfigObject;
use Icinga\Exception\ConfigurationError;
use Icinga\Forms\Account\ChangePasswordForm;
use Icinga\Forms\PreferenceForm;
use Icinga\User\Preferences\PreferencesStore;
use Icinga\Web\Controller;
/**
* My Account
*/
class AccountController extends Controller
{
/**
* {@inheritdoc}
*/
public function init()
{
$this->getTabs()
->add('account', array(
'title' => $this->translate('Update your account'),
'label' => $this->translate('My Account'),
'url' => 'account'
))
->add('navigation', array(
'title' => $this->translate('List and configure your own navigation items'),
'label' => $this->translate('Navigation'),
'url' => 'navigation'
));
}
/**
* My account
*/
public function indexAction()
{
$config = Config::app()->getSection('global');
$user = $this->Auth()->getUser();
if ($user->getAdditional('backend_type') === 'db') {
try {
$userBackend = UserBackend::create($user->getAdditional('backend_name'));
} catch (ConfigurationError $e) {
$userBackend = null;
}
if ($userBackend !== null) {
$changePasswordForm = new ChangePasswordForm();
$changePasswordForm
->setBackend($userBackend)
->handleRequest();
$this->view->changePasswordForm = $changePasswordForm;
}
}
$form = new PreferenceForm();
$form->setPreferences($user->getPreferences());
if ($config->get('config_backend', 'ini') !== 'none') {
$form->setStore(PreferencesStore::create(new ConfigObject(array(
'store' => $config->get('config_backend', 'ini'),
'resource' => $config->config_resource
)), $user));
}
$form->handleRequest();
$this->view->form = $form;
$this->getTabs()->activate('account');
}
}

View File

@ -0,0 +1,100 @@
<?php
/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
namespace Icinga\Controllers;
use Icinga\Exception\NotFoundError;
use Icinga\Forms\Announcement\AcknowledgeAnnouncementForm;
use Icinga\Forms\Announcement\AnnouncementForm;
use Icinga\Web\Announcement\AnnouncementIniRepository;
use Icinga\Web\Controller;
use Icinga\Web\Url;
class AnnouncementsController extends Controller
{
/**
* List all announcements
*/
public function indexAction()
{
$this->getTabs()->add(
'announcements',
array(
'active' => true,
'label' => $this->translate('Announcements'),
'title' => $this->translate('List All Announcements'),
'url' => Url::fromPath('announcements')
)
);
$repo = new AnnouncementIniRepository();
$this->view->announcements = $repo
->select(array('id', 'author', 'message', 'start', 'end'))
->order('start');
}
/**
* Create an announcement
*/
public function newAction()
{
$this->assertPermission('admin');
$form = $this->prepareForm()->add();
$form->handleRequest();
$this->renderForm($form, $this->translate('New Announcement'));
}
/**
* Update an announcement
*/
public function updateAction()
{
$this->assertPermission('admin');
$form = $this->prepareForm()->edit($this->params->getRequired('id'));
try {
$form->handleRequest();
} catch (NotFoundError $_) {
$this->httpNotFound($this->translate('Announcement not found'));
}
$this->renderForm($form, $this->translate('Update Announcement'));
}
/**
* Remove an announcement
*/
public function removeAction()
{
$this->assertPermission('admin');
$form = $this->prepareForm()->remove($this->params->getRequired('id'));
try {
$form->handleRequest();
} catch (NotFoundError $_) {
$this->httpNotFound($this->translate('Announcement not found'));
}
$this->renderForm($form, $this->translate('Remove Announcement'));
}
public function acknowledgeAction()
{
$this->assertHttpMethod('POST');
$this->getResponse()->setHeader('X-Icinga-Container', 'ignore', true);
$form = new AcknowledgeAnnouncementForm();
$form->handleRequest();
}
/**
* Assert permission admin and return a prepared RepositoryForm
*
* @return AnnouncementForm
*/
protected function prepareForm()
{
$form = new AnnouncementForm();
return $form
->setRepository(new AnnouncementIniRepository())
->setRedirectUrl(Url::fromPath('announcements'));
}
}

View File

@ -4,6 +4,8 @@
namespace Icinga\Controllers; namespace Icinga\Controllers;
use Icinga\Application\Icinga; use Icinga\Application\Icinga;
use Icinga\Web\Announcement\AnnouncementCookie;
use Icinga\Web\Announcement\AnnouncementIniRepository;
use Icinga\Web\Controller; use Icinga\Web\Controller;
use Icinga\Web\Session; use Icinga\Web\Session;
@ -14,6 +16,7 @@ class ApplicationStateController extends Controller
{ {
public function indexAction() public function indexAction()
{ {
$this->_helper->layout()->disableLayout();
if (isset($_COOKIE['icingaweb2-session'])) { if (isset($_COOKIE['icingaweb2-session'])) {
$last = (int) $_COOKIE['icingaweb2-session']; $last = (int) $_COOKIE['icingaweb2-session'];
} else { } else {
@ -34,6 +37,23 @@ class ApplicationStateController extends Controller
); );
$_COOKIE['icingaweb2-session'] = $now; $_COOKIE['icingaweb2-session'] = $now;
} }
Icinga::app()->getResponse()->setHeader('X-Icinga-Container', 'ignore', true); $announcementCookie = new AnnouncementCookie();
$announcementRepo = new AnnouncementIniRepository();
if ($announcementCookie->getEtag() !== $announcementRepo->getEtag()) {
$announcementCookie
->setEtag($announcementRepo->getEtag())
->setNextActive($announcementRepo->findNextActive());
$this->getResponse()->setCookie($announcementCookie);
$this->getResponse()->setHeader('X-Icinga-Announcements', 'refresh', true);
} else {
$nextActive = $announcementCookie->getNextActive();
if ($nextActive && $nextActive <= $now) {
$announcementCookie->setNextActive($announcementRepo->findNextActive());
$this->getResponse()->setCookie($announcementCookie);
$this->getResponse()->setHeader('X-Icinga-Announcements', 'refresh', true);
}
}
$this->getResponse()->setHeader('X-Icinga-Container', 'ignore', true);
} }
} }

View File

@ -4,6 +4,7 @@
namespace Icinga\Controllers; namespace Icinga\Controllers;
use Exception; use Exception;
use Icinga\Application\Version;
use InvalidArgumentException; use InvalidArgumentException;
use Icinga\Application\Config; use Icinga\Application\Config;
use Icinga\Application\Icinga; use Icinga\Application\Icinga;
@ -122,6 +123,7 @@ class ConfigController extends Controller
$this->view->module = $module; $this->view->module = $module;
$this->view->tabs = $module->getConfigTabs()->activate('info'); $this->view->tabs = $module->getConfigTabs()->activate('info');
$this->view->moduleGitCommitId = Version::getGitHead($module->getBaseDir());
} else { } else {
$this->view->module = false; $this->view->module = false;
$this->view->tabs = null; $this->view->tabs = null;
@ -213,7 +215,7 @@ class ConfigController extends Controller
$form->setOnSuccess(function (UserBackendConfigForm $form) { $form->setOnSuccess(function (UserBackendConfigForm $form) {
try { try {
$form->add(array_filter($form->getValues())); $form->add($form::transformEmptyValuesToNull($form->getValues()));
} catch (Exception $e) { } catch (Exception $e) {
$form->error($e->getMessage()); $form->error($e->getMessage());
return false; return false;
@ -244,12 +246,7 @@ class ConfigController extends Controller
$form->setIniConfig(Config::app('authentication')); $form->setIniConfig(Config::app('authentication'));
$form->setOnSuccess(function (UserBackendConfigForm $form) use ($backendName) { $form->setOnSuccess(function (UserBackendConfigForm $form) use ($backendName) {
try { try {
$form->edit($backendName, array_map( $form->edit($backendName, $form::transformEmptyValuesToNull($form->getValues()));
function ($v) {
return $v !== '' ? $v : null;
},
$form->getValues()
));
} catch (Exception $e) { } catch (Exception $e) {
$form->error($e->getMessage()); $form->error($e->getMessage());
return false; return false;
@ -393,10 +390,10 @@ class ConfigController extends Controller
$authConfig = Config::app('authentication'); $authConfig = Config::app('authentication');
foreach ($authConfig as $backendName => $config) { foreach ($authConfig as $backendName => $config) {
if ($config->get('resource') === $resource) { if ($config->get('resource') === $resource) {
$form->addDescription(sprintf( $form->warning(sprintf(
$this->translate( $this->translate(
'The resource "%s" is currently utilized for authentication by user backend "%s". ' . 'The resource "%s" is currently utilized for authentication by user backend "%s".'
'Removing the resource can result in noone being able to log in any longer.' . ' Removing the resource can result in noone being able to log in any longer.'
), ),
$resource, $resource,
$backendName $backendName
@ -404,6 +401,17 @@ class ConfigController extends Controller
} }
} }
// Check if selected resource is currently used as user preferences backend
if (Config::app()->get('global', 'config_resource') === $resource) {
$form->warning(sprintf(
$this->translate(
'The resource "%s" is currently utilized to store user preferences. Removing the'
. ' resource causes all current user preferences not being available any longer.'
),
$resource
));
}
$this->view->form = $form; $this->view->form = $form;
$this->render('resource/remove'); $this->render('resource/remove');
} }

View File

@ -32,8 +32,6 @@ class ErrorController extends ActionController
$error = $this->_getParam('error_handler'); $error = $this->_getParam('error_handler');
$exception = $error->exception; $exception = $error->exception;
/** @var \Exception $exception */ /** @var \Exception $exception */
Logger::error($exception);
Logger::error('Stacktrace: %s', $exception->getTraceAsString());
if (! ($isAuthenticated = $this->Auth()->isAuthenticated())) { if (! ($isAuthenticated = $this->Auth()->isAuthenticated())) {
$this->innerLayout = 'guest-error'; $this->innerLayout = 'guest-error';
@ -83,6 +81,7 @@ class ErrorController extends ActionController
break; break;
default: default:
$this->getResponse()->setHttpResponseCode(500); $this->getResponse()->setHttpResponseCode(500);
Logger::error("%s\n%s", $exception, $exception->getTraceAsString());
break; break;
} }
$this->view->message = $exception->getMessage(); $this->view->message = $exception->getMessage();

View File

@ -20,4 +20,9 @@ class LayoutController extends ActionController
$this->_helper->layout()->disableLayout(); $this->_helper->layout()->disableLayout();
$this->view->menuRenderer = Icinga::app()->getMenu()->getRenderer(); $this->view->menuRenderer = Icinga::app()->getMenu()->getRenderer();
} }
public function announcementsAction()
{
$this->_helper->layout()->disableLayout();
}
} }

View File

@ -128,11 +128,11 @@ class NavigationController extends Controller
$this->getTabs() $this->getTabs()
->add( ->add(
'preferences', 'account',
array( array(
'title' => $this->translate('Adjust the preferences of Icinga Web 2 according to your needs'), 'title' => $this->translate('Update your account'),
'label' => $this->translate('Preferences'), 'label' => $this->translate('My Account'),
'url' => 'preference' 'url' => 'account'
) )
) )
->add( ->add(
@ -219,7 +219,7 @@ class NavigationController extends Controller
$form->setDefaultUrl(rawurldecode($this->params->get('url', ''))); $form->setDefaultUrl(rawurldecode($this->params->get('url', '')));
$form->setOnSuccess(function (NavigationConfigForm $form) { $form->setOnSuccess(function (NavigationConfigForm $form) {
$data = array_filter($form->getValues()); $data = $form::transformEmptyValuesToNull($form->getValues());
try { try {
$form->add($data); $form->add($data);
@ -266,12 +266,7 @@ class NavigationController extends Controller
$form->setUserConfig(Config::navigation($itemType, $itemOwner)); $form->setUserConfig(Config::navigation($itemType, $itemOwner));
$form->setRedirectUrl($referrer === 'shared' ? 'navigation/shared' : 'navigation'); $form->setRedirectUrl($referrer === 'shared' ? 'navigation/shared' : 'navigation');
$form->setOnSuccess(function (NavigationConfigForm $form) use ($itemName) { $form->setOnSuccess(function (NavigationConfigForm $form) use ($itemName) {
$data = array_map( $data = $form::transformEmptyValuesToNull($form->getValues());
function ($v) {
return $v !== '' ? $v : null;
},
$form->getValues()
);
try { try {
$form->edit($itemName, $data); $form->edit($itemName, $data);

View File

@ -1,65 +0,0 @@
<?php
/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
namespace Icinga\Controllers;
use Icinga\Application\Config;
use Icinga\Data\ConfigObject;
use Icinga\Forms\PreferenceForm;
use Icinga\User\Preferences\PreferencesStore;
use Icinga\Web\Controller\BasePreferenceController;
use Icinga\Web\Url;
use Icinga\Web\Widget\Tab;
/**
* Application wide preference controller for user preferences
*
* @TODO(el): Rename to PreferencesController: https://dev.icinga.org/issues/10014
*/
class PreferenceController extends BasePreferenceController
{
/**
* Create tabs for this preference controller
*
* @return array
*
* @see BasePreferenceController::createProvidedTabs()
*/
public static function createProvidedTabs()
{
return array(
'preferences' => new Tab(array(
'title' => t('Adjust the preferences of Icinga Web 2 according to your needs'),
'label' => t('Preferences'),
'url' => 'preference'
)),
'navigation' => new Tab(array(
'title' => t('List and configure your own navigation items'),
'label' => t('Navigation'),
'url' => 'navigation'
))
);
}
/**
* Show form to adjust user preferences
*/
public function indexAction()
{
$config = Config::app()->getSection('global');
$user = $this->getRequest()->getUser();
$form = new PreferenceForm();
$form->setPreferences($user->getPreferences());
if ($config->get('config_backend', 'ini') !== 'none') {
$form->setStore(PreferencesStore::create(new ConfigObject(array(
'store' => $config->get('config_backend', 'ini'),
'resource' => $config->config_resource
)), $user));
}
$form->handleRequest();
$this->view->form = $form;
$this->getTabs()->activate('preferences');
}
}

View File

@ -43,7 +43,7 @@ class UsergroupbackendController extends Controller
$form->setIniConfig(Config::app('groups')); $form->setIniConfig(Config::app('groups'));
$form->setOnSuccess(function (UserGroupBackendForm $form) { $form->setOnSuccess(function (UserGroupBackendForm $form) {
try { try {
$form->add(array_filter($form->getValues())); $form->add($form::transformEmptyValuesToNull($form->getValues()));
} catch (Exception $e) { } catch (Exception $e) {
$form->error($e->getMessage()); $form->error($e->getMessage());
return false; return false;
@ -73,12 +73,7 @@ class UsergroupbackendController extends Controller
$form->setIniConfig(Config::app('groups')); $form->setIniConfig(Config::app('groups'));
$form->setOnSuccess(function (UserGroupBackendForm $form) use ($backendName) { $form->setOnSuccess(function (UserGroupBackendForm $form) use ($backendName) {
try { try {
$form->edit($backendName, array_map( $form->edit($backendName, $form::transformEmptyValuesToNull($form->getValues()));
function ($v) {
return $v !== '' ? $v : null;
},
$form->getValues()
));
} catch (Exception $e) { } catch (Exception $e) {
$form->error($e->getMessage()); $form->error($e->getMessage());
return false; return false;

11
application/fonts/fontello-ifont/LICENSE.txt Normal file → Executable file
View File

@ -3,7 +3,7 @@ Font license info
## Font Awesome ## Font Awesome
Copyright (C) 2012 by Dave Gandy Copyright (C) 2016 by Dave Gandy
Author: Dave Gandy Author: Dave Gandy
License: SIL () License: SIL ()
@ -19,15 +19,6 @@ Font license info
Homepage: http://somerandomdude.com/work/iconic/ Homepage: http://somerandomdude.com/work/iconic/
## MFG Labs
Copyright (C) 2012 by Daniel Bruce
Author: MFG Labs
License: SIL (http://scripts.sil.org/OFL)
Homepage: http://www.mfglabs.com/
## Entypo ## Entypo
Copyright (C) 2012 by Daniel Bruce Copyright (C) 2012 by Daniel Bruce

4
application/fonts/fontello-ifont/README.txt Normal file → Executable file
View File

@ -2,14 +2,14 @@ This webfont is generated by http://fontello.com open source project.
================================================================================ ================================================================================
Please, note, that you should obey original font licences, used to make this Please, note, that you should obey original font licenses, used to make this
webfont pack. Details available in LICENSE.txt file. webfont pack. Details available in LICENSE.txt file.
- Usually, it's enough to publish content of LICENSE.txt file somewhere on your - Usually, it's enough to publish content of LICENSE.txt file somewhere on your
site in "About" section. site in "About" section.
- If your project is open-source, usually, it will be ok to make LICENSE.txt - If your project is open-source, usually, it will be ok to make LICENSE.txt
file publically available in your repository. file publicly available in your repository.
- Fonts, used in Fontello, don't require a clickable link on your site. - Fonts, used in Fontello, don't require a clickable link on your site.
But any kind of additional authors crediting is welcome. But any kind of additional authors crediting is welcome.

54
application/fonts/fontello-ifont/config.json Normal file → Executable file
View File

@ -711,7 +711,7 @@
{ {
"uid": "3bd18d47a12b8709e9f4fe9ead4f7518", "uid": "3bd18d47a12b8709e9f4fe9ead4f7518",
"css": "reschedule", "css": "reschedule",
"code": 59492, "code": 59524,
"src": "entypo" "src": "entypo"
}, },
{ {
@ -744,12 +744,6 @@
"code": 59512, "code": 59512,
"src": "typicons" "src": "typicons"
}, },
{
"uid": "b90d80c250a9bbdd6cd3fe00e6351710",
"css": "ok",
"code": 59395,
"src": "iconic"
},
{ {
"uid": "11e664deed5b2587456a4f9c01d720b6", "uid": "11e664deed5b2587456a4f9c01d720b6",
"css": "cancel", "css": "cancel",
@ -775,10 +769,52 @@
"src": "iconic" "src": "iconic"
}, },
{ {
"uid": "9c3b8d8a6d477da4d3e65b92e4e9c290", "uid": "8f28d948aa6379b1a69d2a090e7531d4",
"css": "warning-empty",
"code": 59525,
"src": "typicons"
},
{
"uid": "d4816c0845aa43767213d45574b3b145",
"css": "history",
"code": 61914,
"src": "fontawesome"
},
{
"uid": "b035c28eba2b35c6ffe92aee8b0df507",
"css": "attention-circled",
"code": 59521,
"src": "fontawesome"
},
{
"uid": "73ffeb70554099177620847206c12457",
"css": "binoculars",
"code": 61925,
"src": "fontawesome"
},
{
"uid": "a73c5deb486c8d66249811642e5d719a",
"css": "arrows-cw",
"code": 59492,
"src": "fontawesome"
},
{
"uid": "dd6c6b221a1088ff8a9b9cd32d0b3dd5",
"css": "check",
"code": 59523,
"src": "fontawesome"
},
{
"uid": "b90d80c250a9bbdd6cd3fe00e6351710",
"css": "ok",
"code": 59395,
"src": "iconic"
},
{
"uid": "37c5ab63f10d7ad0b84d0978dcd0c7a8",
"css": "flapping", "css": "flapping",
"code": 59485, "code": 59485,
"src": "mfglabs" "src": "fontawesome"
} }
] ]
} }

0
application/fonts/fontello-ifont/css/animation.css vendored Normal file → Executable file
View File

8
application/fonts/fontello-ifont/css/ifont-codes.css vendored Normal file → Executable file
View File

@ -99,7 +99,7 @@
.icon-bell-off-empty:before { content: '\e861'; } /* '' */ .icon-bell-off-empty:before { content: '\e861'; } /* '' */
.icon-plug:before { content: '\e862'; } /* '' */ .icon-plug:before { content: '\e862'; } /* '' */
.icon-eye-off:before { content: '\e863'; } /* '' */ .icon-eye-off:before { content: '\e863'; } /* '' */
.icon-reschedule:before { content: '\e864'; } /* '' */ .icon-arrows-cw:before { content: '\e864'; } /* '' */
.icon-cw:before { content: '\e865'; } /* '' */ .icon-cw:before { content: '\e865'; } /* '' */
.icon-host:before { content: '\e866'; } /* '' */ .icon-host:before { content: '\e866'; } /* '' */
.icon-thumbs-up:before { content: '\e867'; } /* '' */ .icon-thumbs-up:before { content: '\e867'; } /* '' */
@ -128,3 +128,9 @@
.icon-twitter:before { content: '\e87e'; } /* '' */ .icon-twitter:before { content: '\e87e'; } /* '' */
.icon-facebook-squared:before { content: '\e87f'; } /* '' */ .icon-facebook-squared:before { content: '\e87f'; } /* '' */
.icon-gplus-squared:before { content: '\e880'; } /* '' */ .icon-gplus-squared:before { content: '\e880'; } /* '' */
.icon-attention-circled:before { content: '\e881'; } /* '' */
.icon-check:before { content: '\e883'; } /* '' */
.icon-reschedule:before { content: '\e884'; } /* '' */
.icon-warning-empty:before { content: '\e885'; } /* '' */
.icon-history:before { content: '\f1da'; } /* '' */
.icon-binoculars:before { content: '\f1e5'; } /* '' */

20
application/fonts/fontello-ifont/css/ifont-embedded.css vendored Normal file → Executable file

File diff suppressed because one or more lines are too long

8
application/fonts/fontello-ifont/css/ifont-ie7-codes.css vendored Normal file → Executable file
View File

@ -99,7 +99,7 @@
.icon-bell-off-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe861;&nbsp;'); } .icon-bell-off-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe861;&nbsp;'); }
.icon-plug { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe862;&nbsp;'); } .icon-plug { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe862;&nbsp;'); }
.icon-eye-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe863;&nbsp;'); } .icon-eye-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe863;&nbsp;'); }
.icon-reschedule { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe864;&nbsp;'); } .icon-arrows-cw { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe864;&nbsp;'); }
.icon-cw { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe865;&nbsp;'); } .icon-cw { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe865;&nbsp;'); }
.icon-host { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe866;&nbsp;'); } .icon-host { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe866;&nbsp;'); }
.icon-thumbs-up { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe867;&nbsp;'); } .icon-thumbs-up { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe867;&nbsp;'); }
@ -128,3 +128,9 @@
.icon-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe87e;&nbsp;'); } .icon-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe87e;&nbsp;'); }
.icon-facebook-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe87f;&nbsp;'); } .icon-facebook-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe87f;&nbsp;'); }
.icon-gplus-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe880;&nbsp;'); } .icon-gplus-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe880;&nbsp;'); }
.icon-attention-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe881;&nbsp;'); }
.icon-check { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe883;&nbsp;'); }
.icon-reschedule { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe884;&nbsp;'); }
.icon-warning-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe885;&nbsp;'); }
.icon-history { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1da;&nbsp;'); }
.icon-binoculars { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1e5;&nbsp;'); }

8
application/fonts/fontello-ifont/css/ifont-ie7.css vendored Normal file → Executable file
View File

@ -110,7 +110,7 @@
.icon-bell-off-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe861;&nbsp;'); } .icon-bell-off-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe861;&nbsp;'); }
.icon-plug { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe862;&nbsp;'); } .icon-plug { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe862;&nbsp;'); }
.icon-eye-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe863;&nbsp;'); } .icon-eye-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe863;&nbsp;'); }
.icon-reschedule { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe864;&nbsp;'); } .icon-arrows-cw { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe864;&nbsp;'); }
.icon-cw { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe865;&nbsp;'); } .icon-cw { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe865;&nbsp;'); }
.icon-host { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe866;&nbsp;'); } .icon-host { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe866;&nbsp;'); }
.icon-thumbs-up { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe867;&nbsp;'); } .icon-thumbs-up { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe867;&nbsp;'); }
@ -139,3 +139,9 @@
.icon-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe87e;&nbsp;'); } .icon-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe87e;&nbsp;'); }
.icon-facebook-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe87f;&nbsp;'); } .icon-facebook-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe87f;&nbsp;'); }
.icon-gplus-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe880;&nbsp;'); } .icon-gplus-squared { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe880;&nbsp;'); }
.icon-attention-circled { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe881;&nbsp;'); }
.icon-check { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe883;&nbsp;'); }
.icon-reschedule { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe884;&nbsp;'); }
.icon-warning-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe885;&nbsp;'); }
.icon-history { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1da;&nbsp;'); }
.icon-binoculars { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1e5;&nbsp;'); }

21
application/fonts/fontello-ifont/css/ifont.css vendored Normal file → Executable file
View File

@ -1,10 +1,11 @@
@font-face { @font-face {
font-family: 'ifont'; font-family: 'ifont';
src: url('../font/ifont.eot?65389432'); src: url('../font/ifont.eot?38679513');
src: url('../font/ifont.eot?65389432#iefix') format('embedded-opentype'), src: url('../font/ifont.eot?38679513#iefix') format('embedded-opentype'),
url('../font/ifont.woff?65389432') format('woff'), url('../font/ifont.woff2?38679513') format('woff2'),
url('../font/ifont.ttf?65389432') format('truetype'), url('../font/ifont.woff?38679513') format('woff'),
url('../font/ifont.svg?65389432#ifont') format('svg'); url('../font/ifont.ttf?38679513') format('truetype'),
url('../font/ifont.svg?38679513#ifont') format('svg');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
@ -14,7 +15,7 @@
@media screen and (-webkit-min-device-pixel-ratio:0) { @media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face { @font-face {
font-family: 'ifont'; font-family: 'ifont';
src: url('../font/ifont.svg?65389432#ifont') format('svg'); src: url('../font/ifont.svg?38679513#ifont') format('svg');
} }
} }
*/ */
@ -154,7 +155,7 @@
.icon-bell-off-empty:before { content: '\e861'; } /* '' */ .icon-bell-off-empty:before { content: '\e861'; } /* '' */
.icon-plug:before { content: '\e862'; } /* '' */ .icon-plug:before { content: '\e862'; } /* '' */
.icon-eye-off:before { content: '\e863'; } /* '' */ .icon-eye-off:before { content: '\e863'; } /* '' */
.icon-reschedule:before { content: '\e864'; } /* '' */ .icon-arrows-cw:before { content: '\e864'; } /* '' */
.icon-cw:before { content: '\e865'; } /* '' */ .icon-cw:before { content: '\e865'; } /* '' */
.icon-host:before { content: '\e866'; } /* '' */ .icon-host:before { content: '\e866'; } /* '' */
.icon-thumbs-up:before { content: '\e867'; } /* '' */ .icon-thumbs-up:before { content: '\e867'; } /* '' */
@ -183,3 +184,9 @@
.icon-twitter:before { content: '\e87e'; } /* '' */ .icon-twitter:before { content: '\e87e'; } /* '' */
.icon-facebook-squared:before { content: '\e87f'; } /* '' */ .icon-facebook-squared:before { content: '\e87f'; } /* '' */
.icon-gplus-squared:before { content: '\e880'; } /* '' */ .icon-gplus-squared:before { content: '\e880'; } /* '' */
.icon-attention-circled:before { content: '\e881'; } /* '' */
.icon-check:before { content: '\e883'; } /* '' */
.icon-reschedule:before { content: '\e884'; } /* '' */
.icon-warning-empty:before { content: '\e885'; } /* '' */
.icon-history:before { content: '\f1da'; } /* '' */
.icon-binoculars:before { content: '\f1e5'; } /* '' */

20
application/fonts/fontello-ifont/demo.html Normal file → Executable file
View File

@ -229,11 +229,11 @@ body {
} }
@font-face { @font-face {
font-family: 'ifont'; font-family: 'ifont';
src: url('./font/ifont.eot?43849680'); src: url('./font/ifont.eot?54126565');
src: url('./font/ifont.eot?43849680#iefix') format('embedded-opentype'), src: url('./font/ifont.eot?54126565#iefix') format('embedded-opentype'),
url('./font/ifont.woff?43849680') format('woff'), url('./font/ifont.woff?54126565') format('woff'),
url('./font/ifont.ttf?43849680') format('truetype'), url('./font/ifont.ttf?54126565') format('truetype'),
url('./font/ifont.svg?43849680#ifont') format('svg'); url('./font/ifont.svg?54126565#ifont') format('svg');
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
} }
@ -451,7 +451,7 @@ body {
<div title="Code: 0xe863" class="the-icons span3"><i class="demo-icon icon-eye-off">&#xe863;</i> <span class="i-name">icon-eye-off</span><span class="i-code">0xe863</span></div> <div title="Code: 0xe863" class="the-icons span3"><i class="demo-icon icon-eye-off">&#xe863;</i> <span class="i-name">icon-eye-off</span><span class="i-code">0xe863</span></div>
</div> </div>
<div class="row"> <div class="row">
<div title="Code: 0xe864" class="the-icons span3"><i class="demo-icon icon-reschedule">&#xe864;</i> <span class="i-name">icon-reschedule</span><span class="i-code">0xe864</span></div> <div title="Code: 0xe864" class="the-icons span3"><i class="demo-icon icon-arrows-cw">&#xe864;</i> <span class="i-name">icon-arrows-cw</span><span class="i-code">0xe864</span></div>
<div title="Code: 0xe865" class="the-icons span3"><i class="demo-icon icon-cw">&#xe865;</i> <span class="i-name">icon-cw</span><span class="i-code">0xe865</span></div> <div title="Code: 0xe865" class="the-icons span3"><i class="demo-icon icon-cw">&#xe865;</i> <span class="i-name">icon-cw</span><span class="i-code">0xe865</span></div>
<div title="Code: 0xe866" class="the-icons span3"><i class="demo-icon icon-host">&#xe866;</i> <span class="i-name">icon-host</span><span class="i-code">0xe866</span></div> <div title="Code: 0xe866" class="the-icons span3"><i class="demo-icon icon-host">&#xe866;</i> <span class="i-name">icon-host</span><span class="i-code">0xe866</span></div>
<div title="Code: 0xe867" class="the-icons span3"><i class="demo-icon icon-thumbs-up">&#xe867;</i> <span class="i-name">icon-thumbs-up</span><span class="i-code">0xe867</span></div> <div title="Code: 0xe867" class="the-icons span3"><i class="demo-icon icon-thumbs-up">&#xe867;</i> <span class="i-name">icon-thumbs-up</span><span class="i-code">0xe867</span></div>
@ -494,6 +494,14 @@ body {
</div> </div>
<div class="row"> <div class="row">
<div title="Code: 0xe880" class="the-icons span3"><i class="demo-icon icon-gplus-squared">&#xe880;</i> <span class="i-name">icon-gplus-squared</span><span class="i-code">0xe880</span></div> <div title="Code: 0xe880" class="the-icons span3"><i class="demo-icon icon-gplus-squared">&#xe880;</i> <span class="i-name">icon-gplus-squared</span><span class="i-code">0xe880</span></div>
<div title="Code: 0xe881" class="the-icons span3"><i class="demo-icon icon-attention-circled">&#xe881;</i> <span class="i-name">icon-attention-circled</span><span class="i-code">0xe881</span></div>
<div title="Code: 0xe883" class="the-icons span3"><i class="demo-icon icon-check">&#xe883;</i> <span class="i-name">icon-check</span><span class="i-code">0xe883</span></div>
<div title="Code: 0xe884" class="the-icons span3"><i class="demo-icon icon-reschedule">&#xe884;</i> <span class="i-name">icon-reschedule</span><span class="i-code">0xe884</span></div>
</div>
<div class="row">
<div title="Code: 0xe885" class="the-icons span3"><i class="demo-icon icon-warning-empty">&#xe885;</i> <span class="i-name">icon-warning-empty</span><span class="i-code">0xe885</span></div>
<div title="Code: 0xf1da" class="the-icons span3"><i class="demo-icon icon-history">&#xf1da;</i> <span class="i-name">icon-history</span><span class="i-code">0xf1da</span></div>
<div title="Code: 0xf1e5" class="the-icons span3"><i class="demo-icon icon-binoculars">&#xf1e5;</i> <span class="i-name">icon-binoculars</span><span class="i-code">0xf1e5</span></div>
</div> </div>
</div> </div>
<div class="container footer">Generated by <a href="http://fontello.com">fontello.com</a></div> <div class="container footer">Generated by <a href="http://fontello.com">fontello.com</a></div>

View File

@ -0,0 +1,123 @@
<?php
/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
namespace Icinga\Forms\Account;
use Icinga\Authentication\User\DbUserBackend;
use Icinga\Data\Filter\Filter;
use Icinga\User;
use Icinga\Web\Form;
use Icinga\Web\Notification;
/**
* Form for changing user passwords
*/
class ChangePasswordForm extends Form
{
/**
* The user backend
*
* @var DbUserBackend
*/
protected $backend;
/**
* {@inheritdoc}
*/
public function init()
{
$this->setSubmitLabel($this->translate('Update Account'));
}
/**
* {@inheritdoc}
*/
public function createElements(array $formData)
{
$this->addElement(
'password',
'old_password',
array(
'label' => $this->translate('Old Password'),
'required' => true
)
);
$this->addElement(
'password',
'new_password',
array(
'label' => $this->translate('New Password'),
'required' => true
)
);
$this->addElement(
'password',
'new_password_confirmation',
array(
'label' => $this->translate('Confirm New Password'),
'required' => true,
'validators' => array(
array('identical', false, array('new_password'))
)
)
);
}
/**
* {@inheritdoc}
*/
public function onSuccess()
{
$backend = $this->getBackend();
$backend->update(
$backend->getBaseTable(),
array('password' => $this->getElement('new_password')->getValue()),
Filter::where('user_name', $this->Auth()->getUser()->getUsername())
);
Notification::success($this->translate('Account updated'));
}
/**
* {@inheritdoc}
*/
public function isValid($formData)
{
$valid = parent::isValid($formData);
if (! $valid) {
return false;
}
$oldPasswordEl = $this->getElement('old_password');
if (! $this->backend->authenticate($this->Auth()->getUser(), $oldPasswordEl->getValue())) {
$oldPasswordEl->addError($this->translate('Old password is invalid'));
$this->markAsError();
return false;
}
return true;
}
/**
* Get the user backend
*
* @return DbUserBackend
*/
public function getBackend()
{
return $this->backend;
}
/**
* Set the user backend
*
* @param DbUserBackend $backend
*
* @return $this
*/
public function setBackend(DbUserBackend $backend)
{
$this->backend = $backend;
return $this;
}
}

View File

@ -0,0 +1,79 @@
<?php
/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
namespace Icinga\Forms\Announcement;
use Icinga\Web\Announcement\AnnouncementCookie;
use Icinga\Web\Form;
class AcknowledgeAnnouncementForm extends Form
{
/**
* {@inheritdoc}
*/
public function init()
{
$this->setAction('announcements/acknowledge');
$this->setAttrib('class', 'form-inline acknowledge-announcement-control');
$this->setRedirectUrl('layout/announcements');
}
/**
* {@inheritdoc}
*/
public function addSubmitButton()
{
$this->addElement(
'button',
'btn_submit',
array(
'class' => 'link-button spinner',
'decorators' => array(
'ViewHelper',
array('HtmlTag', array('tag' => 'div', 'class' => 'control-group form-controls'))
),
'escape' => false,
'ignore' => true,
'label' => $this->getView()->icon('cancel'),
'title' => $this->translate('Acknowledge this announcement'),
'type' => 'submit'
)
);
return $this;
}
/**
* {@inheritdoc}
*/
public function createElements(array $formData = array())
{
$this->addElements(
array(
array(
'hidden',
'hash',
array(
'required' => true,
'validators' => array('NotEmpty'),
'decorators' => array('ViewHelper')
)
)
)
);
return $this;
}
/**
* {@inheritdoc}
*/
public function onSuccess()
{
$cookie = new AnnouncementCookie();
$acknowledged = $cookie->getAcknowledged();
$acknowledged[] = $this->getElement('hash')->getValue();
$cookie->setAcknowledged($acknowledged);
$this->getResponse()->setCookie($cookie);
return true;
}
}

View File

@ -0,0 +1,117 @@
<?php
/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
namespace Icinga\Forms\Announcement;
use Icinga\Authentication\Auth;
use Icinga\Data\Filter\Filter;
use Icinga\Forms\RepositoryForm;
/**
* Create, update and delete announcements
*/
class AnnouncementForm extends RepositoryForm
{
/**
* {@inheritDoc}
*/
protected function createInsertElements(array $formData)
{
$this->addElement(
'text',
'author',
array(
'required' => true,
'value' => Auth::getInstance()->getUser()->getUsername(),
'disabled' => true
)
);
$this->addElement(
'textarea',
'message',
array(
'required' => true,
'label' => $this->translate('Message'),
'description' => $this->translate('The message to display to users')
)
);
$this->addElement(
'dateTimePicker',
'start',
array(
'required' => true,
'label' => $this->translate('Start'),
'description' => $this->translate('The time to display the announcement from')
)
);
$this->addElement(
'dateTimePicker',
'end',
array(
'required' => true,
'label' => $this->translate('End'),
'description' => $this->translate('The time to display the announcement until')
)
);
$this->setTitle($this->translate('Create a new announcement'));
$this->setSubmitLabel($this->translate('Create'));
}
/**
* {@inheritDoc}
*/
protected function createUpdateElements(array $formData)
{
$this->createInsertElements($formData);
$this->setTitle(sprintf($this->translate('Edit announcement %s'), $this->getIdentifier()));
$this->setSubmitLabel($this->translate('Save'));
}
/**
* {@inheritDoc}
*/
protected function createDeleteElements(array $formData)
{
$this->setTitle(sprintf($this->translate('Remove announcement %s?'), $this->getIdentifier()));
$this->setSubmitLabel($this->translate('Yes'));
}
/**
* {@inheritDoc}
*/
protected function createFilter()
{
return Filter::where('id', $this->getIdentifier());
}
/**
* {@inheritDoc}
*/
protected function getInsertMessage($success)
{
return $success
? $this->translate('Announcement created')
: $this->translate('Failed to create announcement');
}
/**
* {@inheritDoc}
*/
protected function getUpdateMessage($success)
{
return $success
? $this->translate('Announcement updated')
: $this->translate('Failed to update announcement');
}
/**
* {@inheritDoc}
*/
protected function getDeleteMessage($success)
{
return $success
? $this->translate('Announcement removed')
: $this->translate('Failed to remove announcement');
}
}

View File

@ -39,9 +39,10 @@ class LoginForm extends Form
'text', 'text',
'username', 'username',
array( array(
'required' => true, 'autocapitalize' => 'off',
'class' => false === isset($formData['username']) ? 'autofocus' : '',
'label' => $this->translate('Username'), 'label' => $this->translate('Username'),
'class' => false === isset($formData['username']) ? 'autofocus' : '' 'required' => true
) )
); );
$this->addElement( $this->addElement(

View File

@ -4,6 +4,8 @@
namespace Icinga\Forms\Config\General; namespace Icinga\Forms\Config\General;
use Icinga\Application\Logger; use Icinga\Application\Logger;
use Icinga\Application\Logger\Writer\SyslogWriter;
use Icinga\Application\Platform;
use Icinga\Web\Form; use Icinga\Web\Form;
/** /**
@ -90,22 +92,33 @@ class LoggingConfigForm extends Form
) )
) )
); );
/*
* Note(el): Since we provide only one possible value for the syslog facility, I opt against exposing if (! isset($formData['logging_log']) || $formData['logging_log'] === 'syslog') {
* this configuration. if (Platform::isWindows()) {
*/ /* @see https://secure.php.net/manual/en/function.openlog.php */
// $this->addElement( $this->addElement(
// 'select', 'hidden',
// 'logging_facility', 'logging_facility',
// array( array(
// 'required' => true, 'value' => 'user',
// 'label' => $this->translate('Facility'), 'disabled' => true
// 'description' => $this->translate('The syslog facility to utilize.'), )
// 'multiOptions' => array( );
// 'user' => 'LOG_USER' } else {
// ) $facilities = array_keys(SyslogWriter::$facilities);
// ) $this->addElement(
// ); 'select',
'logging_facility',
array(
'required' => true,
'label' => $this->translate('Facility'),
'description' => $this->translate('The syslog facility to utilize.'),
'value' => 'user',
'multiOptions' => array_combine($facilities, $facilities)
)
);
}
}
} elseif (isset($formData['logging_log']) && $formData['logging_log'] === 'file') { } elseif (isset($formData['logging_log']) && $formData['logging_log'] === 'file') {
$this->addElement( $this->addElement(
'text', 'text',

View File

@ -3,6 +3,7 @@
namespace Icinga\Forms\Config; namespace Icinga\Forms\Config;
use Icinga\Application\Config;
use InvalidArgumentException; use InvalidArgumentException;
use Icinga\Application\Platform; use Icinga\Application\Platform;
use Icinga\Exception\ConfigurationError; use Icinga\Exception\ConfigurationError;
@ -21,6 +22,13 @@ use Icinga\Web\Notification;
class ResourceConfigForm extends ConfigForm class ResourceConfigForm extends ConfigForm
{ {
/**
* If the global config must be updated because a resource has been changed, this is the updated global config
*
* @var Config|null
*/
protected $updatedAppConfig = null;
/** /**
* Initialize this form * Initialize this form
*/ */
@ -104,6 +112,16 @@ class ResourceConfigForm extends ConfigForm
$this->config->removeSection($name); $this->config->removeSection($name);
unset($values['name']); unset($values['name']);
$this->config->setSection($newName, $resourceConfig->merge($values)); $this->config->setSection($newName, $resourceConfig->merge($values));
if ($newName !== $name) {
$appConfig = Config::app();
$section = $appConfig->getSection('global');
if ($section->config_resource === $name) {
$section->config_resource = $newName;
$this->updatedAppConfig = $appConfig->setSection('global', $section);
}
}
return $resourceConfig; return $resourceConfig;
} }
@ -163,10 +181,10 @@ class ResourceConfigForm extends ConfigForm
return false; return false;
} }
} }
$this->add(array_filter($this->getValues())); $this->add(static::transformEmptyValuesToNull($this->getValues()));
$message = $this->translate('Resource "%s" has been successfully created'); $message = $this->translate('Resource "%s" has been successfully created');
} else { // edit existing resource } else { // edit existing resource
$this->edit($resource, array_filter($this->getValues())); $this->edit($resource, static::transformEmptyValuesToNull($this->getValues()));
$message = $this->translate('Resource "%s" has been successfully changed'); $message = $this->translate('Resource "%s" has been successfully changed');
} }
} catch (InvalidArgumentException $e) { } catch (InvalidArgumentException $e) {
@ -376,4 +394,15 @@ class ResourceConfigForm extends ConfigForm
return $this; return $this;
} }
/**
* {@inheritDoc}
*/
protected function writeConfig(Config $config)
{
parent::writeConfig($config);
if ($this->updatedAppConfig !== null) {
$this->updatedAppConfig->saveIni();
}
}
} }

View File

@ -200,12 +200,6 @@ class UserBackendConfigForm extends ConfigForm
} }
$backendConfig->merge($data); $backendConfig->merge($data);
foreach ($backendConfig->toArray() as $k => $v) {
if ($v === null) {
unset($backendConfig->$k);
}
}
$this->config->setSection($name, $backendConfig); $this->config->setSection($name, $backendConfig);
return $this; return $this;
} }

View File

@ -92,6 +92,8 @@ class LdapUserGroupBackendForm extends Form
$this->createGroupConfigElements($defaults, $groupConfigDisabled); $this->createGroupConfigElements($defaults, $groupConfigDisabled);
if (count($userBackends) === 1 || (isset($formData['user_backend']) && $formData['user_backend'] === 'none')) { if (count($userBackends) === 1 || (isset($formData['user_backend']) && $formData['user_backend'] === 'none')) {
$this->createUserConfigElements($defaults, $userConfigDisabled); $this->createUserConfigElements($defaults, $userConfigDisabled);
} else {
$this->createHiddenUserConfigElements();
} }
$this->addElement( $this->addElement(
@ -278,6 +280,19 @@ class LdapUserGroupBackendForm extends Form
); );
} }
/**
* Create and add all elements for the user configuration as hidden inputs
*
* This is required to purge already present options when unlinking a group backend with a user backend.
*/
protected function createHiddenUserConfigElements()
{
$this->addElement('hidden', 'user_class', array('disabled' => true));
$this->addElement('hidden', 'user_filter', array('disabled' => true));
$this->addElement('hidden', 'user_name_attribute', array('disabled' => true));
$this->addElement('hidden', 'user_base_dn', array('disabled' => true));
}
/** /**
* Return the names of all configured LDAP resources * Return the names of all configured LDAP resources
* *

View File

@ -127,14 +127,7 @@ class UserGroupBackendForm extends ConfigForm
unset($data['name']); unset($data['name']);
} }
$backendConfig->merge($data); $this->config->setSection($name, $backendConfig->merge($data));
foreach ($backendConfig->toArray() as $k => $v) {
if ($v === null) {
unset($backendConfig->$k);
}
}
$this->config->setSection($name, $backendConfig);
return $this; return $this;
} }

View File

@ -58,7 +58,7 @@ class ConfigForm extends Form
{ {
$sections = array(); $sections = array();
foreach ($this->getValues() as $sectionAndPropertyName => $value) { foreach ($this->getValues() as $sectionAndPropertyName => $value) {
if ($value === '') { if (empty($value)) {
$value = null; // Causes the config writer to unset it $value = null; // Causes the config writer to unset it
} }
@ -127,4 +127,16 @@ class ConfigForm extends Form
{ {
$config->saveIni(); $config->saveIni();
} }
/**
* Transform all empty values of the given array to null
*
* @param array $values
*
* @return array
*/
public static function transformEmptyValuesToNull(array $values)
{
return array_map(function ($v) { return empty($v) ? null : $v; }, $values);
}
} }

View File

@ -3,10 +3,11 @@
namespace Icinga\Forms\Dashboard; namespace Icinga\Forms\Dashboard;
use Icinga\Web\Widget\Dashboard;
use Icinga\Web\Form; use Icinga\Web\Form;
use Icinga\Web\Form\Validator\InternalUrlValidator;
use Icinga\Web\Form\Validator\UrlValidator; use Icinga\Web\Form\Validator\UrlValidator;
use Icinga\Web\Url; use Icinga\Web\Url;
use Icinga\Web\Widget\Dashboard;
use Icinga\Web\Widget\Dashboard\Dashlet; use Icinga\Web\Widget\Dashboard\Dashlet;
/** /**
@ -70,7 +71,7 @@ class DashletForm extends Form
'description' => $this->translate( 'description' => $this->translate(
'Enter url being loaded in the dashlet. You can paste the full URL, including filters.' 'Enter url being loaded in the dashlet. You can paste the full URL, including filters.'
), ),
'validators' => array(new UrlValidator()) 'validators' => array(new UrlValidator(), new InternalUrlValidator())
) )
); );
$this->addElement( $this->addElement(

View File

@ -426,11 +426,6 @@ class NavigationConfigForm extends ConfigForm
} }
$itemConfig->merge($data); $itemConfig->merge($data);
foreach ($itemConfig->toArray() as $k => $v) {
if ($v === null) {
unset($itemConfig->$k);
}
}
if ($shared) { if ($shared) {
// Share all descendant children // Share all descendant children

View File

@ -53,9 +53,27 @@ class NavigationItemForm extends Form
'allowEmpty' => true, 'allowEmpty' => true,
'label' => $this->translate('Url'), 'label' => $this->translate('Url'),
'description' => $this->translate( 'description' => $this->translate(
'The url of this navigation item. Leave blank if you only want the' 'The url of this navigation item. Leave blank if only the name should be displayed.'
. ' name being displayed. For external urls, make sure to prepend' . ' For urls with username and password and for all external urls,'
. ' an appropriate protocol identifier (e.g. http://example.tld)' . ' make sure to prepend an appropriate protocol identifier (e.g. http://example.tld)'
),
'validators' => array(
array(
'Callback',
false,
array(
'callback' => function($url) {
// Matches if the given url contains obviously
// a username but not any protocol identifier
return !preg_match('#^((?=[^/@]).)+@.*$#', $url);
},
'messages' => array(
'callbackValue' => $this->translate(
'Missing protocol identifier'
)
)
)
)
) )
) )
); );
@ -81,8 +99,10 @@ class NavigationItemForm extends Form
$values = parent::getValues($suppressArrayNotation); $values = parent::getValues($suppressArrayNotation);
if (isset($values['url']) && $values['url']) { if (isset($values['url']) && $values['url']) {
$url = Url::fromPath($values['url']); $url = Url::fromPath($values['url']);
if (! $url->isExternal() && ($relativePath = $url->getRelativeUrl())) { if ($url->getBasePath() === $this->getRequest()->getBasePath()) {
$values['url'] = $relativePath; $values['url'] = $url->getRelativeUrl();
} else {
$values['url'] = $url->getAbsoluteUrl();
} }
} }

View File

@ -156,6 +156,16 @@ class PreferenceForm extends Form
*/ */
public function createElements(array $formData) public function createElements(array $formData)
{ {
if (setlocale(LC_ALL, 0) === 'C') {
$this->warning(
$this->translate(
'Your language setting is not applied because your platform is missing the corresponding locale.'
. ' Make sure to install the correct language pack and restart your web server afterwards.'
),
false
);
}
if (! (bool) Config::app()->get('themes', 'disabled', false)) { if (! (bool) Config::app()->get('themes', 'disabled', false)) {
$themes = Icinga::app()->getThemes(); $themes = Icinga::app()->getThemes();
if (count($themes) > 1) { if (count($themes) > 1) {

View File

@ -42,6 +42,11 @@ class RoleForm extends ConfigForm
'application/stacktraces' => $this->translate( 'application/stacktraces' => $this->translate(
'Allow to adjust in the preferences whether to show stacktraces' 'Allow to adjust in the preferences whether to show stacktraces'
) . ' (application/stacktraces)', ) . ' (application/stacktraces)',
'application/log' => $this->translate('Allow to view the application log')
. ' (application/log)',
'admin' => $this->translate(
'Grant admin permissions, e.g. manage announcements'
) . ' (admin)',
'config/*' => $this->translate('Allow config access') . ' (config/*)' 'config/*' => $this->translate('Allow config access') . ' (config/*)'
); );
@ -284,7 +289,7 @@ class RoleForm extends ConfigForm
*/ */
public function getValues($suppressArrayNotation = false) public function getValues($suppressArrayNotation = false)
{ {
$values = array_filter(parent::getValues($suppressArrayNotation)); $values = static::transformEmptyValuesToNull(parent::getValues($suppressArrayNotation));
if (isset($values['permissions'])) { if (isset($values['permissions'])) {
$values['permissions'] = implode(', ', $values['permissions']); $values['permissions'] = implode(', ', $values['permissions']);
} }

View File

@ -18,6 +18,9 @@ if ($this->layout()->autorefreshInterval) {
?> ?>
<div id="header"> <div id="header">
<div id="announcements" class="container">
<?= $this->widget('announcements') ?>
</div>
<div id="header-logo-container"> <div id="header-logo-container">
<?= $this->qlink( <?= $this->qlink(
'', '',

View File

@ -24,13 +24,14 @@ $innerLayoutScript = $this->layout()->innerLayout . '.phtml';
<html class="no-js<?= $iframeClass ?>" lang="<?= $lang ?>"> <!--<![endif]--> <html class="no-js<?= $iframeClass ?>" lang="<?= $lang ?>"> <!--<![endif]-->
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta content="width=320; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"/>
<meta name="google" value="notranslate"> <meta name="google" value="notranslate">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta http-equiv="cleartype" content="on"> <meta http-equiv="cleartype" content="on">
<title><?= $this->title ? $this->escape($this->title) : 'Icinga Web' ?></title> <title><?= $this->title ? $this->escape($this->title) : 'Icinga Web' ?></title>
<!-- TODO: viewport and scale settings make no sense for us, fix this -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="application-name" content="Icinga Web 2">
<meta name="apple-mobile-web-app-title" content="Icinga">
<?php if ($isIframe): ?> <?php if ($isIframe): ?>
<base target="_parent"/> <base target="_parent"/>
<?php else: ?> <?php else: ?>
@ -47,6 +48,7 @@ $innerLayoutScript = $this->layout()->innerLayout . '.phtml';
<script src="<?= $this->baseUrl('js/vendor/respond.min.js');?>"></script> <script src="<?= $this->baseUrl('js/vendor/respond.min.js');?>"></script>
<![endif]--> <![endif]-->
<link type="image/png" rel="shortcut icon" href="<?= $this->baseUrl('img/favicon.png') ?>" /> <link type="image/png" rel="shortcut icon" href="<?= $this->baseUrl('img/favicon.png') ?>" />
<link rel="apple-touch-icon" href="<?= $this->baseUrl('img/touch-icon.png') ?>">
</head> </head>
<body id="body" class="loading"> <body id="body" class="loading">
<pre id="responsive-debug"></pre> <pre id="responsive-debug"></pre>

View File

@ -31,7 +31,7 @@ if ( isset($pdf) )
} }
</script> </script>
<?= $this->img('img/logo_icinga_big_dark.png', null, array('align' => 'right', 'width' => '75')) ?> <?= $this->img('img/icinga-logo-big-dark.png', null, array('align' => 'right', 'width' => '75')) ?>
<!--<div id="page-header"> <!--<div id="page-header">
<table> <table>
<tr> <tr>

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -7,16 +7,16 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Icinga Web 2 (None)\n" "Project-Id-Version: Icinga Web 2 (None)\n"
"Report-Msgid-Bugs-To: dev@icinga.org\n" "Report-Msgid-Bugs-To: dev@icinga.org\n"
"POT-Creation-Date: 2016-02-29 14:39+0000\n" "POT-Creation-Date: 2016-09-27 09:09+0000\n"
"PO-Revision-Date: 2016-02-29 22:16+0100\n" "PO-Revision-Date: 2016-09-27 11:15+0200\n"
"Last-Translator: Thomas Gelf <thomas@gelf.net>\n" "Last-Translator: Eric Lippmann <eric.lippmann@netways.de>\n"
"Language: de_DE\n" "Language: de_DE\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Language-Team: \n" "Language-Team: \n"
"X-Generator: Poedit 1.8.7\n" "X-Generator: Poedit 1.8.9\n"
#: /vagrant/library/Icinga/Web/Form/Validator/InArray.php:16 #: /vagrant/library/Icinga/Web/Form/Validator/InArray.php:16
#, php-format #, php-format
@ -143,9 +143,8 @@ msgid "Add"
msgstr "Hinzufügen" msgstr "Hinzufügen"
#: /vagrant/library/Icinga/Web/Widget/Tabextension/DashboardSettings.php:25 #: /vagrant/library/Icinga/Web/Widget/Tabextension/DashboardSettings.php:25
#, fuzzy
msgid "Add Dashlet" msgid "Add Dashlet"
msgstr "Dashlet bearbeiten" msgstr "Dashlet hinzufügen"
#: /vagrant/application/controllers/DashboardController.php:76 #: /vagrant/application/controllers/DashboardController.php:76
msgid "Add Dashlet To Dashboard" msgid "Add Dashlet To Dashboard"
@ -164,11 +163,11 @@ msgstr "Zum Dashboard hinzufügen"
msgid "Add To Menu" msgid "Add To Menu"
msgstr "Zum Menü hinzufügen" msgstr "Zum Menü hinzufügen"
#: /vagrant/application/views/scripts/user/list.phtml:34 #: /vagrant/application/views/scripts/user/list.phtml:32
msgid "Add a New User" msgid "Add a New User"
msgstr "Neuen Benutzer anlegen" msgstr "Neuen Benutzer anlegen"
#: /vagrant/application/views/scripts/group/list.phtml:34 #: /vagrant/application/views/scripts/group/list.phtml:32
msgid "Add a New User Group" msgid "Add a New User Group"
msgstr "Neue Gruppe anlegen" msgstr "Neue Gruppe anlegen"
@ -180,13 +179,13 @@ msgstr "Neue Gruppe anlegen"
msgid "Add a new user" msgid "Add a new user"
msgstr "Neuen Benutzer anlegen" msgstr "Neuen Benutzer anlegen"
#: /vagrant/library/Icinga/Web/Widget/FilterEditor.php:409 #: /vagrant/library/Icinga/Web/Widget/FilterEditor.php:410
msgid "Add another filter" msgid "Add another filter"
msgstr "Weiteren Filter hinzufügen..." msgstr "Weiteren Filter hinzufügen"
#: /vagrant/library/Icinga/Web/Widget/FilterWidget.php:80 #: /vagrant/library/Icinga/Web/Widget/FilterWidget.php:80
msgid "Add filter..." msgid "Add filter..."
msgstr "Filter hinzufügen..." msgstr "Filter hinzufügen"
#: /vagrant/application/forms/Config/UserGroup/AddMemberForm.php:125 #: /vagrant/application/forms/Config/UserGroup/AddMemberForm.php:125
#, php-format #, php-format
@ -231,6 +230,10 @@ msgstr "Erlaubt die Einstellung zur Anzeige von Stacktraces"
msgid "Allow to share navigation items" msgid "Allow to share navigation items"
msgstr "Erlaubt das Teilen von Navigationselemten" msgstr "Erlaubt das Teilen von Navigationselemten"
#: /vagrant/application/forms/Security/RoleForm.php:45
msgid "Allow to view the application log"
msgstr "Erlaubt das Einsehen des Anwendungslogs"
#: /vagrant/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php:135 #: /vagrant/application/forms/Config/UserGroup/LdapUserGroupBackendForm.php:135
msgid "" msgid ""
"An additional filter to use when looking up groups using the specified " "An additional filter to use when looking up groups using the specified "
@ -249,7 +252,7 @@ msgstr ""
"Verbindung. Leer lassen, um keine Filter zu verwenden." "Verbindung. Leer lassen, um keine Filter zu verwenden."
#: /vagrant/library/Icinga/Application/Web.php:318 #: /vagrant/library/Icinga/Application/Web.php:318
#: /vagrant/library/Icinga/Web/Menu.php:273 #: /vagrant/library/Icinga/Web/Menu.php:274
msgid "Application" msgid "Application"
msgstr "Anwendung" msgstr "Anwendung"
@ -268,7 +271,7 @@ msgstr "Anwenden"
#: /vagrant/application/controllers/ConfigController.php:50 #: /vagrant/application/controllers/ConfigController.php:50
#: /vagrant/library/Icinga/Application/Web.php:324 #: /vagrant/library/Icinga/Application/Web.php:324
#: /vagrant/library/Icinga/Web/Menu.php:278 #: /vagrant/library/Icinga/Web/Menu.php:279
msgid "Authentication" msgid "Authentication"
msgstr "Authentifizierung" msgstr "Authentifizierung"
@ -365,7 +368,7 @@ msgstr "Abbrechen"
msgid "Cancel this membership" msgid "Cancel this membership"
msgstr "Diese Mitgliedschaft beenden" msgstr "Diese Mitgliedschaft beenden"
#: /vagrant/library/Icinga/Web/Widget/FilterEditor.php:435 #: /vagrant/library/Icinga/Web/Widget/FilterEditor.php:436
msgid "Cancel this operation" msgid "Cancel this operation"
msgstr "Diese Operation abbrechen" msgstr "Diese Operation abbrechen"
@ -446,7 +449,7 @@ msgstr ""
msgid "Click to remove this part of your filter" msgid "Click to remove this part of your filter"
msgstr "Klicken, um diesen Teil des Filters zu löschen" msgstr "Klicken, um diesen Teil des Filters zu löschen"
#: /vagrant/library/Icinga/Repository/Repository.php:1022 #: /vagrant/library/Icinga/Repository/Repository.php:1023
#, php-format #, php-format
msgid "Column \"%s\" cannot be queried" msgid "Column \"%s\" cannot be queried"
msgstr "Spalte “%s” kann nicht abgefragt werden" msgstr "Spalte “%s” kann nicht abgefragt werden"
@ -472,7 +475,7 @@ msgid "Comma-separated list of users that are assigned to the role"
msgstr "Kommaseparierte Liste von Nutzern, die dieser Rolle zugewiesen werden" msgstr "Kommaseparierte Liste von Nutzern, die dieser Rolle zugewiesen werden"
#: /vagrant/library/Icinga/Application/Web.php:312 #: /vagrant/library/Icinga/Application/Web.php:312
#: /vagrant/library/Icinga/Web/Menu.php:268 #: /vagrant/library/Icinga/Web/Menu.php:269
msgid "Configuration" msgid "Configuration"
msgstr "Konfiguration" msgstr "Konfiguration"
@ -486,9 +489,8 @@ msgstr ""
"2" "2"
#: /vagrant/application/controllers/ConfigController.php:49 #: /vagrant/application/controllers/ConfigController.php:49
#, fuzzy
msgid "Configure the user and group backends" msgid "Configure the user and group backends"
msgstr "Neues Backend für Benutzergruppen erstellen" msgstr "Konfiguration der Benutzer- und Gruppen-Backends"
#: /vagrant/application/controllers/ConfigController.php:43 #: /vagrant/application/controllers/ConfigController.php:43
msgid "Configure which resources are being utilized by Icinga Web 2" msgid "Configure which resources are being utilized by Icinga Web 2"
@ -522,7 +524,7 @@ msgid ""
"modules." "modules."
msgstr "" msgstr ""
"Enthält die Verzeichnisse, die nach verfügbaren Modulen durchsucht werden " "Enthält die Verzeichnisse, die nach verfügbaren Modulen durchsucht werden "
"(kommasepariert). Module, die nicht in diesen Verzeichnissen vorhanden sind, " "(getrennt durch Doppelpunkte). Module, die nicht in diesen Verzeichnissen vorhanden sind, "
"können trotzdem in den Modulordner gesymlinkt werden. Diese werden " "können trotzdem in den Modulordner gesymlinkt werden. Diese werden "
"allerdings nicht in der der deaktivierten Module angezeigt." "allerdings nicht in der der deaktivierten Module angezeigt."
@ -652,6 +654,7 @@ msgstr ""
"Momentan ist kein Dashlet verfügbar. Das könnte durch die Aktivierung von " "Momentan ist kein Dashlet verfügbar. Das könnte durch die Aktivierung von "
"einigen der verfügbaren %s behoben werden." "einigen der verfügbaren %s behoben werden."
#: /vagrant/application/controllers/DashboardController.php:257
#: /vagrant/application/forms/Dashboard/DashletForm.php:120 #: /vagrant/application/forms/Dashboard/DashletForm.php:120
#: /vagrant/library/Icinga/Application/Web.php:290 #: /vagrant/library/Icinga/Application/Web.php:290
#: /vagrant/library/Icinga/Web/Menu.php:243 #: /vagrant/library/Icinga/Web/Menu.php:243
@ -712,10 +715,9 @@ msgid "Debug"
msgstr "Debug" msgstr "Debug"
#: /vagrant/application/forms/Config/General/ThemingConfigForm.php:42 #: /vagrant/application/forms/Config/General/ThemingConfigForm.php:42
#, fuzzy
msgctxt "Form element label" msgctxt "Form element label"
msgid "Default Theme" msgid "Default Theme"
msgstr "Standardsprache" msgstr "Standard-Theme"
#: /vagrant/application/views/scripts/config/module.phtml:46 #: /vagrant/application/views/scripts/config/module.phtml:46
msgid "Dependencies" msgid "Dependencies"
@ -742,6 +744,14 @@ msgstr "Automatische Aktualisierung deaktivieren"
msgid "Disable the %s module" msgid "Disable the %s module"
msgstr "Modul %s deaktivieren" msgstr "Modul %s deaktivieren"
#: /vagrant/application/forms/Dashboard/PaneForm.php:42
msgid "Disable the dashboard"
msgstr "Dashboard deaktivieren"
#: /vagrant/application/forms/Dashboard/PaneForm.php:43
msgid "Disabled"
msgstr "Deaktiviert"
#: /vagrant/application/forms/Config/UserBackend/LdapBackendForm.php:88 #: /vagrant/application/forms/Config/UserBackend/LdapBackendForm.php:88
msgctxt "A button to discover LDAP capabilities" msgctxt "A button to discover LDAP capabilities"
msgid "Discover" msgid "Discover"
@ -756,11 +766,10 @@ msgid "Edit Dashlet"
msgstr "Dashlet bearbeiten" msgstr "Dashlet bearbeiten"
#: /vagrant/application/views/scripts/user/show.phtml:16 #: /vagrant/application/views/scripts/user/show.phtml:16
#, fuzzy
msgid "Edit User" msgid "Edit User"
msgstr "Nutzer %s bearbeiten" msgstr "Nutzer bearbeiten"
#: /vagrant/application/views/scripts/dashboard/settings.phtml:53 #: /vagrant/application/views/scripts/dashboard/settings.phtml:54
#, php-format #, php-format
msgid "Edit dashlet %s" msgid "Edit dashlet %s"
msgstr "Dashlet %s bearbeiten" msgstr "Dashlet %s bearbeiten"
@ -831,7 +840,6 @@ msgid "Enter a title for the dashlet."
msgstr "Titel für das Dashlet eingeben" msgstr "Titel für das Dashlet eingeben"
#: /vagrant/application/forms/Dashboard/DashletForm.php:111 #: /vagrant/application/forms/Dashboard/DashletForm.php:111
#, fuzzy
msgid "Enter a title for the new dashboard" msgid "Enter a title for the new dashboard"
msgstr "Titel für das Dashboard eingeben" msgstr "Titel für das Dashboard eingeben"
@ -844,7 +852,6 @@ msgstr ""
"URLs mit Filtern möglich." "URLs mit Filtern möglich."
#: /vagrant/application/controllers/ErrorController.php:109 #: /vagrant/application/controllers/ErrorController.php:109
#, fuzzy
msgid "Error" msgid "Error"
msgstr "Fehler" msgstr "Fehler"
@ -981,17 +988,17 @@ msgstr "Dateipfad"
msgid "Filter Pattern" msgid "Filter Pattern"
msgstr "Muster" msgstr "Muster"
#: /vagrant/library/Icinga/Repository/Repository.php:1066 #: /vagrant/library/Icinga/Repository/Repository.php:1067
#, php-format #, php-format
msgid "Filter column \"%s\" not found" msgid "Filter column \"%s\" not found"
msgstr "Filterspalte “%s” nicht gefunden" msgstr "Filterspalte “%s” nicht gefunden"
#: /vagrant/library/Icinga/Repository/Repository.php:1070 #: /vagrant/library/Icinga/Repository/Repository.php:1071
#, php-format #, php-format
msgid "Filter column \"%s\" not found in table \"%s\"" msgid "Filter column \"%s\" not found in table \"%s\""
msgstr "Filterspalte “%s” in Tabelle “%s” nicht gefunden" msgstr "Filterspalte “%s” in Tabelle “%s” nicht gefunden"
#: /vagrant/library/Icinga/Web/Widget/FilterEditor.php:744 #: /vagrant/library/Icinga/Web/Widget/FilterEditor.php:745
#: /vagrant/library/Icinga/Web/Widget/FilterWidget.php:89 #: /vagrant/library/Icinga/Web/Widget/FilterWidget.php:89
msgid "Filter this list" msgid "Filter this list"
msgstr "Diese Liste filtern" msgstr "Diese Liste filtern"
@ -1110,7 +1117,7 @@ msgstr "Ich bin bereit zur Suche. Warte auf Eingabe"
msgid "Icinga Documentation" msgid "Icinga Documentation"
msgstr "Icinga Dokumentation" msgstr "Icinga Dokumentation"
#: /vagrant/application/views/scripts/authentication/login.phtml:11 #: /vagrant/application/views/scripts/authentication/login.phtml:18
msgid "Icinga Web 2 Documentation" msgid "Icinga Web 2 Documentation"
msgstr "Icinga Web 2 Dokumentation" msgstr "Icinga Web 2 Dokumentation"
@ -1118,7 +1125,7 @@ msgstr "Icinga Web 2 Dokumentation"
msgid "Icinga Web 2 Login" msgid "Icinga Web 2 Login"
msgstr "Icinga Web 2 Anmeldung" msgstr "Icinga Web 2 Anmeldung"
#: /vagrant/application/views/scripts/authentication/login.phtml:12 #: /vagrant/application/views/scripts/authentication/login.phtml:19
msgid "Icinga Web 2 Setup-Wizard" msgid "Icinga Web 2 Setup-Wizard"
msgstr "Icinga Web 2 Einrichtungsassistent" msgstr "Icinga Web 2 Einrichtungsassistent"
@ -1127,18 +1134,17 @@ msgid "Icinga Wiki"
msgstr "Icinga Wiki" msgstr "Icinga Wiki"
#: /vagrant/application/views/scripts/about/index.phtml:55 #: /vagrant/application/views/scripts/about/index.phtml:55
#: /vagrant/application/views/scripts/authentication/login.phtml:37 #: /vagrant/application/views/scripts/authentication/login.phtml:44
msgid "Icinga on Facebook" msgid "Icinga on Facebook"
msgstr "Icinga auf Facebook" msgstr "Icinga auf Facebook"
#: /vagrant/application/views/scripts/about/index.phtml:64 #: /vagrant/application/views/scripts/about/index.phtml:64
#: /vagrant/application/views/scripts/authentication/login.phtml:46 #: /vagrant/application/views/scripts/authentication/login.phtml:53
#, fuzzy
msgid "Icinga on Google+" msgid "Icinga on Google+"
msgstr "Icinga auf Facebook" msgstr "Icinga auf Google+"
#: /vagrant/application/views/scripts/about/index.phtml:46 #: /vagrant/application/views/scripts/about/index.phtml:46
#: /vagrant/application/views/scripts/authentication/login.phtml:27 #: /vagrant/application/views/scripts/authentication/login.phtml:34
msgid "Icinga on Twitter" msgid "Icinga on Twitter"
msgstr "Icinga auf Twitter" msgstr "Icinga auf Twitter"
@ -1188,7 +1194,7 @@ msgstr "Ungültiger Backendtyp “%s” angegeben"
msgid "Invalid resource type \"%s\" provided" msgid "Invalid resource type \"%s\" provided"
msgstr "Ungültiger Ressourcentyp “%s” angegeben" msgstr "Ungültiger Ressourcentyp “%s” angegeben"
#: /vagrant/application/views/scripts/authentication/login.phtml:7 #: /vagrant/application/views/scripts/authentication/login.phtml:14
#, php-format #, php-format
msgid "" msgid ""
"It appears that you did not configure Icinga Web 2 yet so it's not possible " "It appears that you did not configure Icinga Web 2 yet so it's not possible "
@ -1322,7 +1328,7 @@ msgid "Login"
msgstr "Anmelden" msgstr "Anmelden"
#: /vagrant/library/Icinga/Application/Web.php:355 #: /vagrant/library/Icinga/Application/Web.php:355
#: /vagrant/library/Icinga/Web/Menu.php:313 #: /vagrant/library/Icinga/Web/Menu.php:314
msgid "Logout" msgid "Logout"
msgstr "Abmelden" msgstr "Abmelden"
@ -1356,7 +1362,7 @@ msgstr "Menüeintrag"
msgid "Method Not Allowed" msgid "Method Not Allowed"
msgstr "Nicht zulässig" msgstr "Nicht zulässig"
#: /vagrant/library/Icinga/Web/Widget/FilterEditor.php:746 #: /vagrant/library/Icinga/Web/Widget/FilterEditor.php:747
#: /vagrant/library/Icinga/Web/Widget/FilterWidget.php:94 #: /vagrant/library/Icinga/Web/Widget/FilterWidget.php:94
msgid "Modify this filter" msgid "Modify this filter"
msgstr "Diesen Filter bearbeiten" msgstr "Diesen Filter bearbeiten"
@ -1396,7 +1402,7 @@ msgstr "Pfad zu den Modulen"
#: /vagrant/application/controllers/ConfigController.php:96 #: /vagrant/application/controllers/ConfigController.php:96
#: /vagrant/library/Icinga/Application/Web.php:336 #: /vagrant/library/Icinga/Application/Web.php:336
#: /vagrant/library/Icinga/Web/Menu.php:298 #: /vagrant/library/Icinga/Web/Menu.php:299
msgid "Modules" msgid "Modules"
msgstr "Module" msgstr "Module"
@ -1473,9 +1479,8 @@ msgid "New Dashboard Title"
msgstr "Neuen Dashboardtitel vergeben" msgstr "Neuen Dashboardtitel vergeben"
#: /vagrant/application/controllers/DashboardController.php:42 #: /vagrant/application/controllers/DashboardController.php:42
#, fuzzy
msgid "New Dashlet" msgid "New Dashlet"
msgstr "Dashlet aktualisieren" msgstr "Neues Dashlet"
#: /vagrant/application/controllers/NavigationController.php:244 #: /vagrant/application/controllers/NavigationController.php:244
msgid "New Navigation Item" msgid "New Navigation Item"
@ -1550,15 +1555,15 @@ msgstr ""
"Keine Authentifizierungsmethode verfügbar. Wurde bei der Installation von " "Keine Authentifizierungsmethode verfügbar. Wurde bei der Installation von "
"Icinga Web 2 eine authentication.ini erstellt?" "Icinga Web 2 eine authentication.ini erstellt?"
#: /vagrant/application/views/scripts/group/list.phtml:24 #: /vagrant/application/views/scripts/group/list.phtml:22
msgid "No backend found which is able to list user groups" msgid "No backend found which is able to list user groups"
msgstr "Kein Backend gefunden, um Benutzergruppen aufzulisten" msgstr "Kein Backend gefunden, um Benutzergruppen aufzulisten"
#: /vagrant/application/views/scripts/user/list.phtml:24 #: /vagrant/application/views/scripts/user/list.phtml:22
msgid "No backend found which is able to list users" msgid "No backend found which is able to list users"
msgstr "Kein Backend gefunden, um Benutzer aufzulisten" msgstr "Kein Backend gefunden, um Benutzer aufzulisten"
#: /vagrant/application/views/scripts/dashboard/settings.phtml:41 #: /vagrant/application/views/scripts/dashboard/settings.phtml:42
msgid "No dashlets added to dashboard" msgid "No dashlets added to dashboard"
msgstr "Dem Dashboard wurden keine Dashlets hinzugefügt" msgstr "Dem Dashboard wurden keine Dashlets hinzugefügt"
@ -1577,13 +1582,13 @@ msgstr ""
msgid "No roles found." msgid "No roles found."
msgstr "Es wurden keine Rollen gefunden" msgstr "Es wurden keine Rollen gefunden"
#: /vagrant/application/views/scripts/group/list.phtml:46 #: /vagrant/application/views/scripts/group/list.phtml:44
msgid "No user groups found matching the filter" msgid "No user groups found matching the filter"
msgstr "" msgstr ""
"Es wurden keine Benutzergruppen gefunden, welche den Filterkriterien " "Es wurden keine Benutzergruppen gefunden, welche den Filterkriterien "
"entsprechen" "entsprechen"
#: /vagrant/application/views/scripts/user/list.phtml:46 #: /vagrant/application/views/scripts/user/list.phtml:44
msgid "No users found matching the filter" msgid "No users found matching the filter"
msgstr "Es wurde kein Benutzer gefunden, der den Filterkriterien entspricht" msgstr "Es wurde kein Benutzer gefunden, der den Filterkriterien entspricht"
@ -1716,7 +1721,7 @@ msgstr "Port"
#: /vagrant/application/controllers/NavigationController.php:134 #: /vagrant/application/controllers/NavigationController.php:134
#: /vagrant/application/controllers/PreferenceController.php:33 #: /vagrant/application/controllers/PreferenceController.php:33
#: /vagrant/library/Icinga/Application/Web.php:350 #: /vagrant/library/Icinga/Application/Web.php:350
#: /vagrant/library/Icinga/Web/Menu.php:308 #: /vagrant/library/Icinga/Web/Menu.php:309
msgid "Preferences" msgid "Preferences"
msgstr "Einstellungen" msgstr "Einstellungen"
@ -1768,12 +1773,12 @@ msgid "Push to fill in the chosen connection's default settings."
msgstr "" msgstr ""
"Klicken Sie hier, um die gewählten Verbindungseinstellungen auszufüllen." "Klicken Sie hier, um die gewählten Verbindungseinstellungen auszufüllen."
#: /vagrant/library/Icinga/Repository/Repository.php:1018 #: /vagrant/library/Icinga/Repository/Repository.php:1019
#, php-format #, php-format
msgid "Query column \"%s\" not found" msgid "Query column \"%s\" not found"
msgstr "Abfragespalte “%s” wurde nicht gefunden" msgstr "Abfragespalte “%s” wurde nicht gefunden"
#: /vagrant/library/Icinga/Repository/Repository.php:1026 #: /vagrant/library/Icinga/Repository/Repository.php:1027
#, php-format #, php-format
msgid "Query column \"%s\" not found in table \"%s\"" msgid "Query column \"%s\" not found in table \"%s\""
msgstr "Abfragespalte “%s” wurde in Tabelle “%s” nicht gefunden" msgstr "Abfragespalte “%s” wurde in Tabelle “%s” nicht gefunden"
@ -1782,9 +1787,9 @@ msgstr "Abfragespalte “%s” wurde in Tabelle “%s” nicht gefunden"
msgid "Ready to search" msgid "Ready to search"
msgstr "Bereit zur Suche" msgstr "Bereit zur Suche"
#: /vagrant/application/views/scripts/group/list.phtml:55 #: /vagrant/application/views/scripts/group/list.phtml:52
#: /vagrant/application/views/scripts/group/show.phtml:77 #: /vagrant/application/views/scripts/group/show.phtml:77
#: /vagrant/application/views/scripts/user/list.phtml:55 #: /vagrant/application/views/scripts/user/list.phtml:53
msgid "Remove" msgid "Remove"
msgstr "Entfernen" msgstr "Entfernen"
@ -1793,9 +1798,8 @@ msgid "Remove Dashboard"
msgstr "Dashboard entfernen" msgstr "Dashboard entfernen"
#: /vagrant/application/controllers/DashboardController.php:158 #: /vagrant/application/controllers/DashboardController.php:158
#, fuzzy
msgid "Remove Dashlet" msgid "Remove Dashlet"
msgstr "Dashboard entfernen" msgstr "Dashlet entfernen"
#: /vagrant/application/controllers/DashboardController.php:192 #: /vagrant/application/controllers/DashboardController.php:192
msgid "Remove Dashlet From Dashboard" msgid "Remove Dashlet From Dashboard"
@ -1830,7 +1834,7 @@ msgstr "Benutzergruppe entfernen"
msgid "Remove User Group Backend" msgid "Remove User Group Backend"
msgstr "Backend für Benutzergruppen entfernen" msgstr "Backend für Benutzergruppen entfernen"
#: /vagrant/application/views/scripts/dashboard/settings.phtml:71 #: /vagrant/application/views/scripts/dashboard/settings.phtml:72
#, php-format #, php-format
msgid "Remove dashlet %s from pane %s" msgid "Remove dashlet %s from pane %s"
msgstr "Dashlet %s aus Dashboard %s entfernen" msgstr "Dashlet %s aus Dashboard %s entfernen"
@ -1845,7 +1849,7 @@ msgstr "Gruppe %s entfernen"
msgid "Remove navigation item %s" msgid "Remove navigation item %s"
msgstr "Navigationselement %s entfernen" msgstr "Navigationselement %s entfernen"
#: /vagrant/application/views/scripts/dashboard/settings.phtml:32 #: /vagrant/application/views/scripts/dashboard/settings.phtml:33
#, php-format #, php-format
msgid "Remove pane %s" msgid "Remove pane %s"
msgstr "Dashboard %s entfernen" msgstr "Dashboard %s entfernen"
@ -1873,11 +1877,11 @@ msgstr "Diesen Filter entfernen"
msgid "Remove this member" msgid "Remove this member"
msgstr "Dieses Mitglied entfernen" msgstr "Dieses Mitglied entfernen"
#: /vagrant/library/Icinga/Web/Widget/FilterEditor.php:396 #: /vagrant/library/Icinga/Web/Widget/FilterEditor.php:397
msgid "Remove this part of your filter" msgid "Remove this part of your filter"
msgstr "Klicken, um diesen Teil des Filters zu löschen" msgstr "Klicken, um diesen Teil des Filters zu löschen"
#: /vagrant/application/views/scripts/user/list.phtml:84 #: /vagrant/application/views/scripts/user/list.phtml:82
#, php-format #, php-format
msgid "Remove user %s" msgid "Remove user %s"
msgstr "Benutzer %s entfernen" msgstr "Benutzer %s entfernen"
@ -1892,7 +1896,7 @@ msgstr "Benutzer %s entfernen"
msgid "Remove user backend %s" msgid "Remove user backend %s"
msgstr "Benutzerbackend %s entfernen" msgstr "Benutzerbackend %s entfernen"
#: /vagrant/application/views/scripts/group/list.phtml:88 #: /vagrant/application/views/scripts/group/list.phtml:86
#, php-format #, php-format
msgid "Remove user group %s" msgid "Remove user group %s"
msgstr "Benutzergruppe %s entfernen" msgstr "Benutzergruppe %s entfernen"
@ -1998,7 +2002,7 @@ msgstr "Rolle aktualisiert"
#: /vagrant/application/controllers/GroupController.php:344 #: /vagrant/application/controllers/GroupController.php:344
#: /vagrant/application/controllers/RoleController.php:153 #: /vagrant/application/controllers/RoleController.php:153
#: /vagrant/application/controllers/UserController.php:310 #: /vagrant/application/controllers/UserController.php:310
#: /vagrant/library/Icinga/Web/Menu.php:283 #: /vagrant/library/Icinga/Web/Menu.php:284
msgid "Roles" msgid "Roles"
msgstr "Rollen" msgstr "Rollen"
@ -2053,7 +2057,7 @@ msgstr "Domains durchsuchen"
msgid "Search this domain for records of available servers." msgid "Search this domain for records of available servers."
msgstr "Diese Domain nach verfügbaren Servern durchsuchen." msgstr "Diese Domain nach verfügbaren Servern durchsuchen."
#: /vagrant/library/Icinga/Web/Widget/FilterEditor.php:740 #: /vagrant/library/Icinga/Web/Widget/FilterEditor.php:741
msgid "Search..." msgid "Search..."
msgstr "Suche..." msgstr "Suche..."
@ -2062,9 +2066,8 @@ msgid "Searching"
msgstr "Suche" msgstr "Suche"
#: /vagrant/application/forms/Dashboard/DashletForm.php:122 #: /vagrant/application/forms/Dashboard/DashletForm.php:122
#, fuzzy
msgid "Select a dashboard you want to add the dashlet to" msgid "Select a dashboard you want to add the dashlet to"
msgstr "Wählen Sie ein Dashboard aus, dem Sie das Dashlet hinzufügen wollen." msgstr "Wählen Sie das Dashboard aus, dem Sie das Dashlet hinzufügen wollen"
#: /vagrant/application/forms/Config/User/CreateMembershipForm.php:81 #: /vagrant/application/forms/Config/User/CreateMembershipForm.php:81
#, php-format #, php-format
@ -2117,7 +2120,7 @@ msgstr "Freigegeben"
msgid "Shared Navigation" msgid "Shared Navigation"
msgstr "Freigegebene Navigation" msgstr "Freigegebene Navigation"
#: /vagrant/library/Icinga/Web/Widget/Dashboard.php:238 #: /vagrant/library/Icinga/Web/Widget/Dashboard.php:235
#, php-format #, php-format
msgctxt "dashboard.pane.tooltip" msgctxt "dashboard.pane.tooltip"
msgid "Show %s" msgid "Show %s"
@ -2133,13 +2136,13 @@ msgstr "Suche anzeigen"
msgid "Show Stacktraces" msgid "Show Stacktraces"
msgstr "Stacktrace anzeigen" msgstr "Stacktrace anzeigen"
#: /vagrant/application/views/scripts/dashboard/settings.phtml:61 #: /vagrant/application/views/scripts/dashboard/settings.phtml:62
#, php-format #, php-format
msgid "Show dashlet %s" msgid "Show dashlet %s"
msgstr "Dashlet %s anzeigen" msgstr "Dashlet %s anzeigen"
#: /vagrant/application/views/scripts/group/show.phtml:93 #: /vagrant/application/views/scripts/group/show.phtml:93
#: /vagrant/application/views/scripts/user/list.phtml:70 #: /vagrant/application/views/scripts/user/list.phtml:68
#, php-format #, php-format
msgid "Show detailed information about %s" msgid "Show detailed information about %s"
msgstr "Zeige detaillierte Informationen über %s" msgstr "Zeige detaillierte Informationen über %s"
@ -2149,7 +2152,7 @@ msgstr "Zeige detaillierte Informationen über %s"
msgid "Show detailed information for group %s" msgid "Show detailed information for group %s"
msgstr "Zeige detaillierte Informationen über die Gruppe %s" msgstr "Zeige detaillierte Informationen über die Gruppe %s"
#: /vagrant/application/views/scripts/group/list.phtml:72 #: /vagrant/application/views/scripts/group/list.phtml:69
#, php-format #, php-format
msgid "Show detailed information for user group %s" msgid "Show detailed information for user group %s"
msgstr "Zeige detaillierte Informationen über die Benutzergruppe %s" msgstr "Zeige detaillierte Informationen über die Benutzergruppe %s"
@ -2206,7 +2209,7 @@ msgstr "Sortieren nach"
msgid "State" msgid "State"
msgstr "Status" msgstr "Status"
#: /vagrant/library/Icinga/Web/Widget/FilterEditor.php:422 #: /vagrant/library/Icinga/Web/Widget/FilterEditor.php:423
msgid "Strip this filter" msgid "Strip this filter"
msgstr "Diesen Filter bearbeiten" msgstr "Diesen Filter bearbeiten"
@ -2224,7 +2227,7 @@ msgid "Target"
msgstr "Ziel" msgstr "Ziel"
#: /vagrant/application/views/scripts/about/index.phtml:29 #: /vagrant/application/views/scripts/about/index.phtml:29
#: /vagrant/application/views/scripts/authentication/login.phtml:19 #: /vagrant/application/views/scripts/authentication/login.phtml:26
msgid "The Icinga Project" msgid "The Icinga Project"
msgstr "Das Icinga Projekt" msgstr "Das Icinga Projekt"
@ -2296,7 +2299,7 @@ msgstr ""
#: /vagrant/application/forms/Config/General/ThemingConfigForm.php:40 #: /vagrant/application/forms/Config/General/ThemingConfigForm.php:40
msgctxt "Form element description" msgctxt "Form element description"
msgid "The default theme" msgid "The default theme"
msgstr "Standardtheme" msgstr "Standard-Theme"
#: /vagrant/application/views/scripts/showConfiguration.phtml:5 #: /vagrant/application/views/scripts/showConfiguration.phtml:5
#, php-format #, php-format
@ -2561,14 +2564,12 @@ msgstr "Der eindeutige Name dieser Ressource"
#: /vagrant/application/forms/Navigation/NavigationItemForm.php:56 #: /vagrant/application/forms/Navigation/NavigationItemForm.php:56
msgid "" msgid ""
"The url of this navigation item. Leave blank if you only want the name being " "The url of this navigation item. Leave blank if only the name should be displayed. For urls with username and password and "
"displayed. For external urls, make sure to prepend an appropriate protocol " "for all external urls, make sure to prepend an appropriate protocol identifier (e.g. http://example.tld)"
"identifier (e.g. http://example.tld)"
msgstr "" msgstr ""
"Die URL dieses Navigationselementes. Dies kann leer gelassen werden, wenn " "Die URL dieses Navigationselementes. Dies kann leer gelassen werden, wenn nur der Name angezeigt werden soll. Stelle für URLs "
"nur der Name angezeigt werden soll. Stellen Sie für externe URLs sicher, " "mit Benutzername und Passwort sowie alle externen URLs sicher, dass eine passender Protokollnamen voran gestellt ist (z.B. "
"dass Sie einen passenden Protokollnamen voran stellen (z.B. http://example." "http://example.tld)"
"tld)"
#: /vagrant/application/forms/Navigation/DashletForm.php:29 #: /vagrant/application/forms/Navigation/DashletForm.php:29
msgid "" msgid ""
@ -2600,7 +2601,7 @@ msgstr "Der für die Authentifizierung zu benutzende Benutzername."
#: /vagrant/application/forms/PreferenceForm.php:170 #: /vagrant/application/forms/PreferenceForm.php:170
msgctxt "Form element label" msgctxt "Form element label"
msgid "Theme" msgid "Theme"
msgstr "" msgstr "Theme"
#: /vagrant/application/views/scripts/navigation/shared.phtml:15 #: /vagrant/application/views/scripts/navigation/shared.phtml:15
msgid "There are currently no navigation items being shared" msgid "There are currently no navigation items being shared"
@ -2635,6 +2636,7 @@ msgid "This module has no dependencies"
msgstr "Dieses Modul hat keine Abhängigkeiten" msgstr "Dieses Modul hat keine Abhängigkeiten"
#: /vagrant/library/Icinga/Application/Modules/Module.php:721 #: /vagrant/library/Icinga/Application/Modules/Module.php:721
#: /vagrant/library/Icinga/Application/Modules/ModuleInfo.php:13
msgid "This module has no description" msgid "This module has no description"
msgstr "Dieses Modul hat keine Beschreibung" msgstr "Dieses Modul hat keine Beschreibung"
@ -2651,6 +2653,14 @@ msgid "Tick this box to share this item with others"
msgstr "" msgstr ""
"Markieren Sie dieses Kästchen, um dieses Element mit Anderen zu teilen." "Markieren Sie dieses Kästchen, um dieses Element mit Anderen zu teilen."
#: /vagrant/application/forms/Dashboard/PaneForm.php:34
msgid "Title"
msgstr "Titel"
#: /vagrant/application/forms/Dashboard/PaneForm.php:33
msgid "Title of the dashboard"
msgstr "Titel für das Dashboard"
#: /vagrant/application/forms/Config/Resource/SshResourceForm.php:91 #: /vagrant/application/forms/Config/Resource/SshResourceForm.php:91
msgid "To modify the private key you must recreate this resource." msgid "To modify the private key you must recreate this resource."
msgstr "" msgstr ""
@ -2708,6 +2718,10 @@ msgstr "Freigabe aufheben"
msgid "Unshare this navigation item" msgid "Unshare this navigation item"
msgstr "Freigabe dieses Navigationselementes aufheben" msgstr "Freigabe dieses Navigationselementes aufheben"
#: /vagrant/application/forms/Dashboard/PaneForm.php:21
msgid "Update Dashboard"
msgstr "Dashboard aktualisieren"
#: /vagrant/application/controllers/DashboardController.php:86 #: /vagrant/application/controllers/DashboardController.php:86
#: /vagrant/application/controllers/DashboardController.php:92 #: /vagrant/application/controllers/DashboardController.php:92
msgid "Update Dashlet" msgid "Update Dashlet"
@ -2752,8 +2766,7 @@ msgstr ""
#: /vagrant/library/Icinga/Web/Form.php:951 #: /vagrant/library/Icinga/Web/Form.php:951
#: /vagrant/library/Icinga/Web/Form/Decorator/Autosubmit.php:100 #: /vagrant/library/Icinga/Web/Form/Decorator/Autosubmit.php:100
msgid "" msgid ""
"Upon its value has changed, this field issues an automatic update of this " "Upon its value changing, this field issues an automatic update of this page."
"page."
msgstr "" msgstr ""
"Dieses Feld löst eine automatische Aktualisierung dieser Seite aus, wenn " "Dieses Feld löst eine automatische Aktualisierung dieser Seite aus, wenn "
"sein Inhalt geändert wird." "sein Inhalt geändert wird."
@ -2821,7 +2834,7 @@ msgstr "Backends für Benutzer"
#: /vagrant/application/controllers/GroupController.php:69 #: /vagrant/application/controllers/GroupController.php:69
#: /vagrant/application/controllers/UserController.php:101 #: /vagrant/application/controllers/UserController.php:101
#: /vagrant/application/views/scripts/group/list.phtml:53 #: /vagrant/application/views/scripts/group/list.phtml:50
#: /vagrant/library/Icinga/Authentication/UserGroup/DbUserGroupBackend.php:115 #: /vagrant/library/Icinga/Authentication/UserGroup/DbUserGroupBackend.php:115
#: /vagrant/library/Icinga/Authentication/UserGroup/DbUserGroupBackend.php:120 #: /vagrant/library/Icinga/Authentication/UserGroup/DbUserGroupBackend.php:120
#: /vagrant/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php:465 #: /vagrant/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php:465
@ -2833,7 +2846,6 @@ msgid "User Group Backend"
msgstr "Backend für Benutzergruppen" msgstr "Backend für Benutzergruppen"
#: /vagrant/application/views/scripts/config/userbackend/reorder.phtml:19 #: /vagrant/application/views/scripts/config/userbackend/reorder.phtml:19
#, fuzzy
msgid "User Group Backends" msgid "User Group Backends"
msgstr "Backend für Benutzergruppen" msgstr "Backend für Benutzergruppen"
@ -2905,7 +2917,7 @@ msgstr ""
"soll. Bitte beachten Sie, dass für diesen Benutzer ein schlüsselbasierter " "soll. Bitte beachten Sie, dass für diesen Benutzer ein schlüsselbasierter "
"Zugriff möglich sein muss." "Zugriff möglich sein muss."
#: /vagrant/library/Icinga/Web/Menu.php:293 #: /vagrant/library/Icinga/Web/Menu.php:294
msgid "Usergroups" msgid "Usergroups"
msgstr "Benutzergruppen" msgstr "Benutzergruppen"
@ -2915,7 +2927,7 @@ msgstr "Benutzergruppen"
#: /vagrant/application/forms/Config/Resource/DbResourceForm.php:127 #: /vagrant/application/forms/Config/Resource/DbResourceForm.php:127
#: /vagrant/application/forms/Config/User/UserForm.php:33 #: /vagrant/application/forms/Config/User/UserForm.php:33
#: /vagrant/application/views/scripts/group/show.phtml:75 #: /vagrant/application/views/scripts/group/show.phtml:75
#: /vagrant/application/views/scripts/user/list.phtml:53 #: /vagrant/application/views/scripts/user/list.phtml:51
#: /vagrant/library/Icinga/Authentication/User/DbUserBackend.php:115 #: /vagrant/library/Icinga/Authentication/User/DbUserBackend.php:115
#: /vagrant/library/Icinga/Authentication/User/DbUserBackend.php:118 #: /vagrant/library/Icinga/Authentication/User/DbUserBackend.php:118
#: /vagrant/library/Icinga/Authentication/User/LdapUserBackend.php:255 #: /vagrant/library/Icinga/Authentication/User/LdapUserBackend.php:255
@ -2932,7 +2944,7 @@ msgstr "Benutzername"
#: /vagrant/application/forms/Navigation/NavigationConfigForm.php:613 #: /vagrant/application/forms/Navigation/NavigationConfigForm.php:613
#: /vagrant/application/forms/Security/RoleForm.php:117 #: /vagrant/application/forms/Security/RoleForm.php:117
#: /vagrant/application/views/scripts/role/list.phtml:23 #: /vagrant/application/views/scripts/role/list.phtml:23
#: /vagrant/library/Icinga/Web/Menu.php:288 #: /vagrant/library/Icinga/Web/Menu.php:289
msgid "Users" msgid "Users"
msgstr "Benutzer" msgstr "Benutzer"
@ -2967,6 +2979,17 @@ msgctxt "app.config.logging.level"
msgid "Warning" msgid "Warning"
msgstr "Warnung" msgstr "Warnung"
#: /vagrant/application/views/scripts/authentication/login.phtml:5
msgid ""
"Welcome to Icinga Web 2. For users of the screen reader Jaws full and "
"expectant compliant accessibility is possible only with use of the Firefox "
"browser. VoiceOver on Mac OS X is tested on Chrome, Safari and Firefox."
msgstr ""
"Willkommen zu Icinga Web 2. Für Nutzer des Screenreader JAWS ist die "
"vollständige und erwartungskonforme Zugänglichkeit nur mit Nutzung des "
"Browsers Firefox möglich. VoiceOver unter Mac OS X ist getestet mit Chrome, "
"Safari und Firefox."
#: /vagrant/application/views/scripts/dashboard/index.phtml:12 #: /vagrant/application/views/scripts/dashboard/index.phtml:12
msgid "Welcome to Icinga Web!" msgid "Welcome to Icinga Web!"
msgstr "Willkommen zu Icinga Web 2!" msgstr "Willkommen zu Icinga Web 2!"

View File

@ -2517,16 +2517,6 @@ msgstr "Tipo del backend gruppo utenti"
msgid "The unique name of this resource" msgid "The unique name of this resource"
msgstr "Nome univoco per questa risorsa" msgstr "Nome univoco per questa risorsa"
#: /usr/share/icingaweb2/application/forms/Navigation/NavigationItemForm.php:56
msgid ""
"The url of this navigation item. Leave blank if you only want the name being "
"displayed. For external urls, make sure to prepend an appropriate protocol "
"identifier (e.g. http://example.tld)"
msgstr ""
"La url di questo oggetto di navigazione. Lasciare vuoto se si vuole solo "
"mostrare il nome. Per url esterne ricordare di anteporre l'appropriato "
"identificatore di protocollo (ex. http://esempio.tld)"
#: /usr/share/icingaweb2/application/forms/Navigation/DashletForm.php:29 #: /usr/share/icingaweb2/application/forms/Navigation/DashletForm.php:29
msgid "" msgid ""
"The url to load in the dashlet. For external urls, make sure to prepend an " "The url to load in the dashlet. For external urls, make sure to prepend an "
@ -2696,7 +2686,7 @@ msgstr ""
#: /usr/share/php/Icinga/Web/Form.php:951 #: /usr/share/php/Icinga/Web/Form.php:951
#: /usr/share/php/Icinga/Web/Form/Decorator/Autosubmit.php:100 #: /usr/share/php/Icinga/Web/Form/Decorator/Autosubmit.php:100
msgid "" msgid ""
"Upon its value has changed, this field issues an automatic update of this " "Upon its value changing, this field issues an automatic update of this "
"page." "page."
msgstr "Cambiando questo valore la pagina verrà ricaricata automaticamente." msgstr "Cambiando questo valore la pagina verrà ricaricata automaticamente."

View File

@ -2417,16 +2417,6 @@ msgstr ""
msgid "The unique name of this resource" msgid "The unique name of this resource"
msgstr "Уникальное название ресурса" msgstr "Уникальное название ресурса"
#: /vagrant/application/forms/Navigation/NavigationItemForm.php:56
msgid ""
"The url of this navigation item. Leave blank if you only want the name being "
"displayed. For external urls, make sure to prepend an appropriate protocol "
"identifier (e.g. http://example.tld)"
msgstr ""
"URL адрес элемента навигации. Оставьте пустым, если необходимо только "
"отображение имени. Для внешних адресов убедитесь, что в начале ссылке указан "
"корректный протокол (например, http://example.tld)"
#: /vagrant/application/forms/Navigation/DashletForm.php:29 #: /vagrant/application/forms/Navigation/DashletForm.php:29
msgid "" msgid ""
"The url to load in the dashlet. For external urls, make sure to prepend an " "The url to load in the dashlet. For external urls, make sure to prepend an "
@ -2581,7 +2571,7 @@ msgstr ""
#: /vagrant/library/Icinga/Web/Form.php:951 #: /vagrant/library/Icinga/Web/Form.php:951
#: /vagrant/library/Icinga/Web/Form/Decorator/Autosubmit.php:100 #: /vagrant/library/Icinga/Web/Form/Decorator/Autosubmit.php:100
msgid "" msgid ""
"Upon its value has changed, this field issues an automatic update of this " "Upon its value changing, this field issues an automatic update of this "
"page." "page."
msgstr "В случае изменения значения поля страница будет обновлена" msgstr "В случае изменения значения поля страница будет обновлена"

View File

@ -2,13 +2,7 @@
<?= $tabs ?> <?= $tabs ?>
</div> </div>
<div id="about" class="content content-centered"> <div id="about" class="content content-centered">
<?= $this->img( <?= $this->img('img/icinga-logo-big-dark.png', null, array('width' => 320)) ?>
'img/logo_icinga_big_dark.png',
null,
array(
'width' => 320
)
) ?>
<dl class="name-value-list"> <dl class="name-value-list">
<?php if (isset($version['appVersion'])): ?> <?php if (isset($version['appVersion'])): ?>
<dt><?= $this->translate('Version') ?></dt> <dt><?= $this->translate('Version') ?></dt>
@ -82,7 +76,7 @@
null, null,
array( array(
'target' => '_blank', 'target' => '_blank',
'icon' => 'chat', 'icon' => 'chat-empty',
'title' => $this->translate('Support / Mailinglists') 'title' => $this->translate('Support / Mailinglists')
) )
) ?> ) ?>

View File

@ -0,0 +1,11 @@
<div class="controls">
<?= $tabs ?>
</div>
<div class="content">
<?php if (isset($changePasswordForm)): ?>
<h1><?= $this->translate('Account') ?></h1>
<?= $changePasswordForm ?>
<?php endif ?>
<h1><?= $this->translate('Preferences') ?></h1>
<?= $form ?>
</div>

View File

@ -0,0 +1,61 @@
<?php if (! $compact): ?>
<div class="controls">
<?= $tabs ?>
</div>
<?php endif ?>
<div class="content">
<?php if ($this->hasPermission('admin')) {
echo $this->qlink(
$this->translate('Create a New Announcement') ,
'announcements/new',
null,
array(
'class' => 'button-link',
'data-base-target' => '_next',
'icon' => 'plus',
'title' => $this->translate('Create a new announcement')
)
);
} ?>
<?php if (! $announcements->hasResult()): ?>
<p><?= $this->translate('No announcements found.') ?></p>
</div>
<?php return; endif ?>
<table data-base-target="_next" class="table-row-selectable common-table">
<thead>
<tr>
<th><?= $this->translate('Author') ?></th>
<th><?= $this->translate('Message') ?></th>
<th><?= $this->translate('Start') ?></th>
<th><?= $this->translate('End') ?></th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($announcements as $announcement): /** @var object $announcement */ ?>
<?php if ($this->hasPermission('admin')): ?>
<tr href="<?= $this->href('announcements/update', array('id' => $announcement->id)) ?>">
<?php else: ?>
<tr>
<?php endif ?>
<td><?= $this->escape($announcement->author) ?></td>
<td><?= $this->ellipsis($announcement->message, 100) ?></td>
<td><?= $this->formatDateTime($announcement->start->getTimestamp()) ?></td>
<td><?= $this->formatDateTime($announcement->end->getTimestamp()) ?></td>
<?php if ($this->hasPermission('admin')): ?>
<td class="icon-col"><?= $this->qlink(
null,
'announcements/remove',
array('id' => $announcement->id),
array(
'class' => 'action-link',
'icon' => 'cancel',
'title' => $this->translate('Remove this announcement')
)
) ?></td>
<?php endif ?>
</tr>
<?php endforeach ?>
</tbody>
</table>
</div>

View File

@ -1,5 +1,12 @@
<div id="login" class="centered-ghost"> <div id="login" class="centered-ghost">
<div class="centered-content" data-base-target="layout"> <div class="centered-content" data-base-target="layout">
<div role="status" class="sr-only">
<?= $this->translate(
'Welcome to Icinga Web 2. For users of the screen reader Jaws full and expectant compliant'
. ' accessibility is possible only with use of the Firefox browser. VoiceOver on Mac OS X is tested on'
. ' Chrome, Safari and Firefox.'
) ?>
</div>
<div id="icinga-logo" aria-hidden="true"></div> <div id="icinga-logo" aria-hidden="true"></div>
<?php if ($requiresSetup): ?> <?php if ($requiresSetup): ?>
<p class="config-note"><?= sprintf( <p class="config-note"><?= sprintf(

View File

@ -37,10 +37,20 @@
</td> </td>
<tr> <tr>
<th><?= $this->escape($this->translate('Version')) ?></th> <th><?= $this->escape($this->translate('Version')) ?></th>
<td><?= $this->escape($module->getVersion()) ?></td></tr> <td><?= $this->escape($module->getVersion()) ?></td>
</tr>
<?php if (isset($moduleGitCommitId) && $moduleGitCommitId !== false): ?>
<tr>
<th><?= $this->escape($this->translate('Git commit')) ?></th>
<td><?= $this->escape($moduleGitCommitId) ?></td>
</tr>
<?php endif ?>
<tr> <tr>
<th><?= $this->escape($this->translate('Description')) ?></th> <th><?= $this->escape($this->translate('Description')) ?></th>
<td><?= nl2br($this->escape($module->getDescription())) ?></td> <td>
<strong><?= $this->escape($module->getTitle()) ?></strong><br>
<?= nl2br($this->escape($module->getDescription())) ?>
</td>
</tr> </tr>
<tr> <tr>
<th><?= $this->escape($this->translate('Dependencies')) ?></th> <th><?= $this->escape($this->translate('Dependencies')) ?></th>

View File

@ -0,0 +1 @@
<?= $this->widget('announcements') ?>

View File

@ -6,11 +6,11 @@ $searchDashboard = new SearchDashboard();
$searchDashboard->setUser($this->Auth()->getUser()); $searchDashboard->setUser($this->Auth()->getUser());
if ($searchDashboard->search('dummy')->getPane('search')->hasDashlets()): ?> if ($searchDashboard->search('dummy')->getPane('search')->hasDashlets()): ?>
<form action="<?= $this->href('search') ?>" method="get" role="search"> <form action="<?= $this->href('search') ?>" method="get" role="search" class="search-control">
<input <input type="text" name="q" id="search" class="search search-input"
type="text" name="q" id="search" class="search" placeholder="<?= $this->translate('Search') ?> &hellip;" placeholder="<?= $this->translate('Search') ?> &hellip;"
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" required="required">
/> <button class="search-reset icon-cancel" type="reset"></button>
</form> </form>
<?php endif; ?> <?php endif; ?>
<?= $menuRenderer->setCssClass('primary-nav')->setElementTag('nav')->setHeading(t('Navigation')); ?> <?= $menuRenderer->setCssClass('primary-nav')->setElementTag('nav')->setHeading(t('Navigation')); ?>

View File

@ -1,6 +0,0 @@
<div class="controls">
<?= $tabs; ?>
</div>
<div class="content">
<?= $form; ?>
</div>

View File

@ -16,7 +16,7 @@
# along with this program; if not, write to the Free Software Foundation # along with this program; if not, write to the Free Software Foundation
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. # Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
import urllib2, json, sys, string import urllib2, json, sys, string, collections
from argparse import ArgumentParser from argparse import ArgumentParser
DESCRIPTION="update release changes" DESCRIPTION="update release changes"
@ -43,14 +43,14 @@ def format_header(text, lvl, ftype = ftype):
def format_logentry(log_entry, args = args, issue_url = ISSUE_URL): def format_logentry(log_entry, args = args, issue_url = ISSUE_URL):
if args.links: if args.links:
if args.html: if args.html:
return "<li> {0} <a href=\"{3}{1}\">{1}</a>: {2}</li>".format(log_entry[0], log_entry[1], log_entry[2], issue_url) return "<li> {0} <a href=\"{4}{1}\">{1}</a> ({2}): {3}</li>".format(log_entry[0], log_entry[1], log_entry[2], log_entry[3],issue_url)
else: else:
return "* {0} [{1}]({3}{1} \"{0} {1}\"): {2}".format(log_entry[0], log_entry[1], log_entry[2], issue_url) return "* {0} [{1}]({4}{1} \"{0} {1}\") ({2}): {3}".format(log_entry[0], log_entry[1], log_entry[2], log_entry[3], issue_url)
else: else:
if args.html: if args.html:
return "<li>%s %d: %s</li>" % log_entry return "<li>%s %d (%s): %s</li>" % log_entry
else: else:
return "* %s %d: %s" % log_entry return "* %s %d (%s): %s" % log_entry
def print_category(category, entries): def print_category(category, entries):
if len(entries) > 0: if len(entries) > 0:
@ -60,7 +60,10 @@ def print_category(category, entries):
if args.html: if args.html:
print "<ul>" print "<ul>"
for entry in sorted(entries): tmp_entries = collections.OrderedDict(sorted(entries.items()))
for cat, entry_list in tmp_entries.iteritems():
for entry in entry_list:
print format_logentry(entry) print format_logentry(entry)
if args.html: if args.html:
@ -108,9 +111,10 @@ if changes:
offset = 0 offset = 0
features = [] features = {}
bugfixes = [] bugfixes = {}
support = [] support = {}
category = ""
while True: while True:
# We could filter using &cf_13=1, however this doesn't currently work because the custom field isn't set # We could filter using &cf_13=1, however this doesn't currently work because the custom field isn't set
@ -135,14 +139,29 @@ while True:
if ignore_issue: if ignore_issue:
continue continue
entry = (issue["tracker"]["name"], issue["id"], issue["subject"].strip()) if "category" in issue:
category = str(issue["category"]["name"])
else:
category = "no category"
# the order is important for print_category()
entry = (issue["tracker"]["name"], issue["id"], category, issue["subject"].strip())
if issue["tracker"]["name"] == "Feature": if issue["tracker"]["name"] == "Feature":
features.append(entry) try:
features[category].append(entry)
except KeyError:
features[category] = [ entry ]
elif issue["tracker"]["name"] == "Bug": elif issue["tracker"]["name"] == "Bug":
bugfixes.append(entry) try:
bugfixes[category].append(entry)
except KeyError:
bugfixes[category] = [ entry ]
elif issue["tracker"]["name"] == "Support": elif issue["tracker"]["name"] == "Support":
support.append(entry) try:
support[category].append(entry)
except KeyError:
support[category] = [ entry ]
print_category("Feature", features) print_category("Feature", features)
print_category("Bugfixes", bugfixes) print_category("Bugfixes", bugfixes)

View File

@ -19,20 +19,20 @@ Most such actions (like rescheduling a check) can be done with just a single cli
Icinga Web 2 can be installed easily from packages from the official package repositories. Icinga Web 2 can be installed easily from packages from the official package repositories.
Setting it up is also easy with the web based setup wizard. Setting it up is also easy with the web based setup wizard.
See [here](installation#installation) for more information about the installation. See [here](02-Installation.md#installation) for more information about the installation.
## <a id="about-configuration"></a> Configuration ## <a id="about-configuration"></a> Configuration
Icinga Web 2 can be configured via the user interface and .ini files. Icinga Web 2 can be configured via the user interface and .ini files.
See [here](configuration#configuration) for more information about the configuration. See [here](03-Configuration.md#configuration) for more information about the configuration.
## <a id="about-authentication"></a> Authentication ## <a id="about-authentication"></a> Authentication
With Icinga Web 2 you can authenticate against relational databases, LDAP and more. With Icinga Web 2 you can authenticate against relational databases, LDAP and more.
These authentication methods can be easily configured (via the corresponding .ini file). These authentication methods can be easily configured (via the corresponding .ini file).
See [here](authentication#authentication) for more information about See [here](05-Authentication.md#authentication) for more information about
the different authentication methods available and how to configure them. the different authentication methods available and how to configure them.
## <a id="about-authorization"></a> Authorization ## <a id="about-authorization"></a> Authorization
@ -41,7 +41,7 @@ In Icinga Web 2 there are permissions and restrictions to allow and deny (respec
roles to view or to do certain things. roles to view or to do certain things.
These roles can be assigned to users and groups. These roles can be assigned to users and groups.
See [here](security#security) for more information about authorization See [here](06-Security.md#security) for more information about authorization
and how to configure roles. and how to configure roles.
## <a id="about-preferences"></a> User preferences ## <a id="about-preferences"></a> User preferences
@ -50,7 +50,7 @@ Besides the global configuration each user has individual configuration options
like the interface's language or the current timezone. like the interface's language or the current timezone.
They can be stored either in a database or in .ini files. They can be stored either in a database or in .ini files.
See [here](preferences#preferences) for more information about a user's preferences See [here](07-Preferences.md#preferences) for more information about a user's preferences
and how to configure their storage type. and how to configure their storage type.
## <a id="about-documentation"></a> Documentation ## <a id="about-documentation"></a> Documentation

View File

@ -15,6 +15,7 @@ thoroughly.
* Icinga 1.x w/ IDO; Icinga 2.x w/ IDO feature enabled * Icinga 1.x w/ IDO; Icinga 2.x w/ IDO feature enabled
* The IDO table prefix must be icinga_ which is the default * The IDO table prefix must be icinga_ which is the default
* MySQL or PostgreSQL PHP libraries * MySQL or PostgreSQL PHP libraries
* cURL PHP library when using the Icinga 2 API for transmitting external commands
### <a id="pagespeed-incompatibility"></a> PageSpeed Module Incompatibility ### <a id="pagespeed-incompatibility"></a> PageSpeed Module Incompatibility
@ -35,16 +36,16 @@ pagespeed Disallow "*/icingaweb2/*";
Below is a list of official package repositories for installing Icinga Web 2 for various operating systems. Below is a list of official package repositories for installing Icinga Web 2 for various operating systems.
Distribution | Repository | Distribution | Repository |
------------------------|--------------------------- | ------------- | ---------- |
Debian | [debmon](http://debmon.org/packages/debmon-wheezy/icingaweb2), [Icinga Repository](http://packages.icinga.org/debian/) | Debian | [Icinga Repository](http://packages.icinga.org/debian/) |
Ubuntu | [Icinga Repository](http://packages.icinga.org/ubuntu/) | Ubuntu | [Icinga Repository](http://packages.icinga.org/ubuntu/) |
RHEL/CentOS | [Icinga Repository](http://packages.icinga.org/epel/) | RHEL/CentOS | [Icinga Repository](http://packages.icinga.org/epel/) |
openSUSE | [Icinga Repository](http://packages.icinga.org/openSUSE/) | openSUSE | [Icinga Repository](http://packages.icinga.org/openSUSE/) |
SLES | [Icinga Repository](http://packages.icinga.org/SUSE/) | SLES | [Icinga Repository](http://packages.icinga.org/SUSE/) |
Gentoo | - | Gentoo | [Upstream](https://packages.gentoo.org/packages/www-apps/icingaweb2) |
FreeBSD | - | FreeBSD | [Upstream](http://portsmon.freebsd.org/portoverview.py?category=net-mgmt&portname=icingaweb2) |
ArchLinux | [Upstream](https://aur.archlinux.org/packages/icingaweb2) | ArchLinux | [Upstream](https://aur.archlinux.org/packages/icingaweb2) |
Packages for distributions other than the ones listed above may also be available. Packages for distributions other than the ones listed above may also be available.
Please contact your distribution packagers. Please contact your distribution packagers.
@ -52,23 +53,29 @@ Please contact your distribution packagers.
### <a id="package-repositories"></a> Setting up Package Repositories ### <a id="package-repositories"></a> Setting up Package Repositories
You need to add the Icinga repository to your package management configuration for installing Icinga Web 2. You need to add the Icinga repository to your package management configuration for installing Icinga Web 2.
Below is a list with examples for various distributions. If you've already configured your OS to use the Icinga repository for installing Icinga 2, you may skip this step.
Below is a list with **examples** for various distributions.
**Debian (debmon)**: **Debian Jessie**:
```
wget -O - http://debmon.org/debmon/repo.key 2>/dev/null | apt-key add -
echo 'deb http://debmon.org/debmon debmon-wheezy main' >/etc/apt/sources.list.d/debmon.list
apt-get update
```
**Ubuntu Trusty**:
``` ```
wget -O - http://packages.icinga.org/icinga.key | apt-key add - wget -O - http://packages.icinga.org/icinga.key | apt-key add -
add-apt-repository 'deb http://packages.icinga.org/ubuntu icinga-trusty main' echo 'deb http://packages.icinga.org/debian icinga-jessie main' >/etc/apt/sources.list.d/icinga.list
apt-get update apt-get update
``` ```
For other Ubuntu versions just replace trusty with your distribution\'s code name. > INFO
>
> For other Debian versions just replace jessie with your distribution's code name.
**Ubuntu Xenial**:
```
wget -O - http://packages.icinga.org/icinga.key | apt-key add -
add-apt-repository 'deb http://packages.icinga.org/ubuntu icinga-xenial main'
apt-get update
```
> INFO
>
> For other Ubuntu versions just replace xenial with your distribution's code name.
**RHEL and CentOS**: **RHEL and CentOS**:
``` ```
@ -108,14 +115,7 @@ The packages for RHEL/CentOS depend on other packages which are distributed as p
[EPEL repository](http://fedoraproject.org/wiki/EPEL). Please make sure to enable this repository by following [EPEL repository](http://fedoraproject.org/wiki/EPEL). Please make sure to enable this repository by following
[these instructions](http://fedoraproject.org/wiki/EPEL#How_can_I_use_these_extra_packages.3F). [these instructions](http://fedoraproject.org/wiki/EPEL#How_can_I_use_these_extra_packages.3F).
> Please note that installing Icinga Web 2 on **RHEL/CentOS 5** is not supported due to EOL versions of PHP and > Please note that installing Icinga Web 2 on **RHEL/CentOS 5** is not supported due to EOL versions of PHP and PostgreSQL.
> PostgreSQL.
#### <a id="package-repositories-wheezy-notes"></a> Debian wheezy Notes
The packages for Debian wheezy depend on other packages which are distributed as part of the
[wheezy-backports](http://backports.debian.org/) repository. Please make sure to enable this repository by following
[these instructions](http://backports.debian.org/Instructions/).
### <a id="installing-from-package-example"></a> Installing Icinga Web 2 ### <a id="installing-from-package-example"></a> Installing Icinga Web 2
@ -126,7 +126,6 @@ Below is a list with examples for various distributions. The additional package
``` ```
apt-get install icingaweb2 apt-get install icingaweb2
``` ```
For Debian wheezy please read the [package repositories notes](#package-repositories-wheezy-notes).
**RHEL, CentOS and Fedora**: **RHEL, CentOS and Fedora**:
``` ```
@ -449,24 +448,24 @@ path = "/var/run/icinga2/cmd/icinga2.cmd"
Finally visit Icinga Web 2 in your browser to login as `icingaadmin` user: `/icingaweb2`. Finally visit Icinga Web 2 in your browser to login as `icingaadmin` user: `/icingaweb2`.
# <a id="upgrading"></a> Upgrading Icinga Web 2 ## <a id="upgrading"></a> Upgrading Icinga Web 2
## <a id="upgrading-to-2.3.x"></a> Upgrading to Icinga Web 2 2.3.x ### <a id="upgrading-to-2.3.x"></a> Upgrading to Icinga Web 2 2.3.x
* Icinga Web 2 version 2.3.x does not introduce any backward incompatible change. * Icinga Web 2 version 2.3.x does not introduce any backward incompatible change.
## <a id="upgrading-to-2.2.0"></a> Upgrading to Icinga Web 2 2.2.0 ### <a id="upgrading-to-2.2.0"></a> Upgrading to Icinga Web 2 2.2.0
* The menu entry `Authorization` beneath `Config` has been renamed to `Authentication`. The role, user backend and user * The menu entry `Authorization` beneath `Config` has been renamed to `Authentication`. The role, user backend and user
group backend configuration which was previously found beneath `Authentication` has been moved to `Application`. group backend configuration which was previously found beneath `Authentication` has been moved to `Application`.
## <a id="upgrading-to-2.1.x"></a> Upgrading to Icinga Web 2 2.1.x ### <a id="upgrading-to-2.1.x"></a> Upgrading to Icinga Web 2 2.1.x
* Since Icinga Web 2 version 2.1.3 LDAP user group backends respect the configuration option `group_filter`. * Since Icinga Web 2 version 2.1.3 LDAP user group backends respect the configuration option `group_filter`.
Users who changed the configuration manually and used the option `filter` instead Users who changed the configuration manually and used the option `filter` instead
have to change it back to `group_filter`. have to change it back to `group_filter`.
## <a id="upgrading-to-2.0.0"></a> Upgrading to Icinga Web 2 2.0.0 ### <a id="upgrading-to-2.0.0"></a> Upgrading to Icinga Web 2 2.0.0
* Icinga Web 2 installations from package on RHEL/CentOS 7 now depend on `php-ZendFramework` which is available through * Icinga Web 2 installations from package on RHEL/CentOS 7 now depend on `php-ZendFramework` which is available through
the [EPEL repository](http://fedoraproject.org/wiki/EPEL). Before, Zend was installed as Icinga Web 2 vendor library the [EPEL repository](http://fedoraproject.org/wiki/EPEL). Before, Zend was installed as Icinga Web 2 vendor library
@ -488,7 +487,7 @@ Finally visit Icinga Web 2 in your browser to login as `icingaadmin` user: `/ici
**&lt;config-dir&gt;/preferences/&lt;username&gt;/config.ini**. **&lt;config-dir&gt;/preferences/&lt;username&gt;/config.ini**.
The content of the file remains unchanged. The content of the file remains unchanged.
## <a id="upgrading-to-rc1"></a> Upgrading to Icinga Web 2 Release Candidate 1 ### <a id="upgrading-to-rc1"></a> Upgrading to Icinga Web 2 Release Candidate 1
The first release candidate of Icinga Web 2 introduces the following non-backward compatible changes: The first release candidate of Icinga Web 2 introduces the following non-backward compatible changes:
@ -507,12 +506,12 @@ The first release candidate of Icinga Web 2 introduces the following non-backwar
predefined subset of filter columns. Please see the module's security predefined subset of filter columns. Please see the module's security
related documentation for more details. related documentation for more details.
## <a id="upgrading-to-beta3"></a> Upgrading to Icinga Web 2 Beta 3 ### <a id="upgrading-to-beta3"></a> Upgrading to Icinga Web 2 Beta 3
Because Icinga Web 2 Beta 3 does not introduce any backward incompatible change you don't have to change your Because Icinga Web 2 Beta 3 does not introduce any backward incompatible change you don't have to change your
configuration files after upgrading to Icinga Web 2 Beta 3. configuration files after upgrading to Icinga Web 2 Beta 3.
## <a id="upgrading-to-beta2"></a> Upgrading to Icinga Web 2 Beta 2 ### <a id="upgrading-to-beta2"></a> Upgrading to Icinga Web 2 Beta 2
Icinga Web 2 Beta 2 introduces access control based on roles for secured actions. If you've already set up Icinga Web 2, Icinga Web 2 Beta 2 introduces access control based on roles for secured actions. If you've already set up Icinga Web 2,
you are required to create the file **roles.ini** beneath Icinga Web 2's configuration directory with the following you are required to create the file **roles.ini** beneath Icinga Web 2's configuration directory with the following
@ -526,5 +525,6 @@ permissions = "*"
After please log out from Icinga Web 2 and log in again for having all permissions granted. After please log out from Icinga Web 2 and log in again for having all permissions granted.
If you delegated authentication to your web server using the `autologin` backend, you have to switch to the `external` If you delegated authentication to your web server using the `autologin` backend, you have to switch to the `external`
authentication backend to be able to log in again. The new name better reflects whats going on. A similar change authentication backend to be able to log in again. The new name better reflects
what's going on. A similar change
affects environments that opted for not storing preferences, your new backend is `none`. affects environments that opted for not storing preferences, your new backend is `none`.

View File

@ -1,15 +1,15 @@
# <a id="configuration"></a> Configuration # <a id="configuration"></a> Configuration
## Overview ## <a id="configuration-overview"></a> Overview
Apart from its web configuration capabilities, the local configuration is Apart from its web configuration capabilities, the local configuration is
stored in `/etc/icingaweb2` by default (depending on your config setup). stored in `/etc/icingaweb2` by default (depending on your config setup).
File/Directory | Description | File/Directory | Description/Purpose |
--------------------------------------------------------- | ------------------------------------------------- | ------------------- |
config.ini | General configuration (logging, preferences) | **config.ini** | general configuration (logging, preferences, etc.) |
[resources.ini](04-Ressources.md) | Global resources (Icinga Web 2 database for preferences and authentication, Icinga IDO database) | [**resources.ini**](04-Ressources.md) | global resources (Icinga Web 2 database for preferences and authentication, Icinga IDO database) |
roles.ini | User specific roles (e.g. `administrators`) and permissions | **roles.ini** | user specific roles (e.g. `administrators`) and permissions |
[authentication.ini](05-Authentication.md) | Authentication backends (e.g. database) | [**authentication.ini**](05-Authentication.md) | authentication backends (e.g. database) |
enabledModules | Contains symlinks to enabled modules | **enabledModules** | contains symlinks to enabled modules |
modules | Directory for module specific configuration | **modules** | directory for module specific configuration |

View File

@ -1,12 +1,12 @@
# <a id="resources"></a> Resources # <a id="resources"></a> Resources
The INI configuration file **config/resources.ini** contains information about data sources that can be referenced in other The configuration file `config/resources.ini` contains information about data sources that can be referenced in other
configuration files. This allows you to manage all data sources at one central place, avoiding the need to edit several configuration files. This allows you to manage all data sources at one central place, avoiding the need to edit several
different files, when the information about a data source changes. different files, when the information about a data source changes.
## <a id="resources-configuration"></a> Configuration ## <a id="resources-configuration"></a> Configuration
Each section in **config/resources.ini** represents a data source with the section name being the identifier used to Each section in `config/resources.ini` represents a data source with the section name being the identifier used to
reference this specific data source. Depending on the data source type, the sections define different directives. reference this specific data source. Depending on the data source type, the sections define different directives.
The available data source types are *db*, *ldap*, *ssh* and *livestatus* which will described in detail in the following The available data source types are *db*, *ldap*, *ssh* and *livestatus* which will described in detail in the following
paragraphs. paragraphs.
@ -16,19 +16,19 @@ paragraphs.
A Database resource defines a connection to a SQL databases which can contain users and groups A Database resource defines a connection to a SQL databases which can contain users and groups
to handle authentication and authorization, monitoring data or user preferences. to handle authentication and authorization, monitoring data or user preferences.
Directive | Description | Directive | Description |
----------------|------------ | ------------- | ----------- |
**type** | `db` | **type** | `db` |
**db** | Database management system. In most cases `mysql` or `pgsql`. | **db** | Database management system. In most cases `mysql` or `pgsql`. |
**host** | Connect to the database server on the given host. For using unix domain sockets, specify `localhost` for MySQL and the path to the unix domain socket directory for PostgreSQL. | **host** | Connect to the database server on the given host. For using unix domain sockets, specify `localhost` for MySQL and the path to the unix domain socket directory for PostgreSQL. |
**port** | Port number to use. Mandatory for connections to a PostgreSQL database. | **port** | Port number to use. Mandatory for connections to a PostgreSQL database. |
**username** | The username to use when connecting to the server. | **username** | The username to use when connecting to the server. |
**password** | The password to use when connecting to the server. | **password** | The password to use when connecting to the server. |
**dbname** | The database to use. | **dbname** | The database to use. |
**Example:** #### <a id="resources-configuration-database-example"></a> Example
```` ```
[icingaweb-mysql-tcp] [icingaweb-mysql-tcp]
type = db type = db
db = mysql db = mysql
@ -60,60 +60,45 @@ dbname = icingaweb
A LDAP resource represents a tree in a LDAP directory. LDAP is usually used for authentication and authorization. A LDAP resource represents a tree in a LDAP directory. LDAP is usually used for authentication and authorization.
Directive | Description | Directive | Description |
----------------|------------ | ----------------- | ----------- |
**type** | `ldap` | **type** | `ldap` |
**hostname** | Connect to the LDAP server on the given host. | **hostname** | Connect to the LDAP server on the given host. |
**port** | Port number to use for the connection. | **port** | Port number to use for the connection. |
**root_dn** | Root object of the tree, e.g. "ou=people,dc=icinga,dc=org" | **root_dn** | Root object of the tree, e.g. `ou=people,dc=icinga,dc=org` |
**bind_dn** | The user to use when connecting to the server. | **bind_dn** | The user to use when connecting to the server. |
**bind_pw** | The password to use when connecting to the server. | **bind_pw** | The password to use when connecting to the server. |
| **encryption** | Type of encryption to use: `none` (default), `starttls`, `ldaps`. |
**Example:** #### <a id="resources-configuration-ldap-example"></a> Example
```` ```
[ad] [ad]
type = ldap type = ldap
hostname = localhost hostname = localhost
port = 389 port = 389
root_dn = "ou=people,dc=icinga,dc=org" root_dn = "ou=people,dc=icinga,dc=org"
bind_dn = "cn=admin,ou=people,dc=icinga,dc=org" bind_dn = "cn=admin,ou=people,dc=icinga,dc=org"
bind_pw = admin` bind_pw = admin
```` ```
### <a id="resources-configuration-ssh"></a> SSH ### <a id="resources-configuration-ssh"></a> SSH
A SSH resource contains the information about the user and the private key location, which can be used for the key-based A SSH resource contains the information about the user and the private key location, which can be used for the key-based
ssh authentication. ssh authentication.
Directive | Description | Directive | Description |
--------------------|------------ | ----------------- | ----------- |
**type** | `ssh` | **type** | `ssh` |
**user** | The username to use when connecting to the server. | **user** | The username to use when connecting to the server. |
**private_key** | The path to the private key of the user. | **private_key** | The path to the private key of the user. |
**Example:** #### <a id="resources-configuration-ssh-example"></a> Example
```
````
[ssh] [ssh]
type = "ssh" type = "ssh"
user = "ssh-user" user = "ssh-user"
private_key = "/etc/icingaweb2/ssh/ssh-user" private_key = "/etc/icingaweb2/ssh/ssh-user"
```` ```
### <a id="resources-configuration-livestatus"></a> Livestatus
A Livestatus resource represents the location of a Livestatus socket which is used for fetching monitoring data.
Directive | Description
----------------|------------
**type** | `livestatus`
**socket** | Location of the Livestatus socket. Either a path to a local Livestatus socket or a path to a remote Livestatus socket in the format `tcp://<host>:<port>`.
**Example:**
````
[livestatus]
type = livestatus
socket = /var/run/icinga2/cmd/livestatus
````

View File

@ -68,13 +68,13 @@ Active Directory or LDAP configuration method.
### <a id="authentication-configuration-ldap-authentication"></a> LDAP ### <a id="authentication-configuration-ldap-authentication"></a> LDAP
Directive | Description | Directive | Description |
------------------------|------------ | ------------------------- | ----------- |
**backend** | `ldap` | **backend** | `ldap` |
**resource** | The name of the LDAP resource defined in [resources.ini](04-Resources.md#resources). | **resource** | The name of the LDAP resource defined in [resources.ini](04-Resources.md#resources). |
**user_class** | LDAP user class. | **user_class** | LDAP user class. |
**user_name_attribute** | LDAP attribute which contains the username. | **user_name_attribute** | LDAP attribute which contains the username. |
**filter** | LDAP search filter. | **filter** | LDAP search filter. |
**Example:** **Example:**
@ -93,10 +93,10 @@ with Icinga Web 2 (e.g. an alias) no matter what the primary user id might actua
### <a id="authentication-configuration-ad-authentication"></a> Active Directory ### <a id="authentication-configuration-ad-authentication"></a> Active Directory
Directive | Description | Directive | Description |
------------------------|------------ | ------------- | ----------- |
**backend** | `msldap` | **backend** | `msldap` |
**resource** | The name of the LDAP resource defined in [resources.ini](04-Resources.md#resources). | **resource** | The name of the LDAP resource defined in [resources.ini](04-Resources.md#resources). |
**Example:** **Example:**
@ -112,10 +112,10 @@ If you want to authenticate against a MySQL or a PostgreSQL database, you have t
[database resource](04-Resources.md#resources-configuration-database) which will be referenced as data source for the database [database resource](04-Resources.md#resources-configuration-database) which will be referenced as data source for the database
authentication method. authentication method.
Directive | Description | Directive | Description |
------------------------|------------ | ------------------------| ----------- |
**backend** | `db` | **backend** | `db` |
**resource** | The name of the database resource defined in [resources.ini](04-Resources.md#resources). | **resource** | The name of the database resource defined in [resources.ini](04-Resources.md#resources). |
**Example:** **Example:**

View File

@ -11,14 +11,14 @@ environment they are in charge of.
This chapter will describe how to do the security configuration of Icinga Web 2 This chapter will describe how to do the security configuration of Icinga Web 2
and how to apply permissions and restrictions to users or groups of users. and how to apply permissions and restrictions to users or groups of users.
## Basics ## <a id="security-basics"></a> Basics
Icinga Web 2 access control is done by defining **roles** that associate permissions Icinga Web 2 access control is done by defining **roles** that associate permissions
and restrictions with **users** and **groups**. There are two general kinds of and restrictions with **users** and **groups**. There are two general kinds of
things to which access can be managed: actions and objects. things to which access can be managed: actions and objects.
### Actions ### <a id="security-basics-actions"></a>Actions
Actions are all the things an Icinga Web 2 user can do, like changing a certain configuration, Actions are all the things an Icinga Web 2 user can do, like changing a certain configuration,
changing permissions or sending a command to the Icinga instance through the Icinga command pipe. changing permissions or sending a command to the Icinga instance through the Icinga command pipe.
@ -28,7 +28,7 @@ A permission is a simple list of identifiers of actions a user is
allowed to do. Permissions are described in greater detail in the allowed to do. Permissions are described in greater detail in the
section [Permissions](#permissions). section [Permissions](#permissions).
### Objects ### <a id="security-basics-objects"></a> Objects
There are all kinds of different objects in Icinga Web 2: Hosts, Services, Notifications, Downtimes and Events. There are all kinds of different objects in Icinga Web 2: Hosts, Services, Notifications, Downtimes and Events.
@ -37,7 +37,7 @@ By default, a user can **see everything**, but it is possible to **explicitly re
Restrictions are complex filter queries that describe what objects should be displayed to a user. Restrictions are described Restrictions are complex filter queries that describe what objects should be displayed to a user. Restrictions are described
in greater detail in the section [Restrictions](#restrictions). in greater detail in the section [Restrictions](#restrictions).
### Users ### <a id="security-basics-users"></a>Users
Anyone who can **login** to Icinga Web 2 is considered a user and can be referenced to by the Anyone who can **login** to Icinga Web 2 is considered a user and can be referenced to by the
**user name** used during login. **user name** used during login.
@ -55,13 +55,13 @@ an **authentication backend**. For extended information on setting up authentica
backend to fetch users and groups from, which must be configured separately. backend to fetch users and groups from, which must be configured separately.
</div> </div>
#### Managing Users #### <a id="security-basics-users-managing"></a>Managing Users
When using a [Database When using a [Database
as authentication backend](05-Authentication.md#authentication-configuration-db-authentication), it is possible to create, add and delete users directly in the frontend. This configuration as authentication backend](05-Authentication.md#authentication-configuration-db-authentication), it is possible to create, add and delete users directly in the frontend. This configuration
can be found at **Configuration > Authentication > Users **. can be found at **Configuration > Authentication > Users **.
### Groups ### <a id="security-basics-groups"></a>Groups
If there is a big amount of users to manage, it would be tedious to specify each user If there is a big amount of users to manage, it would be tedious to specify each user
separately when regularly referring to the same group of users. Because of that, it is possible to group users. separately when regularly referring to the same group of users. Because of that, it is possible to group users.
@ -72,13 +72,13 @@ Like users, groups are identified solely by their **name** that is provided by
please read the chapter [Authentication](05-Authentication.md#authentication). please read the chapter [Authentication](05-Authentication.md#authentication).
#### Managing Groups #### <a id="security-basics-groups-managing"></a>Managing Groups
When using a [Database as an authentication backend](05-Authentication.md#authentication-configuration-db-authentication), When using a [Database as an authentication backend](05-Authentication.md#authentication-configuration-db-authentication),
it is possible to manage groups and group memberships directly in the frontend. This configuration it is possible to manage groups and group memberships directly in the frontend. This configuration
can be found at **Configuration > Authentication > Groups **. can be found at **Configuration > Authentication > Groups **.
## Roles ## <a id="security-roles"></a>Roles
A role defines a set of **permissions** and **restrictions** and assigns A role defines a set of **permissions** and **restrictions** and assigns
those to **users** and **groups**. For example, a role **admins** could define that certain those to **users** and **groups**. For example, a role **admins** could define that certain
@ -91,17 +91,16 @@ and restrictions of the user itself and all the groups the user is member of. Pe
be simply added up, while restrictions follow a slighty more complex pattern, that is described be simply added up, while restrictions follow a slighty more complex pattern, that is described
in the section [Stacking Filters](#stacking-filters). in the section [Stacking Filters](#stacking-filters).
### Configuration ### <a id="security-roles-configuration"></a>Configuration
Roles can be changed either through the icingaweb2 interface, by navigation Roles can be changed either through the icingaweb2 interface, by navigation
to the page **Configuration > Authentication > Roles**, or through editing the to the page **Configuration > Authentication > Roles**, or through editing the
configuration file: configuration file:
/etc/icingaweb2/roles.ini /etc/icingaweb2/roles.ini
#### Introducing Example #### <a id="security-roles-configuration-example"></a>Introducing Example
To get you a quick start, here is an example of what a role definition could look like: To get you a quick start, here is an example of what a role definition could look like:
@ -125,12 +124,12 @@ Each role is defined as a section, with the name of the role as section name. Th
attributes can be defined for each role in a default Icinga Web 2 installation: attributes can be defined for each role in a default Icinga Web 2 installation:
Directive | Description | Directive | Description |
---------------------------|----------------------------------------------------------------------------- | ----------------------------- | ----------- |
users | A comma-separated list of user **user names** that are affected by this role | **users** | a comma-separated list of user **user names** that are affected by this role |
groups | A comma-separated list of **group names** that are affected by this role | **groups** | a comma-separated list of **group names** that are affected by this role |
permissions | A comma-separated list of **permissions** granted by this role | **permissions** | a comma-separated list of **permissions** granted by this role |
monitoring/filter/objects | A **filter expression** that restricts the access to services and hosts | **monitoring/filter/objects** | a **filter expression** that restricts the access to services and hosts |
@ -154,17 +153,17 @@ a module permission in the format `module/<moduleName>` for each installed modul
When multiple roles assign permissions to the same user (either directly or indirectly When multiple roles assign permissions to the same user (either directly or indirectly
through a group) all permissions are added together to get the users actual permission set. through a group) all permissions are added together to get the users actual permission set.
### Global Permissions ### <a id="permissions-global"></a> Global Permissions
Name | Permits | Name | Permits |
--------------------------|-------------------------------------------------------- | ----------------------------- | ------------ |
* | Allow everything, including module-specific permissions | **\*** | allow everything, including module-specific permissions |
config/* | Allow all configuration actions | **config/\*** | allow all configuration actions |
config/modules | Allow enabling or disabling modules | **config/modules** | allow enabling or disabling modules |
module/&lt;moduleName&gt; | Allow access to module &lt;moduleName&gt; | **module/&lt;moduleName&gt;** | allow access to module &lt;moduleName&gt; |
### Monitoring Module Permissions ### <a id="permissions-module"></a> Monitoring Module Permissions
The built-in monitoring module defines an additional set of permissions, that The built-in monitoring module defines an additional set of permissions, that
is described in detail in the monitoring module documentation. is described in detail in the monitoring module documentation.
@ -183,7 +182,7 @@ in a default installation, is the `monitoring/filter/objects` directive, defined
that can be used to apply filter to hosts and services. This directive was previously that can be used to apply filter to hosts and services. This directive was previously
mentioned in the section [Syntax](#syntax). mentioned in the section [Syntax](#syntax).
### Filter Expressions ### <a id="restrictions-filter"></a>Filter Expressions
Filters operate on columns. A complete list of all available filter columns on hosts and services can be found in Filters operate on columns. A complete list of all available filter columns on hosts and services can be found in
the monitoring module documentation. the monitoring module documentation.
@ -235,7 +234,7 @@ expression:
As a result, a user is be able to see hosts that are matched by **ANY** of As a result, a user is be able to see hosts that are matched by **ANY** of
the filter expressions. The following examples will show the usefulness of this behavior: the filter expressions. The following examples will show the usefulness of this behavior:
#### Example 1: Negation #### <a id="restrictions-filter-example1"></a>Example 1: Negation
[winadmin] [winadmin]
groups = "windows-admins" groups = "windows-admins"
@ -251,7 +250,7 @@ Will only match hosts and services whose host name does **not** contain **win**
Notice that because of the behavior of two stacking filters, a user that is member of **windows-admins** and **web-admins**, will now be able to see both, Windows and non-Windows hosts and services. Notice that because of the behavior of two stacking filters, a user that is member of **windows-admins** and **web-admins**, will now be able to see both, Windows and non-Windows hosts and services.
#### Example 2: Hostgroups #### <a id="restrictions-filter-example2"></a>Example 2: Hostgroups
[unix-server] [unix-server]
groups = "unix-admins" groups = "unix-admins"

View File

@ -22,7 +22,7 @@ For storing preferences in INI files you have to add the following section to th
``` ```
[preferences] [preferences]
type = ini type = ini
```` ```
### <a id="preferences-configuration-db"></a> Store Preferences in a Database ### <a id="preferences-configuration-db"></a> Store Preferences in a Database
@ -30,10 +30,10 @@ In order to be more flexible in distributed setups you can store preferences in
For storing preferences in a database, you have to define a [database resource](04-Resources.md#resources-configuration-database) For storing preferences in a database, you have to define a [database resource](04-Resources.md#resources-configuration-database)
which will be referenced as resource for the preferences storage. which will be referenced as resource for the preferences storage.
Directive | Description | Directive | Description |
------------------------|------------ | ------------- | ----------- |
**type** | `db` | **type** | `db` |
**resource** | The name of the database resource defined in [resources.ini](04-Resources.md#resources). | **resource** | The name of the database resource defined in [resources.ini](04-Resources.md#resources). |
**Example:** **Example:**

View File

@ -1,6 +1,10 @@
# Vagrant # <a id="vagrant"></a> Vagrant
## Requirements This chapter shows how to set up and use our [Icinga Vagrant
boxes](https://github.com/icinga/icinga-vagrant) that we've created for
development, tests and demo cases.
## <a id="vagrant-requirements"></a>Requirements
* Vagrant &gt;= version 1.5 * Vagrant &gt;= version 1.5
* VirtualBox or Parallels Desktop * VirtualBox or Parallels Desktop
@ -13,7 +17,7 @@ Parallels requires the additional provider plugin
$ vagrant plugin install vagrant-parallels $ vagrant plugin install vagrant-parallels
## General ## <a id="vagrant-general"></a>General
The Icinga Web 2 project ships with a Vagrant virtual machine that integrates The Icinga Web 2 project ships with a Vagrant virtual machine that integrates
the source code with various services and example data in a controlled the source code with various services and example data in a controlled
@ -21,9 +25,9 @@ environment. This enables developers and users to test Livestatus,
MySQL and PostgreSQL backends as well as the LDAP authentication. All you MySQL and PostgreSQL backends as well as the LDAP authentication. All you
have to do is install Vagrant and run: have to do is install Vagrant and run:
```` ```
vagrant up vagrant up
```` ```
> **Note:** The first boot of the vm takes a fairly long time because > **Note:** The first boot of the vm takes a fairly long time because
> you'll download a plain CentOS base box and Vagrant will automatically > you'll download a plain CentOS base box and Vagrant will automatically
@ -31,7 +35,7 @@ vagrant up
After you should be able to browse [localhost:8080/icingaweb2](http://localhost:8080/icingaweb2). After you should be able to browse [localhost:8080/icingaweb2](http://localhost:8080/icingaweb2).
## Log into Icinga Web 2 ## <a id="vagrant-login"></a>Log into Icinga Web 2
Both LDAP and a MySQL are configured as authentication backend. Please use one of the following login credentials: Both LDAP and a MySQL are configured as authentication backend. Please use one of the following login credentials:
@ -47,11 +51,11 @@ Both LDAP and a MySQL are configured as authentication backend. Please use one o
## Testing the Source Code ## <a id="vagrant-testing"></a>Testing the Source Code
All software required to run tests is installed in the virtual machine. All software required to run tests is installed in the virtual machine.
In order to run all tests you have to execute the following command: In order to run all tests you have to execute the following command:
```` ```
vagrant ssh -c "icingacli test php unit" vagrant ssh -c "icingacli test php unit"
```` ```

View File

@ -3,7 +3,7 @@
%define revision 1 %define revision 1
Name: icingaweb2 Name: icingaweb2
Version: 2.3.2 Version: 2.3.4
Release: %{revision}%{?dist} Release: %{revision}%{?dist}
Summary: Icinga Web 2 Summary: Icinga Web 2
Group: Applications/System Group: Applications/System
@ -41,11 +41,11 @@ Requires: apache2-mod_php5
%{?suse_version:Requires(pre): pwdutils} %{?suse_version:Requires(pre): pwdutils}
Requires: %{name}-common = %{version}-%{release} Requires: %{name}-common = %{version}-%{release}
Requires: php-Icinga = %{version}-%{release} Requires: php-Icinga = %{version}-%{release}
Requires: %{name}-vendor-dompdf Requires: %{name}-vendor-dompdf = 0.7.0-1%{?dist}
Requires: %{name}-vendor-HTMLPurifier Requires: %{name}-vendor-HTMLPurifier = 4.8.0-1%{?dist}
Requires: %{name}-vendor-JShrink Requires: %{name}-vendor-JShrink = 1.1.0-1%{?dist}
Requires: %{name}-vendor-lessphp Requires: %{name}-vendor-lessphp = 0.4.0-1%{?dist}
Requires: %{name}-vendor-Parsedown Requires: %{name}-vendor-Parsedown = 1.6.0-1%{?dist}
%description %description
@ -106,7 +106,7 @@ Icinga CLI
%package vendor-dompdf %package vendor-dompdf
Version: 0.6.2 Version: 0.7.0
Release: 1%{?dist} Release: 1%{?dist}
Summary: Icinga Web 2 vendor library dompdf Summary: Icinga Web 2 vendor library dompdf
Group: Development/Libraries Group: Development/Libraries
@ -118,7 +118,7 @@ Icinga Web 2 vendor library dompdf
%package vendor-HTMLPurifier %package vendor-HTMLPurifier
Version: 4.7.0 Version: 4.8.0
Release: 1%{?dist} Release: 1%{?dist}
Summary: Icinga Web 2 vendor library HTMLPurifier Summary: Icinga Web 2 vendor library HTMLPurifier
Group: Development/Libraries Group: Development/Libraries
@ -130,7 +130,7 @@ Icinga Web 2 vendor library HTMLPurifier
%package vendor-JShrink %package vendor-JShrink
Version: 1.0.1 Version: 1.1.0
Release: 1%{?dist} Release: 1%{?dist}
Summary: Icinga Web 2 vendor library JShrink Summary: Icinga Web 2 vendor library JShrink
Group: Development/Libraries Group: Development/Libraries
@ -154,7 +154,7 @@ Icinga Web 2 vendor library lessphp
%package vendor-Parsedown %package vendor-Parsedown
Version: 1.0.0 Version: 1.6.0
Release: 1%{?dist} Release: 1%{?dist}
Summary: Icinga Web 2 vendor library Parsedown Summary: Icinga Web 2 vendor library Parsedown
Group: Development/Libraries Group: Development/Libraries

View File

@ -487,6 +487,7 @@ abstract class ApplicationBootstrap
case E_NOTICE: case E_NOTICE:
case E_WARNING: case E_WARNING:
case E_STRICT: case E_STRICT:
case E_RECOVERABLE_ERROR:
throw new ErrorException($errstr, 0, $errno, $errfile, $errline); throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
} }
return false; // Continue with the normal error handler return false; // Continue with the normal error handler

View File

@ -4,7 +4,8 @@
namespace Icinga\Application; namespace Icinga\Application;
use Exception; use Exception;
use Icinga\Application\Logger; use Icinga\Authentication\Auth;
use Icinga\Application\Modules\Manager;
use Icinga\Exception\ProgrammingError; use Icinga\Exception\ProgrammingError;
/** /**
@ -148,6 +149,46 @@ class Hook
); );
} }
/**
* Extract the Icinga module name from a given namespaced class name
*
* Does no validation, prefix must have been checked before
*
* Shameless copy of ClassLoader::extractModuleName()
*
* @param string $class The hook's class path
*
* @return string
*/
protected static function extractModuleName($class)
{
return lcfirst(
substr(
$class,
ClassLoader::MODULE_PREFIX_LENGTH,
strpos(
$class,
ClassLoader::NAMESPACE_SEPARATOR,
ClassLoader::MODULE_PREFIX_LENGTH + 1
) - ClassLoader::MODULE_PREFIX_LENGTH
)
);
}
/**
* Return whether the user has the permission to access the module which provides the given hook
*
* @param string $class The hook's class path
*
* @return bool
*/
protected static function hasPermission($class)
{
return Auth::getInstance()->hasPermission(
Manager::MODULE_PERMISSION_NS . self::extractModuleName($class)
);
}
/** /**
* Test for a valid class name * Test for a valid class name
* *
@ -218,12 +259,14 @@ class Hook
} }
foreach (self::$hooks[$name] as $key => $hook) { foreach (self::$hooks[$name] as $key => $hook) {
if (self::hasPermission($key)) {
if (self::createInstance($name, $key) === null) { if (self::createInstance($name, $key) === null) {
return array(); return array();
} }
} }
}
return self::$instances[$name]; return isset(self::$instances[$name]) ? self::$instances[$name] : array();
} }
/** /**
@ -238,7 +281,11 @@ class Hook
$name = self::normalizeHookName($name); $name = self::normalizeHookName($name);
if (self::has($name)) { if (self::has($name)) {
return self::createInstance($name, key(self::$hooks[$name])); foreach (self::$hooks[$name] as $key => $hook) {
if (self::hasPermission($key)) {
return self::createInstance($name, $key);
}
}
} }
} }

View File

@ -6,6 +6,7 @@ namespace Icinga\Application\Logger\Writer;
use Icinga\Data\ConfigObject; use Icinga\Data\ConfigObject;
use Icinga\Application\Logger; use Icinga\Application\Logger;
use Icinga\Application\Logger\LogWriter; use Icinga\Application\Logger\LogWriter;
use Icinga\Exception\ConfigurationError;
/** /**
* Log to the syslog service * Log to the syslog service
@ -32,7 +33,15 @@ class SyslogWriter extends LogWriter
* @var array * @var array
*/ */
public static $facilities = array( public static $facilities = array(
'user' => LOG_USER 'user' => LOG_USER,
'local0' => LOG_LOCAL0,
'local1' => LOG_LOCAL1,
'local2' => LOG_LOCAL2,
'local3' => LOG_LOCAL3,
'local4' => LOG_LOCAL4,
'local5' => LOG_LOCAL5,
'local6' => LOG_LOCAL6,
'local7' => LOG_LOCAL7
); );
/** /**
@ -55,7 +64,16 @@ class SyslogWriter extends LogWriter
public function __construct(ConfigObject $config) public function __construct(ConfigObject $config)
{ {
$this->ident = $config->get('application', 'icingaweb2'); $this->ident = $config->get('application', 'icingaweb2');
$this->facility = static::$facilities['user'];
$configuredFacility = $config->get('facility', 'user');
if (! isset(static::$facilities[$configuredFacility])) {
throw new ConfigurationError(
'Invalid logging facility: "%s" (expected one of: %s)',
$configuredFacility,
implode(', ', array_keys(static::$facilities))
);
}
$this->facility = static::$facilities[$configuredFacility];
} }
/** /**

View File

@ -249,6 +249,15 @@ class Manager
); );
} }
if (strtolower(substr($name, 0, 18)) === 'icingaweb2-module-') {
throw new ConfigurationError(
'Cannot enable module "%s": Directory name does not match the module\'s name.'
. ' Please rename the module to "%s" before enabling.',
$name,
substr($name, 18)
);
}
clearstatcache(true); clearstatcache(true);
$target = $this->installedBaseDirs[$name]; $target = $this->installedBaseDirs[$name];
$link = $this->enableDir . DIRECTORY_SEPARATOR . $name; $link = $this->enableDir . DIRECTORY_SEPARATOR . $name;

View File

@ -362,25 +362,28 @@ class Module
public function getMenu() public function getMenu()
{ {
$this->launchConfigScript(); $this->launchConfigScript();
return $this->createMenu($this->menuItems); return Navigation::fromArray($this->createMenu($this->menuItems));
} }
/** /**
* Create and return a new navigation for the given menu items * Create and return an array structure for the given menu items
* *
* @param MenuItemContainer[] $items * @param MenuItemContainer[] $items
* *
* @return Navigation * @return array
*/ */
private function createMenu(array $items) private function createMenu(array $items)
{ {
$navigation = new Navigation(); $navigation = array();
foreach ($items as $item) { foreach ($items as $item) {
/** @var MenuItemContainer $item */ /** @var MenuItemContainer $item */
$navigationItem = $navigation->createItem($item->getName(), $item->getProperties()); $properties = $item->getProperties();
$navigationItem->setChildren($this->createMenu($item->getChildren())); $properties['children'] = $this->createMenu($item->getChildren());
$navigationItem->setLabel($this->translate($item->getName())); if (! isset($properties['label'])) {
$navigation->addItem($navigationItem); $properties['label'] = $this->translate($item->getName());
}
$navigation[$item->getName()] = $properties;
} }
return $navigation; return $navigation;
@ -671,6 +674,8 @@ class Module
$metadata->description .= $line; $metadata->description .= $line;
continue; continue;
} }
} elseif (empty($line)) {
continue;
} }
list($key, $val) = preg_split('/:\s+/', $line, 2); list($key, $val) = preg_split('/:\s+/', $line, 2);

View File

@ -8,7 +8,7 @@ namespace Icinga\Application;
*/ */
class Version class Version
{ {
const VERSION = '2.3.2'; const VERSION = '2.3.4';
/** /**
* Get the version of this instance of Icinga Web 2 * Get the version of this instance of Icinga Web 2
@ -25,24 +25,38 @@ class Version
} }
} }
$gitDir = Icinga::app()->getBaseDir('.git'); $gitCommitId = static::getGitHead(Icinga::app()->getBaseDir());
$gitHead = @file_get_contents($gitDir . DIRECTORY_SEPARATOR . 'HEAD'); if ($gitCommitId !== false) {
if (false !== $gitHead) { $version['gitCommitID'] = $gitCommitId;
$matches = array();
if (@preg_match('/(?<!.)ref:\s+(.+?)$/ms', $gitHead, $matches)) {
$gitCommitID = @file_get_contents($gitDir . DIRECTORY_SEPARATOR . $matches[1]);
} else {
$gitCommitID = $gitHead;
}
if (false !== $gitCommitID) {
$matches = array();
if (@preg_match('/(?<!.)(?P<gitCommitID>[0-9a-f]+)$/ms', $gitCommitID, $matches)) {
return array_merge($version, $matches);
}
}
} }
return $version; return $version;
} }
/**
* Get the current commit of the Git repository in the given path
*
* @param string $repo Path to the Git repository
* @param bool $bare Whether the Git repository is bare
*
* @return string|bool False if not available
*/
public static function getGitHead($repo, $bare = false)
{
if (! $bare) {
$repo .= '/.git';
}
$head = @file_get_contents($repo . '/HEAD');
if ($head !== false) {
if (preg_match('/^ref: (.+)/', $head, $matches)) {
return @file_get_contents($repo . '/' . $matches[1]);
}
return $head;
}
return false;
}
} }

View File

@ -304,7 +304,12 @@ class Web extends EmbeddedWeb
'about' => array( 'about' => array(
'label' => t('About'), 'label' => t('About'),
'url' => 'about', 'url' => 'about',
'priority' => 701 'priority' => 700
),
'announcements' => array(
'label' => t('Announcements'),
'url' => 'announcements',
'priority' => 710
) )
) )
), ),
@ -346,10 +351,10 @@ class Web extends EmbeddedWeb
'icon' => 'user', 'icon' => 'user',
'priority' => 900, 'priority' => 900,
'children' => array( 'children' => array(
'preferences' => array( 'account' => array(
'label' => t('Preferences'), 'label' => t('My Account'),
'priority' => 100, 'priority' => 100,
'url' => 'preference' 'url' => 'account'
), ),
'logout' => array( 'logout' => array(
'label' => t('Logout'), 'label' => t('Logout'),
@ -366,7 +371,7 @@ class Web extends EmbeddedWeb
'label' => t('Application Log'), 'label' => t('Application Log'),
'url' => 'list/applicationlog', 'url' => 'list/applicationlog',
'permission' => 'application/log', 'permission' => 'application/log',
'priority' => 710 'priority' => 900
); );
} }
} else { } else {
@ -411,6 +416,9 @@ class Web extends EmbeddedWeb
private function setupUser() private function setupUser()
{ {
$auth = Auth::getInstance(); $auth = Auth::getInstance();
if (! $this->request->isXmlHttpRequest() && $this->request->isApiRequest() && ! $auth->isAuthenticated()) {
$auth->authHttp();
}
if ($auth->isAuthenticated()) { if ($auth->isAuthenticated()) {
$user = $auth->getUser(); $user = $auth->getUser();
$this->getRequest()->setUser($user); $this->getRequest()->setUser($user);

View File

@ -79,19 +79,18 @@ class Auth
} }
/** /**
* Whether the user is authenticated * Get whether the user is authenticated
*
* @param bool $ignoreSession True to prevent session authentication
* *
* @return bool * @return bool
*/ */
public function isAuthenticated($ignoreSession = false) public function isAuthenticated()
{ {
if ($this->user === null && ! $ignoreSession) { if ($this->user !== null) {
$this->authenticateFromSession(); return true;
} }
$this->authenticateFromSession();
if ($this->user === null && ! $this->authExternal()) { if ($this->user === null && ! $this->authExternal()) {
return $this->authHttp(); return false;
} }
return true; return true;
} }
@ -275,15 +274,12 @@ class Auth
* *
* @return bool * @return bool
*/ */
protected function authHttp() public function authHttp()
{ {
$request = $this->getRequest(); $request = $this->getRequest();
if ($request->isXmlHttpRequest() || ! $request->isApiRequest()) {
return false;
}
$header = $request->getHeader('Authorization'); $header = $request->getHeader('Authorization');
if (empty($header)) { if (empty($header)) {
$this->challengeHttp(); return false;
} }
list($scheme) = explode(' ', $header, 2); list($scheme) = explode(' ', $header, 2);
if ($scheme !== 'Basic') { if ($scheme !== 'Basic') {
@ -294,7 +290,7 @@ class Auth
$credentials = array_filter(explode(':', $credentials, 2)); $credentials = array_filter(explode(':', $credentials, 2));
if (count($credentials) !== 2) { if (count($credentials) !== 2) {
// Deny empty username and/or password // Deny empty username and/or password
$this->challengeHttp(); return false;
} }
$user = new User($credentials[0]); $user = new User($credentials[0]);
$password = $credentials[1]; $password = $credentials[1];
@ -303,7 +299,7 @@ class Auth
$user->setIsHttpUser(true); $user->setIsHttpUser(true);
return true; return true;
} else { } else {
$this->challengeHttp(); return false;
} }
} }
@ -312,7 +308,7 @@ class Auth
* *
* Sends the response w/ the 401 Unauthorized status code and WWW-Authenticate header. * Sends the response w/ the 401 Unauthorized status code and WWW-Authenticate header.
*/ */
protected function challengeHttp() public function challengeHttp()
{ {
$response = $this->getResponse(); $response = $this->getResponse();
$response->setHttpResponseCode(401); $response->setHttpResponseCode(401);

View File

@ -118,6 +118,8 @@ class AuthChain implements Authenticatable, Iterator
continue; continue;
} }
if ($authenticated) { if ($authenticated) {
$user->setAdditional('backend_name', $backend->getName());
$user->setAdditional('backend_type', $this->config->current()->get('backend'));
return true; return true;
} }
} }

View File

@ -3,6 +3,7 @@
namespace Icinga\Authentication\User; namespace Icinga\Authentication\User;
use Icinga\Application\Logger;
use Icinga\Data\ConfigObject; use Icinga\Data\ConfigObject;
use Icinga\User; use Icinga\User;
@ -11,6 +12,13 @@ use Icinga\User;
*/ */
class ExternalBackend implements UserBackendInterface class ExternalBackend implements UserBackendInterface
{ {
/**
* Possible variables where to read the user from
*
* @var string[]
*/
public static $remoteUserEnvvars = array('REMOTE_USER', 'REDIRECT_REMOTE_USER');
/** /**
* The name of this backend * The name of this backend
* *
@ -55,7 +63,7 @@ class ExternalBackend implements UserBackendInterface
/** /**
* Get the remote user from environment or $_SERVER, if any * Get the remote user from environment or $_SERVER, if any
* *
* @param string $variable The name variable where to read the user from * @param string $variable The name of the variable where to read the user from
* *
* @return string|null * @return string|null
*/ */
@ -65,29 +73,46 @@ class ExternalBackend implements UserBackendInterface
if ($username !== false) { if ($username !== false) {
return $username; return $username;
} }
if (array_key_exists($variable, $_SERVER)) { if (array_key_exists($variable, $_SERVER)) {
return $_SERVER[$variable]; return $_SERVER[$variable];
} }
return null;
} }
/**
* Get the remote user information from environment or $_SERVER, if any
*
* @return array Contains always two entries, the username and origin which may both set to null.
*/
public static function getRemoteUserInformation()
{
foreach (static::$remoteUserEnvvars as $envVar) {
$username = static::getRemoteUser($envVar);
if ($username !== null) {
return array($username, $envVar);
}
}
return array(null, null);
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function authenticate(User $user, $password = null) public function authenticate(User $user, $password = null)
{ {
$username = static::getRemoteUser(); list($username, $field) = static::getRemoteUserInformation();
if ($username !== null) { if ($username !== null) {
$user->setExternalUserInformation($username, 'REMOTE_USER'); $user->setExternalUserInformation($username, $field);
if ($this->stripUsernameRegexp) { 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 Logger::error('Failed to strip external username. The configured regular expression is invalid.');
// additional message in that case? return false;
$username = $stripped;
} }
$username = $stripped;
} }
$user->setUsername($username); $user->setUsername($username);

View File

@ -254,7 +254,7 @@ class DbUserGroupBackend extends DbRepository implements UserGroupBackendInterfa
$this->requireTable('group_membership'), $this->requireTable('group_membership'),
'g.id = gm.group_id', 'g.id = gm.group_id',
array() array()
); )->group('g.id');
} }
/** /**

View File

@ -9,8 +9,8 @@ use Icinga\Data\Inspection;
use PDO; use PDO;
use Iterator; use Iterator;
use Zend_Db; use Zend_Db;
use Zend_Db_Expr;
use Icinga\Data\ConfigObject; use Icinga\Data\ConfigObject;
use Icinga\Data\Db\DbQuery;
use Icinga\Data\Extensible; use Icinga\Data\Extensible;
use Icinga\Data\Filter\Filter; use Icinga\Data\Filter\Filter;
use Icinga\Data\Filter\FilterAnd; use Icinga\Data\Filter\FilterAnd;
@ -462,7 +462,7 @@ class DbConnection implements Selectable, Extensible, Updatable, Reducible, Insp
if ($sign === '=') { if ($sign === '=') {
return $column . ' IN (' . $this->dbAdapter->quote($value) . ')'; return $column . ' IN (' . $this->dbAdapter->quote($value) . ')';
} elseif ($sign === '!=') { } elseif ($sign === '!=') {
return $column . ' NOT IN (' . $this->dbAdapter->quote($value) . ')'; return sprintf('(%1$s NOT IN (%2$s) OR %1$s IS NULL)', $column, $this->dbAdapter->quote($value));
} }
throw new ProgrammingError( throw new ProgrammingError(
@ -470,10 +470,10 @@ class DbConnection implements Selectable, Extensible, Updatable, Reducible, Insp
); );
} elseif ($sign === '=' && strpos($value, '*') !== false) { } elseif ($sign === '=' && strpos($value, '*') !== false) {
if ($value === '*') { if ($value === '*') {
// We'll ignore such filters as it prevents index usage and because "*" means anything, anything means // We'll ignore such filters as it prevents index usage and because "*" means anything, so whether we're
// all whereas all means that whether we use a filter to match anything or no filter at all makes no // using a real column with a valid comparison here or just an expression which can only be evaluated to
// difference, except for performance reasons... // true makes no difference, except for performance reasons...
return ''; return new Zend_Db_Expr('TRUE');
} }
return $column . ' LIKE ' . $this->dbAdapter->quote(preg_replace('~\*~', '%', $value)); return $column . ' LIKE ' . $this->dbAdapter->quote(preg_replace('~\*~', '%', $value));
@ -482,12 +482,18 @@ class DbConnection implements Selectable, Extensible, Updatable, Reducible, Insp
// We'll ignore such filters as it prevents index usage and because "*" means nothing, so whether we're // We'll ignore such filters as it prevents index usage and because "*" means nothing, so whether we're
// using a real column with a valid comparison here or just an expression which cannot be evaluated to // using a real column with a valid comparison here or just an expression which cannot be evaluated to
// true makes no difference, except for performance reasons... // true makes no difference, except for performance reasons...
return $this->dbAdapter->quote(0); return new Zend_Db_Expr('FALSE');
} }
return $column . ' NOT LIKE ' . $this->dbAdapter->quote(preg_replace('~\*~', '%', $value)); return sprintf(
'(%1$s NOT LIKE %2$s OR %1$s IS NULL)',
$column,
$this->dbAdapter->quote(preg_replace('~\*~', '%', $value))
);
} elseif ($sign === '!=') {
return sprintf('(%1$s != %2$s OR %1$s IS NULL)', $column, $this->dbAdapter->quote($value));
} else { } else {
return $column . ' ' . $sign . ' ' . $this->dbAdapter->quote($value); return sprintf('%s %s %s', $column, $sign, $this->dbAdapter->quote($value));
} }
} }

View File

@ -4,6 +4,7 @@
namespace Icinga\Data\Db; namespace Icinga\Data\Db;
use Exception; use Exception;
use Zend_Db_Expr;
use Zend_Db_Select; use Zend_Db_Select;
use Icinga\Application\Logger; use Icinga\Application\Logger;
use Icinga\Data\Filter\FilterAnd; use Icinga\Data\Filter\FilterAnd;
@ -300,30 +301,30 @@ class DbQuery extends SimpleQuery
if ($sign === '=') { if ($sign === '=') {
return $col . ' IN (' . $this->escapeForSql($expression) . ')'; return $col . ' IN (' . $this->escapeForSql($expression) . ')';
} elseif ($sign === '!=') { } elseif ($sign === '!=') {
return $col . ' NOT IN (' . $this->escapeForSql($expression) . ')'; return sprintf('(%1$s NOT IN (%2$s) OR %1$s IS NULL)', $col, $this->escapeForSql($expression));
} }
throw new QueryException('Unable to render array expressions with operators other than equal or not equal'); throw new QueryException('Unable to render array expressions with operators other than equal or not equal');
} elseif ($sign === '=' && strpos($expression, '*') !== false) { } elseif ($sign === '=' && strpos($expression, '*') !== false) {
if ($expression === '*') { if ($expression === '*') {
// We'll ignore such filters as it prevents index usage and because "*" means anything, anything means return new Zend_Db_Expr('TRUE');
// all whereas all means that whether we use a filter to match anything or no filter at all makes no
// difference, except for performance reasons...
return '';
} }
return $col . ' LIKE ' . $this->escapeForSql($this->escapeWildcards($expression)); return $col . ' LIKE ' . $this->escapeForSql($this->escapeWildcards($expression));
} elseif ($sign === '!=' && strpos($expression, '*') !== false) { } elseif ($sign === '!=' && strpos($expression, '*') !== false) {
if ($expression === '*') { if ($expression === '*') {
// We'll ignore such filters as it prevents index usage and because "*" means nothing, so whether we're return new Zend_Db_Expr('FALSE');
// using a real column with a valid comparison here or just an expression which cannot be evaluated to
// true makes no difference, except for performance reasons...
return $this->escapeForSql(0);
} }
return $col . ' NOT LIKE ' . $this->escapeForSql($this->escapeWildcards($expression)); return sprintf(
'(%1$s NOT LIKE %2$s OR %1$s IS NULL)',
$col,
$this->escapeForSql($this->escapeWildcards($expression))
);
} elseif ($sign === '!=') {
return sprintf('(%1$s %2$s %3$s OR %1$s IS NULL)', $col, $sign, $this->escapeForSql($expression));
} else { } else {
return $col . ' ' . $sign . ' ' . $this->escapeForSql($expression); return sprintf('%s %s %s', $col, $sign, $this->escapeForSql($expression));
} }
} }

View File

@ -107,6 +107,10 @@ class FilterExpression extends Filter
public function __toString() public function __toString()
{ {
if ($this->isBooleanTrue()) {
return $this->column;
}
$expression = is_array($this->expression) ? $expression = is_array($this->expression) ?
'( ' . implode(' | ', $this->expression) . ' )' : '( ' . implode(' | ', $this->expression) . ' )' :
$this->expression; $this->expression;
@ -121,6 +125,10 @@ class FilterExpression extends Filter
public function toQueryString() public function toQueryString()
{ {
if ($this->isBooleanTrue()) {
return $this->column;
}
$expression = is_array($this->expression) ? $expression = is_array($this->expression) ?
'(' . implode('|', array_map('rawurlencode', $this->expression)) . ')' : '(' . implode('|', array_map('rawurlencode', $this->expression)) . ')' :
rawurlencode($this->expression); rawurlencode($this->expression);
@ -128,6 +136,11 @@ class FilterExpression extends Filter
return $this->column . $this->sign . $expression; return $this->column . $this->sign . $expression;
} }
protected function isBooleanTrue()
{
return $this->sign === '=' && $this->expression === true;
}
/** /**
* If $var is a scalar, do the same as strtolower() would do. * If $var is a scalar, do the same as strtolower() would do.
* If $var is an array, map $this->strtolowerRecursive() to its elements. * If $var is an array, map $this->strtolowerRecursive() to its elements.

View File

@ -56,15 +56,26 @@ class ResourceFactory implements ConfigAwareFactory
} }
/** /**
* Return the configuration of all existing resources, or get all resources of a given type. * Get the configuration of all existing resources, or all resources of the given type
* *
* @return Config The configuration containing all resources * @param string $type Filter for resource type
*
* @return Config The resources configuration
*/ */
public static function getResourceConfigs() public static function getResourceConfigs($type = null)
{ {
self::assertResourcesExist(); self::assertResourcesExist();
if ($type === null) {
return self::$resources; return self::$resources;
} }
$resources = array();
foreach (self::$resources as $name => $resource) {
if ($resource->get('type') === $type) {
$resources[$name] = $resource;
}
}
return Config::fromArray($resources);
}
/** /**
* Check if the existing resources are set. If not, load them from resources.ini * Check if the existing resources are set. If not, load them from resources.ini

View File

@ -3,15 +3,17 @@
namespace Icinga\File; namespace Icinga\File;
use Traversable;
class Csv class Csv
{ {
protected $query; protected $query;
protected function __construct() {} protected function __construct() {}
public static function fromQuery($query) public static function fromQuery(Traversable $query)
{ {
$csv = new Csv(); $csv = new static();
$csv->query = $query; $csv->query = $query;
return $csv; return $csv;
} }
@ -26,7 +28,7 @@ class Csv
{ {
$first = true; $first = true;
$csv = ''; $csv = '';
foreach ($this->query->fetchAll() as $row) { foreach ($this->query as $row) {
if ($first) { if ($first) {
$csv .= implode(',', array_keys((array) $row)) . "\r\n"; $csv .= implode(',', array_keys((array) $row)) . "\r\n";
$first = false; $first = false;

View File

@ -149,6 +149,10 @@ class IniWriter
$domSection = $doc->getSection($section); $domSection = $doc->getSection($section);
} }
foreach ($directives as $key => $value) { foreach ($directives as $key => $value) {
if ($value === null) {
continue;
}
if ($value instanceof ConfigObject) { if ($value instanceof ConfigObject) {
throw new ProgrammingError('Cannot diff recursive configs'); throw new ProgrammingError('Cannot diff recursive configs');
} }

View File

@ -3,28 +3,16 @@
namespace Icinga\File; namespace Icinga\File;
use DOMPDF; use Dompdf\Dompdf;
use DOMDocument; use Dompdf\Options;
use DOMXPath;
use Font_Metrics;
use Icinga\Application\Icinga; use Icinga\Application\Icinga;
use Icinga\Web\StyleSheet;
use Icinga\Web\Url;
use Icinga\Exception\ProgrammingError; use Icinga\Exception\ProgrammingError;
use Icinga\Web\Url;
require_once 'dompdf/dompdf_config.inc.php'; require_once 'dompdf/autoload.inc.php';
require_once 'dompdf/include/autoload.inc.php';
class Pdf extends DOMPDF class Pdf
{ {
public $paginateTable = false;
public function __construct()
{
$this->set_paper('A4', 'portrait');
parent::__construct();
}
protected function assertNoHeadersSent() protected function assertNoHeadersSent()
{ {
if (headers_sent()) { if (headers_sent()) {
@ -50,12 +38,15 @@ class Pdf extends DOMPDF
$html = $layout->render(); $html = $layout->render();
$imgDir = Url::fromPath('img'); $imgDir = Url::fromPath('img');
$html = preg_replace('~src="' . $imgDir . '/~', 'src="' . Icinga::app()->getBootstrapDirectory() . '/img/', $html); $html = preg_replace('~src="' . $imgDir . '/~', 'src="' . Icinga::app()->getBootstrapDirectory() . '/img/', $html);
$this->load_html($html); $options = new Options();
$this->render(); $options->set('defaultPaperSize', 'A4');
$dompdf = new Dompdf($options);
$dompdf->loadHtml($html);
$dompdf->render();
$request = $controller->getRequest(); $request = $controller->getRequest();
$this->stream( $dompdf->stream(
sprintf( sprintf(
'%s-%s-%d.pdf', '%s-%s-%d',
$request->getControllerName(), $request->getControllerName(),
$request->getActionName(), $request->getActionName(),
time() time()

View File

@ -5,6 +5,7 @@ namespace Icinga\Repository;
use Exception; use Exception;
use Icinga\Application\Config; use Icinga\Application\Config;
use Icinga\Data\ConfigObject;
use Icinga\Data\Extensible; use Icinga\Data\Extensible;
use Icinga\Data\Filter\Filter; use Icinga\Data\Filter\Filter;
use Icinga\Data\Updatable; use Icinga\Data\Updatable;
@ -18,33 +19,218 @@ use Icinga\Exception\StatementException;
* Additionally provided features: * Additionally provided features:
* <ul> * <ul>
* <li>Insert, update and delete capabilities</li> * <li>Insert, update and delete capabilities</li>
* <li>Triggers for inserts, updates and deletions</li>
* <li>Lazy initialization of table specific configs</li>
* </ul> * </ul>
*/ */
abstract class IniRepository extends Repository implements Extensible, Updatable, Reducible abstract class IniRepository extends Repository implements Extensible, Updatable, Reducible
{ {
/** /**
* The datasource being used * The configuration files used as table specific datasources
* *
* @var Config * This must be initialized by concrete repository implementations, in the following format
* <code>
* array(
* 'table_name' => array(
* 'config' => 'name_of_the_ini_file_without_extension',
* 'keyColumn' => 'the_name_of_the_column_to_use_as_key_column',
* ['module' => 'the_name_of_the_module_if_any']
* )
* )
* </code>
*
* @var array
*/ */
protected $ds; protected $configs;
/**
* The tables for which triggers are available when inserting, updating or deleting rows
*
* This may be initialized by concrete repository implementations and describes for which table names triggers
* are available. The repository attempts to find a method depending on the type of event and table for which
* to run the trigger. The name of such a method is expected to be declared using lowerCamelCase.
* (e.g. group_membership will be translated to onUpdateGroupMembership and groupmembership will be translated
* to onUpdateGroupmembership) The available events are onInsert, onUpdate and onDelete.
*
* @var array
*/
protected $triggers;
/** /**
* Create a new INI repository object * Create a new INI repository object
* *
* @param Config $ds The data source to use * @param Config|null $ds The data source to use
* *
* @throws ProgrammingError In case the given data source does not provide a valid key column * @throws ProgrammingError In case the given data source does not provide a valid key column
*/ */
public function __construct(Config $ds) public function __construct(Config $ds = null)
{ {
parent::__construct($ds); // First! Due to init(). parent::__construct($ds); // First! Due to init().
if (! $ds->getConfigObject()->getKeyColumn()) { if ($ds !== null && !$ds->getConfigObject()->getKeyColumn()) {
throw new ProgrammingError('INI repositories require their data source to provide a valid key column'); throw new ProgrammingError('INI repositories require their data source to provide a valid key column');
} }
} }
/**
* {@inheritDoc}
*
* @return Config
*/
public function getDataSource($table = null)
{
if ($this->ds !== null) {
return parent::getDataSource($table);
}
$table = $table ?: $this->getBaseTable();
$configs = $this->getConfigs();
if (! isset($configs[$table])) {
throw new ProgrammingError('Config for table "%s" missing', $table);
} elseif (! $configs[$table] instanceof Config) {
$configs[$table] = $this->createConfig($configs[$table], $table);
}
if (! $configs[$table]->getConfigObject()->getKeyColumn()) {
throw new ProgrammingError(
'INI repositories require their data source to provide a valid key column'
);
}
return $configs[$table];
}
/**
* Return the configuration files used as table specific datasources
*
* Calls $this->initializeConfigs() in case $this->configs is null.
*
* @return array
*/
public function getConfigs()
{
if ($this->configs === null) {
$this->configs = $this->initializeConfigs();
}
return $this->configs;
}
/**
* Overwrite this in your repository implementation in case you need to initialize the configs lazily
*
* @return array
*/
protected function initializeConfigs()
{
return array();
}
/**
* Return the tables for which triggers are available when inserting, updating or deleting rows
*
* Calls $this->initializeTriggers() in case $this->triggers is null.
*
* @return array
*/
public function getTriggers()
{
if ($this->triggers === null) {
$this->triggers = $this->initializeTriggers();
}
return $this->triggers;
}
/**
* Overwrite this in your repository implementation in case you need to initialize the triggers lazily
*
* @return array
*/
protected function initializeTriggers()
{
return array();
}
/**
* Run a trigger for the given table and row which is about to be inserted
*
* @param string $table
* @param ConfigObject $new
*
* @return ConfigObject
*/
public function onInsert($table, ConfigObject $new)
{
$trigger = $this->getTrigger($table, 'onInsert');
if ($trigger !== null) {
$row = $this->$trigger($new);
if ($row !== null) {
$new = $row;
}
}
return $new;
}
/**
* Run a trigger for the given table and row which is about to be updated
*
* @param string $table
* @param ConfigObject $old
* @param ConfigObject $new
*
* @return ConfigObject
*/
public function onUpdate($table, ConfigObject $old, ConfigObject $new)
{
$trigger = $this->getTrigger($table, 'onUpdate');
if ($trigger !== null) {
$row = $this->$trigger($old, $new);
if ($row !== null) {
$new = $row;
}
}
return $new;
}
/**
* Run a trigger for the given table and row which has been deleted
*
* @param string $table
* @param ConfigObject $old
*
* @return ConfigObject
*/
public function onDelete($table, ConfigObject $old)
{
$trigger = $this->getTrigger($table, 'onDelete');
if ($trigger !== null) {
$this->$trigger($old);
}
}
/**
* Return the name of the trigger method for the given table and event-type
*
* @param string $table The table name for which to return a trigger method
* @param string $event The name of the event type
*
* @return string
*/
protected function getTrigger($table, $event)
{
if (! in_array($table, $this->getTriggers())) {
return;
}
$identifier = join('', array_map('ucfirst', explode('_', $table)));
if (method_exists($this, $event . $identifier)) {
return $event . $identifier;
}
}
/** /**
* Insert the given data for the given target * Insert the given data for the given target
* *
@ -57,17 +243,20 @@ abstract class IniRepository extends Repository implements Extensible, Updatable
*/ */
public function insert($target, array $data) public function insert($target, array $data)
{ {
$ds = $this->getDataSource($target);
$newData = $this->requireStatementColumns($target, $data); $newData = $this->requireStatementColumns($target, $data);
$section = $this->extractSectionName($newData);
if ($this->ds->hasSection($section)) { $config = $this->onInsert($target, new ConfigObject($newData));
$section = $this->extractSectionName($config, $ds->getConfigObject()->getKeyColumn());
if ($ds->hasSection($section)) {
throw new StatementException(t('Cannot insert. Section "%s" does already exist'), $section); throw new StatementException(t('Cannot insert. Section "%s" does already exist'), $section);
} }
$this->ds->setSection($section, $newData); $ds->setSection($section, $config);
try { try {
$this->ds->saveIni(); $ds->saveIni();
} catch (Exception $e) { } catch (Exception $e) {
throw new StatementException(t('Failed to insert. An error occurred: %s'), $e->getMessage()); throw new StatementException(t('Failed to insert. An error occurred: %s'), $e->getMessage());
} }
@ -84,8 +273,10 @@ abstract class IniRepository extends Repository implements Extensible, Updatable
*/ */
public function update($target, array $data, Filter $filter = null) public function update($target, array $data, Filter $filter = null)
{ {
$ds = $this->getDataSource($target);
$newData = $this->requireStatementColumns($target, $data); $newData = $this->requireStatementColumns($target, $data);
$keyColumn = $this->ds->getConfigObject()->getKeyColumn();
$keyColumn = $ds->getConfigObject()->getKeyColumn();
if ($filter === null && isset($newData[$keyColumn])) { if ($filter === null && isset($newData[$keyColumn])) {
throw new StatementException( throw new StatementException(
t('Cannot update. Column "%s" holds a section\'s name which must be unique'), t('Cannot update. Column "%s" holds a section\'s name which must be unique'),
@ -93,16 +284,14 @@ abstract class IniRepository extends Repository implements Extensible, Updatable
); );
} }
$query = $ds->select();
if ($filter !== null) { if ($filter !== null) {
$filter = $this->requireFilter($target, $filter); $query->addFilter($this->requireFilter($target, $filter));
} }
/** @var ConfigObject $config */
$newSection = null; $newSection = null;
foreach (iterator_to_array($this->ds) as $section => $config) { foreach ($query as $section => $config) {
if ($filter !== null && !$filter->matches($config)) {
continue;
}
if ($newSection !== null) { if ($newSection !== null) {
throw new StatementException( throw new StatementException(
t('Cannot update. Column "%s" holds a section\'s name which must be unique'), t('Cannot update. Column "%s" holds a section\'s name which must be unique'),
@ -110,27 +299,37 @@ abstract class IniRepository extends Repository implements Extensible, Updatable
); );
} }
$newConfig = clone $config;
foreach ($newData as $column => $value) { foreach ($newData as $column => $value) {
if ($column === $keyColumn) { if ($column === $keyColumn) {
$newSection = $value; $newSection = $value;
} else { } else {
$config->$column = $value; $newConfig->$column = $value;
} }
} }
// This is necessary as the query result set contains the key column.
unset($newConfig->$keyColumn);
if ($newSection) { if ($newSection) {
if ($this->ds->hasSection($newSection)) { if ($ds->hasSection($newSection)) {
throw new StatementException(t('Cannot update. Section "%s" does already exist'), $newSection); throw new StatementException(t('Cannot update. Section "%s" does already exist'), $newSection);
} }
$this->ds->removeSection($section)->setSection($newSection, $config); $ds->removeSection($section)->setSection(
$newSection,
$this->onUpdate($target, $config, $newConfig)
);
} else { } else {
$this->ds->setSection($section, $config); $ds->setSection(
$section,
$this->onUpdate($target, $config, $newConfig)
);
} }
} }
try { try {
$this->ds->saveIni(); $ds->saveIni();
} catch (Exception $e) { } catch (Exception $e) {
throw new StatementException(t('Failed to update. An error occurred: %s'), $e->getMessage()); throw new StatementException(t('Failed to update. An error occurred: %s'), $e->getMessage());
} }
@ -146,41 +345,74 @@ abstract class IniRepository extends Repository implements Extensible, Updatable
*/ */
public function delete($target, Filter $filter = null) public function delete($target, Filter $filter = null)
{ {
$ds = $this->getDataSource($target);
$query = $ds->select();
if ($filter !== null) { if ($filter !== null) {
$filter = $this->requireFilter($target, $filter); $query->addFilter($this->requireFilter($target, $filter));
} }
foreach (iterator_to_array($this->ds) as $section => $config) { /** @var ConfigObject $config */
if ($filter === null || $filter->matches($config)) { foreach ($query as $section => $config) {
$this->ds->removeSection($section); $ds->removeSection($section);
} $this->onDelete($target, $config);
} }
try { try {
$this->ds->saveIni(); $ds->saveIni();
} catch (Exception $e) { } catch (Exception $e) {
throw new StatementException(t('Failed to delete. An error occurred: %s'), $e->getMessage()); throw new StatementException(t('Failed to delete. An error occurred: %s'), $e->getMessage());
} }
} }
/** /**
* Extract and return the section name off of the given $data * Create and return a Config for the given meta and table
* *
* @param array $data * @param array $meta
* @param string $table
*
* @return Config
*
* @throws ProgrammingError In case the given meta is invalid
*/
protected function createConfig(array $meta, $table)
{
if (! isset($meta['name'])) {
throw new ProgrammingError('Config file name missing for table "%s"', $table);
} elseif (! isset($meta['keyColumn'])) {
throw new ProgrammingError('Config key column name missing for table "%s"', $table);
}
if (isset($meta['module'])) {
$config = Config::module($meta['module'], $meta['name']);
} else {
$config = Config::app($meta['name']);
}
$config->getConfigObject()->setKeyColumn($meta['keyColumn']);
return $config;
}
/**
* Extract and return the section name off of the given $config
*
* @param array|ConfigObject $config
* @param string $keyColumn
* *
* @return string * @return string
* *
* @throws ProgrammingError In case no valid section name is available * @throws ProgrammingError In case no valid section name is available
*/ */
protected function extractSectionName(array & $data) protected function extractSectionName( & $config, $keyColumn)
{ {
$keyColumn = $this->ds->getConfigObject()->getKeyColumn(); if (! is_array($config) && !$config instanceof ConfigObject) {
if (! isset($data[$keyColumn])) { throw new ProgrammingError('$config is neither an array nor a ConfigObject');
throw new ProgrammingError('$data does not provide a value for key column "%s"', $keyColumn); } elseif (! isset($config[$keyColumn])) {
throw new ProgrammingError('$config does not provide a value for key column "%s"', $keyColumn);
} }
$section = $data[$keyColumn]; $section = $config[$keyColumn];
unset($data[$keyColumn]); unset($config[$keyColumn]);
return $section; return $section;
} }
} }

View File

@ -90,6 +90,13 @@ abstract class Repository implements Selectable
*/ */
protected $blacklistedQueryColumns; protected $blacklistedQueryColumns;
/**
* Whether the blacklisted query columns are in the legacy format
*
* @var bool
*/
protected $legacyBlacklistedQueryColumns;
/** /**
* The filter columns being provided * The filter columns being provided
* *
@ -105,6 +112,13 @@ abstract class Repository implements Selectable
*/ */
protected $filterColumns; protected $filterColumns;
/**
* Whether the provided filter columns are in the legacy format
*
* @var bool
*/
protected $legacyFilterColumns;
/** /**
* The search columns (or aliases) being provided * The search columns (or aliases) being provided
* *
@ -112,6 +126,13 @@ abstract class Repository implements Selectable
*/ */
protected $searchColumns; protected $searchColumns;
/**
* Whether the provided search columns are in the legacy format
*
* @var bool
*/
protected $legacySearchColumns;
/** /**
* The sort rules to be applied on a query * The sort rules to be applied on a query
* *
@ -140,6 +161,13 @@ abstract class Repository implements Selectable
*/ */
protected $sortRules; protected $sortRules;
/**
* Whether the provided sort rules are in the legacy format
*
* @var bool
*/
protected $legacySortRules;
/** /**
* The value conversion rules to apply on a query or statement * The value conversion rules to apply on a query or statement
* *
@ -186,9 +214,10 @@ abstract class Repository implements Selectable
/** /**
* Create a new repository object * Create a new repository object
* *
* @param Selectable $ds The datasource to use * @param Selectable|null $ds The datasource to use.
* Only pass null if you have overridden {@link getDataSource()}!
*/ */
public function __construct(Selectable $ds) public function __construct(Selectable $ds = null)
{ {
$this->ds = $ds; $this->ds = $ds;
$this->aliasTableMap = array(); $this->aliasTableMap = array();
@ -235,12 +264,23 @@ abstract class Repository implements Selectable
} }
/** /**
* Return the datasource being used * Return the datasource being used for the given table
*
* @param string $table
* *
* @return Selectable * @return Selectable
*
* @throws ProgrammingError In case no datasource is available
*/ */
public function getDataSource() public function getDataSource($table = null)
{ {
if ($this->ds === null) {
throw new ProgrammingError(
'No data source available. It is required to either pass it'
. ' at initialization time or by overriding this method.'
);
}
return $this->ds; return $this->ds;
} }
@ -323,25 +363,45 @@ abstract class Repository implements Selectable
* *
* Calls $this->initializeBlacklistedQueryColumns() in case $this->blacklistedQueryColumns is null. * Calls $this->initializeBlacklistedQueryColumns() in case $this->blacklistedQueryColumns is null.
* *
* @param string $table
*
* @return array * @return array
*/ */
public function getBlacklistedQueryColumns() public function getBlacklistedQueryColumns($table = null)
{ {
if ($this->blacklistedQueryColumns === null) { if ($this->blacklistedQueryColumns === null) {
$this->blacklistedQueryColumns = $this->initializeBlacklistedQueryColumns(); $this->legacyBlacklistedQueryColumns = false;
$blacklistedQueryColumns = $this->initializeBlacklistedQueryColumns($table);
if (is_int(key($blacklistedQueryColumns))) {
$this->blacklistedQueryColumns[$table] = $blacklistedQueryColumns;
} else {
$this->blacklistedQueryColumns = $blacklistedQueryColumns;
}
} elseif ($this->legacyBlacklistedQueryColumns === null) {
$this->legacyBlacklistedQueryColumns = is_int(key($this->blacklistedQueryColumns));
} }
if ($this->legacyBlacklistedQueryColumns) {
return $this->blacklistedQueryColumns; return $this->blacklistedQueryColumns;
} elseif (! isset($this->blacklistedQueryColumns[$table])) {
$this->blacklistedQueryColumns[$table] = $this->initializeBlacklistedQueryColumns($table);
}
return $this->blacklistedQueryColumns[$table];
} }
/** /**
* Overwrite this in your repository implementation in case you * Overwrite this in your repository implementation in case you need to initialize the
* need to initialize the blacklisted query columns lazily * blacklisted query columns lazily or dependent on a query's current base table
*
* @param string $table
* *
* @return array * @return array
*/ */
protected function initializeBlacklistedQueryColumns() protected function initializeBlacklistedQueryColumns()
{ {
// $table is not part of the signature due to PHP strict standards
return array(); return array();
} }
@ -350,24 +410,47 @@ abstract class Repository implements Selectable
* *
* Calls $this->initializeFilterColumns() in case $this->filterColumns is null. * Calls $this->initializeFilterColumns() in case $this->filterColumns is null.
* *
* @param string $table
*
* @return array * @return array
*/ */
public function getFilterColumns() public function getFilterColumns($table = null)
{ {
if ($this->filterColumns === null) { if ($this->filterColumns === null) {
$this->filterColumns = $this->initializeFilterColumns(); $this->legacyFilterColumns = false;
$filterColumns = $this->initializeFilterColumns($table);
$foundTables = array_intersect_key($this->getQueryColumns(), $filterColumns);
if (empty($foundTables)) {
$this->filterColumns[$table] = $filterColumns;
} else {
$this->filterColumns = $filterColumns;
}
} elseif ($this->legacyFilterColumns === null) {
$foundTables = array_intersect_key($this->getQueryColumns(), $this->filterColumns);
$this->legacyFilterColumns = empty($foundTables);
} }
if ($this->legacyFilterColumns) {
return $this->filterColumns; return $this->filterColumns;
} elseif (! isset($this->filterColumns[$table])) {
$this->filterColumns[$table] = $this->initializeFilterColumns($table);
}
return $this->filterColumns[$table];
} }
/** /**
* Overwrite this in your repository implementation in case you need to initialize the filter columns lazily * Overwrite this in your repository implementation in case you need to initialize
* the filter columns lazily or dependent on a query's current base table
*
* @param string $table
* *
* @return array * @return array
*/ */
protected function initializeFilterColumns() protected function initializeFilterColumns()
{ {
// $table is not part of the signature due to PHP strict standards
return array(); return array();
} }
@ -376,24 +459,45 @@ abstract class Repository implements Selectable
* *
* Calls $this->initializeSearchColumns() in case $this->searchColumns is null. * Calls $this->initializeSearchColumns() in case $this->searchColumns is null.
* *
* @param string $table
*
* @return array * @return array
*/ */
public function getSearchColumns() public function getSearchColumns($table = null)
{ {
if ($this->searchColumns === null) { if ($this->searchColumns === null) {
$this->searchColumns = $this->initializeSearchColumns(); $this->legacySearchColumns = false;
$searchColumns = $this->initializeSearchColumns($table);
if (is_int(key($searchColumns))) {
$this->searchColumns[$table] = $searchColumns;
} else {
$this->searchColumns = $searchColumns;
}
} elseif ($this->legacySearchColumns === null) {
$this->legacySearchColumns = is_int(key($this->searchColumns));
} }
if ($this->legacySearchColumns) {
return $this->searchColumns; return $this->searchColumns;
} elseif (! isset($this->searchColumns[$table])) {
$this->searchColumns[$table] = $this->initializeSearchColumns($table);
}
return $this->searchColumns[$table];
} }
/** /**
* Overwrite this in your repository implementation in case you need to initialize the search columns lazily * Overwrite this in your repository implementation in case you need to initialize
* the search columns lazily or dependent on a query's current base table
*
* @param string $table
* *
* @return array * @return array
*/ */
protected function initializeSearchColumns() protected function initializeSearchColumns()
{ {
// $table is not part of the signature due to PHP strict standards
return array(); return array();
} }
@ -402,24 +506,47 @@ abstract class Repository implements Selectable
* *
* Calls $this->initializeSortRules() in case $this->sortRules is null. * Calls $this->initializeSortRules() in case $this->sortRules is null.
* *
* @param string $table
*
* @return array * @return array
*/ */
public function getSortRules() public function getSortRules($table = null)
{ {
if ($this->sortRules === null) { if ($this->sortRules === null) {
$this->sortRules = $this->initializeSortRules(); $this->legacySortRules = false;
$sortRules = $this->initializeSortRules($table);
$foundTables = array_intersect_key($this->getQueryColumns(), $sortRules);
if (empty($foundTables)) {
$this->sortRules[$table] = $sortRules;
} else {
$this->sortRules = $sortRules;
}
} elseif ($this->legacySortRules === null) {
$foundTables = array_intersect_key($this->getQueryColumns(), $this->sortRules);
$this->legacyFilterColumns = empty($foundTables);
} }
if ($this->legacySortRules) {
return $this->sortRules; return $this->sortRules;
} elseif (! isset($this->sortRules[$table])) {
$this->sortRules[$table] = $this->initializeSortRules($table);
}
return $this->sortRules[$table];
} }
/** /**
* Overwrite this in your repository implementation in case you need to initialize the sort rules lazily * Overwrite this in your repository implementation in case you need to initialize
* the sort rules lazily or dependent on a query's current base table
*
* @param string $table
* *
* @return array * @return array
*/ */
protected function initializeSortRules() protected function initializeSortRules()
{ {
// $table is not part of the signature due to PHP strict standards
return array(); return array();
} }
@ -900,7 +1027,7 @@ abstract class Repository implements Selectable
throw new ProgrammingError('Table name "%s" not found', $table); throw new ProgrammingError('Table name "%s" not found', $table);
} }
$blacklist = $this->getBlacklistedQueryColumns(); $blacklist = $this->getBlacklistedQueryColumns($table);
$columns = array(); $columns = array();
foreach ($queryColumns[$table] as $alias => $column) { foreach ($queryColumns[$table] as $alias => $column) {
$name = is_string($alias) ? $alias : $column; $name = is_string($alias) ? $alias : $column;
@ -994,7 +1121,7 @@ abstract class Repository implements Selectable
return false; return false;
} }
return !in_array($alias, $this->getBlacklistedQueryColumns()) return !in_array($alias, $this->getBlacklistedQueryColumns($table))
&& $this->validateQueryColumnAssociation($table, $name); && $this->validateQueryColumnAssociation($table, $name);
} }
@ -1019,7 +1146,7 @@ abstract class Repository implements Selectable
throw new QueryException(t('Query column "%s" not found'), $name); throw new QueryException(t('Query column "%s" not found'), $name);
} }
if (in_array($alias, $this->getBlacklistedQueryColumns())) { if (in_array($alias, $this->getBlacklistedQueryColumns($table))) {
throw new QueryException(t('Column "%s" cannot be queried'), $name); throw new QueryException(t('Column "%s" cannot be queried'), $name);
} }
@ -1107,7 +1234,7 @@ abstract class Repository implements Selectable
throw new StatementException('Statement column "%s" not found', $name); throw new StatementException('Statement column "%s" not found', $name);
} }
if (in_array($alias, $this->getBlacklistedQueryColumns())) { if (in_array($alias, $this->getBlacklistedQueryColumns($table))) {
throw new StatementException('Column "%s" cannot be referenced in a statement', $name); throw new StatementException('Column "%s" cannot be referenced in a statement', $name);
} }

View File

@ -69,9 +69,13 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
*/ */
public function __clone() public function __clone()
{ {
if ($this->query !== null) {
$this->query = clone $this->query; $this->query = clone $this->query;
}
if ($this->iterator !== null) {
$this->iterator = clone $this->iterator; $this->iterator = clone $this->iterator;
} }
}
/** /**
* Return a string representation of this query * Return a string representation of this query
@ -105,7 +109,7 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
*/ */
public function from($target, array $columns = null) public function from($target, array $columns = null)
{ {
$this->query = $this->repository->getDataSource()->select(); $this->query = $this->repository->getDataSource($target)->select();
$this->query->from($this->repository->requireTable($target, $this)); $this->query->from($this->repository->requireTable($target, $this));
$this->query->columns($this->prepareQueryColumns($target, $columns)); $this->query->columns($this->prepareQueryColumns($target, $columns));
$this->target = $target; $this->target = $target;
@ -200,7 +204,7 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
*/ */
public function getFilterColumns() public function getFilterColumns()
{ {
return $this->repository->getFilterColumns(); return $this->repository->getFilterColumns($this->target);
} }
/** /**
@ -210,7 +214,7 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
*/ */
public function getSearchColumns() public function getSearchColumns()
{ {
return $this->repository->getSearchColumns(); return $this->repository->getSearchColumns($this->target);
} }
/** /**
@ -290,7 +294,7 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
*/ */
public function getSortRules() public function getSortRules()
{ {
return $this->repository->getSortRules(); return $this->repository->getSortRules($this->target);
} }
/** /**
@ -712,7 +716,7 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
if ($this->query instanceof Traversable) { if ($this->query instanceof Traversable) {
$iterator = $this->query; $iterator = $this->query;
} else { } else {
$iterator = $this->repository->getDataSource()->query($this->query); $iterator = $this->repository->getDataSource($this->target)->query($this->query);
} }
if ($iterator instanceof IteratorAggregate) { if ($iterator instanceof IteratorAggregate) {

View File

@ -0,0 +1,158 @@
<?php
/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
namespace Icinga\Web;
/**
* An announcement to be displayed prominently in the web UI
*/
class Announcement
{
/**
* @var string
*/
protected $author;
/**
* @var string
*/
protected $message;
/**
* @var int
*/
protected $start;
/**
* @var int
*/
protected $end;
/**
* Hash of the message
*
* @var string|null
*/
protected $hash = null;
/**
* Announcement constructor
*
* @param array $properties
*/
public function __construct(array $properties = array())
{
foreach ($properties as $key => $value) {
$method = 'set' . ucfirst($key);
if (method_exists($this, $method)) {
$this->$method($value);
}
}
}
/**
* Get the author of the acknowledged
*
* @return string
*/
public function getAuthor()
{
return $this->author;
}
/**
* Set the author of the acknowledged
*
* @param string $author
*
* @return $this
*/
public function setAuthor($author)
{
$this->author = $author;
return $this;
}
/**
* Get the message of the acknowledged
*
* @return string
*/
public function getMessage()
{
return $this->message;
}
/**
* Set the message of the acknowledged
*
* @param string $message
*
* @return $this
*/
public function setMessage($message)
{
$this->message = $message;
$this->hash = null;
return $this;
}
/**
* Get the start date and time of the acknowledged
*
* @return int
*/
public function getStart()
{
return $this->start;
}
/**
* Set the start date and time of the acknowledged
*
* @param int $start
*
* @return $this
*/
public function setStart($start)
{
$this->start = $start;
return $this;
}
/**
* Get the end date and time of the acknowledged
*
* @return int
*/
public function getEnd()
{
return $this->end;
}
/**
* Set the end date and time of the acknowledged
*
* @param int $end
*
* @return $this
*/
public function setEnd($end)
{
$this->end = $end;
return $this;
}
/**
* Get the hash of the acknowledgement
*
* @return string
*/
public function getHash()
{
if ($this->hash === null) {
$this->hash = md5($this->message);
}
return $this->hash;
}
}

View File

@ -0,0 +1,137 @@
<?php
/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
namespace Icinga\Web\Announcement;
use Icinga\Web\Cookie;
/**
* Handle acknowledged announcements via cookie
*/
class AnnouncementCookie extends Cookie
{
/**
* Array of hashes representing acknowledged announcements
*
* @var string[]
*/
protected $acknowledged = array();
/**
* ETag of the last known announcements.ini
*
* @var string
*/
protected $etag;
/**
* Timestamp of the next active acknowledgement, if any
*
* @var int|null
*/
protected $nextActive;
/**
* AnnouncementCookie constructor
*/
public function __construct()
{
parent::__construct('icingaweb2-announcements');
$this->setExpire(2147483648);
if (isset($_COOKIE['icingaweb2-announcements'])) {
$cookie = json_decode($_COOKIE['icingaweb2-announcements'], true);
if ($cookie !== null) {
if (isset($cookie['acknowledged'])) {
$this->setAcknowledged($cookie['acknowledged']);
}
if (isset($cookie['etag'])) {
$this->setEtag($cookie['etag']);
}
if (isset($cookie['next'])) {
$this->setNextActive($cookie['next']);
}
}
}
}
/**
* Get the hashes of the acknowledged announcements
*
* @return string[]
*/
public function getAcknowledged()
{
return $this->acknowledged;
}
/**
* Set the hashes of the acknowledged announcements
*
* @param string[] $acknowledged
*
* @return $this
*/
public function setAcknowledged(array $acknowledged)
{
$this->acknowledged = $acknowledged;
return $this;
}
/**
* Get the ETag
*
* @return string
*/
public function getEtag()
{
return $this->etag;
}
/**
* Set the ETag
*
* @param string $etag
*
* @return $this
*/
public function setEtag($etag)
{
$this->etag = $etag;
return $this;
}
/**
* Get the timestamp of the next active announcement
*
* @return int
*/
public function getNextActive()
{
return $this->nextActive;
}
/**
* Set the timestamp of the next active announcement
*
* @param int $nextActive
*
* @return $this
*/
public function setNextActive($nextActive)
{
$this->nextActive = $nextActive;
return $this;
}
/**
* {@inheritdoc}
*/
public function getValue()
{
return json_encode(array(
'acknowledged' => $this->getAcknowledged(),
'etag' => $this->getEtag(),
'next' => $this->getNextActive()
));
}
}

View File

@ -0,0 +1,162 @@
<?php
/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
namespace Icinga\Web\Announcement;
use DateTime;
use Icinga\Data\ConfigObject;
use Icinga\Data\Filter\Filter;
use Icinga\Data\Filter\FilterAnd;
use Icinga\Data\SimpleQuery;
use Icinga\Repository\IniRepository;
use Icinga\Web\Announcement;
/**
* A collection of announcements stored in an INI file
*/
class AnnouncementIniRepository extends IniRepository
{
/**
* {@inheritdoc}
*/
protected $queryColumns = array('announcement' => array('id', 'author', 'message', 'hash', 'start', 'end'));
/**
* {@inheritdoc}
*/
protected $triggers = array('announcement');
/**
* {@inheritDoc}
*/
protected $configs = array('announcement' => array(
'name' => 'announcements',
'keyColumn' => 'id'
));
/**
* {@inheritDoc}
*/
protected $conversionRules = array('announcement' => array(
'start' => 'timestamp',
'end' => 'timestamp'
));
/**
* Create a DateTime from a timestamp
*
* @param string $timestamp
*
* @return DateTime|null
*/
protected function retrieveTimestamp($timestamp)
{
if ($timestamp !== null) {
$dateTime = new DateTime();
$dateTime->setTimestamp($timestamp);
return $dateTime;
}
return null;
}
/**
* Get a DateTime's timestamp
*
* @param DateTime $datetime
*
* @return int|null
*/
protected function persistTimestamp(DateTime $datetime)
{
return $datetime === null ? null : $datetime->getTimestamp();
}
/**
* Before-insert trigger (per row)
*
* @param ConfigObject $new The original data to insert
*
* @return ConfigObject The eventually modified data to insert
*/
protected function onInsertAnnouncement(ConfigObject $new)
{
if (! isset($new->id)) {
$new->id = uniqid();
}
if (! isset($new->hash)) {
$announcement = new Announcement($new->toArray());
$new->hash = $announcement->getHash();
}
return $new;
}
/**
* Before-update trigger (per row)
*
* @param ConfigObject $old The original data as currently stored
* @param ConfigObject $new The original data to update
*
* @return ConfigObject The eventually modified data to update
*/
protected function onUpdateAnnouncement(ConfigObject $old, ConfigObject $new)
{
if ($new->message !== $old->message) {
$announcement = new Announcement($new->toArray());
$new->hash = $announcement->getHash();
}
return $new;
}
/**
* Get the ETag of the announcements.ini file
*
* @return string
*/
public function getEtag()
{
$file = $this->getDataSource('announcement')->getConfigFile();
if (@is_readable($file)) {
$mtime = filemtime($file);
$size = filesize($file);
return hash('crc32', $mtime . $size);
}
return null;
}
/**
* Get the query for all active announcements
*
* @return SimpleQuery
*/
public function findActive()
{
$now = new DateTime();
$query = $this
->select(array('hash', 'message'))
->setFilter(new FilterAnd(array(
Filter::expression('start', '<=', $now),
Filter::expression('end', '>=', $now)
)))
->order('start');
return $query;
}
/**
* Get the timestamp of the next active announcement
*
* @return int|null
*/
public function findNextActive()
{
$now = new DateTime();
$query = $this
->select(array('start'))
->setFilter(Filter::expression('start', '>', $now))
->order('start')
->limit(1);
$nextActive = $query->fetchRow();
return $nextActive !== false ? $nextActive->start->getTimestamp() : null;
}
}

View File

@ -99,6 +99,8 @@ class ActionController extends Zend_Controller_Action
Zend_Controller_Response_Abstract $response, Zend_Controller_Response_Abstract $response,
array $invokeArgs = array() array $invokeArgs = array()
) { ) {
/** @var \Icinga\Web\Request $request */
/** @var \Icinga\Web\Response $response */
$this->params = UrlParams::fromQueryString(); $this->params = UrlParams::fromQueryString();
$this->setRequest($request) $this->setRequest($request)
@ -124,7 +126,11 @@ class ActionController extends Zend_Controller_Action
$this->_helper->layout()->disableLayout(); $this->_helper->layout()->disableLayout();
} }
// $auth->authenticate($request, $response, $this->requiresLogin());
if ($this->requiresLogin()) { if ($this->requiresLogin()) {
if (! $request->isXmlHttpRequest() && $request->isApiRequest()) {
Auth::getInstance()->challengeHttp();
}
$this->redirectToLogin(Url::fromRequest()); $this->redirectToLogin(Url::fromRequest());
} }
@ -179,7 +185,7 @@ class ActionController extends Zend_Controller_Action
*/ */
public function assertPermission($permission) public function assertPermission($permission)
{ {
if ($this->requiresAuthentication && ! $this->Auth()->hasPermission($permission)) { if (! $this->Auth()->hasPermission($permission)) {
throw new SecurityException('No permission for %s', $permission); throw new SecurityException('No permission for %s', $permission);
} }
} }
@ -255,8 +261,9 @@ class ActionController extends Zend_Controller_Action
/** /**
* Return restriction information for an eventually authenticated user * Return restriction information for an eventually authenticated user
* *
* @param string $name Permission name * @param string $name Restriction name
* @return Array *
* @return array
*/ */
public function getRestrictions($name) public function getRestrictions($name)
{ {
@ -268,7 +275,6 @@ class ActionController extends Zend_Controller_Action
* user is currently not authenticated * user is currently not authenticated
* *
* @return bool * @return bool
* @see requiresAuthentication
*/ */
protected function requiresLogin() protected function requiresLogin()
{ {

View File

@ -26,7 +26,8 @@ class ModuleActionController extends ActionController
protected function prepareInit() protected function prepareInit()
{ {
$this->moduleInit(); $this->moduleInit();
if ($this->getFrontController()->getDefaultModule() !== $this->getModuleName()) { if (($this->Auth()->isAuthenticated() || $this->requiresLogin())
&& $this->getFrontController()->getDefaultModule() !== $this->getModuleName()) {
$this->assertPermission(Manager::MODULE_PERMISSION_NS . $this->getModuleName()); $this->assertPermission(Manager::MODULE_PERMISSION_NS . $this->getModuleName());
} }
} }

View File

@ -948,7 +948,7 @@ class Form extends Zend_Form
if ($this->getUseFormAutosubmit()) { if ($this->getUseFormAutosubmit()) {
$warningId = 'autosubmit_warning_' . $el->getId(); $warningId = 'autosubmit_warning_' . $el->getId();
$warningText = $this->getView()->escape($this->translate( $warningText = $this->getView()->escape($this->translate(
'Upon its value has changed, this field issues an automatic update of this page.' 'Upon its value changing, this field issues an automatic update of this page.'
)); ));
$autosubmitDecorator = $this->_getDecorator('Callback', array( $autosubmitDecorator = $this->_getDecorator('Callback', array(
'placement' => 'PREPEND', 'placement' => 'PREPEND',
@ -1576,30 +1576,38 @@ class Form extends Zend_Form
} }
/** /**
* Add a error notification and prevent the form from being successfully validated * Add a error notification
* *
* @param string|array $message The notification message * @param string|array $message The notification message
* @param bool $markAsError Whether to prevent the form from being successfully validated or not
* *
* @return $this * @return $this
*/ */
public function error($message) public function error($message, $markAsError = true)
{ {
$this->addNotification($message, self::NOTIFICATION_ERROR); $this->addNotification($message, self::NOTIFICATION_ERROR);
if ($markAsError) {
$this->markAsError(); $this->markAsError();
}
return $this; return $this;
} }
/** /**
* Add a warning notification and prevent the form from being successfully validated * Add a warning notification
* *
* @param string|array $message The notification message * @param string|array $message The notification message
* @param bool $markAsError Whether to prevent the form from being successfully validated or not
* *
* @return $this * @return $this
*/ */
public function warning($message) public function warning($message, $markAsError = true)
{ {
$this->addNotification($message, self::NOTIFICATION_WARNING); $this->addNotification($message, self::NOTIFICATION_WARNING);
if ($markAsError) {
$this->markAsError(); $this->markAsError();
}
return $this; return $this;
} }

View File

@ -97,7 +97,7 @@ class Autosubmit extends Zend_Form_Decorator_Abstract
$isForm = $this->getElement() instanceof Form; $isForm = $this->getElement() instanceof Form;
$warning = $isForm $warning = $isForm
? t('Upon any of this form\'s fields were changed, this page is being updated automatically.') ? t('Upon any of this form\'s fields were changed, this page is being updated automatically.')
: t('Upon its value has changed, this field issues an automatic update of this page.'); : t('Upon its value changing, this field issues an automatic update of this page.');
$content .= $this->getView()->icon('cw', $warning, array( $content .= $this->getView()->icon('cw', $warning, array(
'aria-hidden' => $isForm ? 'false' : 'true', 'aria-hidden' => $isForm ? 'false' : 'true',
'class' => 'spinner autosubmit-info' 'class' => 'spinner autosubmit-info'

View File

@ -0,0 +1,39 @@
<?php
/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
namespace Icinga\Web\Form\Validator;
use Zend_Validate_Abstract;
use Icinga\Web\Url;
/**
* Validator that checks whether a textfield doesn't contain an external URL
*/
class InternalUrlValidator extends Zend_Validate_Abstract
{
/**
* {@inheritdoc}
*/
public function isValid($value)
{
if (Url::fromPath($value)->isExternal()) {
$this->_error('IS_EXTERNAL');
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
protected function _error($messageKey, $value = null)
{
if ($messageKey === 'IS_EXTERNAL') {
$this->_messages[$messageKey] = t('The url must not be external.');
} else {
parent::_error($messageKey, $value);
}
}
}

View File

@ -117,7 +117,7 @@ class JavaScript
// We do not minify vendor files // We do not minify vendor files
foreach ($vendorFiles as $file) { foreach ($vendorFiles as $file) {
$out .= file_get_contents($file); $out .= ';' . ltrim(trim(file_get_contents($file)), ';') . "\n";
} }
foreach ($jsFiles as $file) { foreach ($jsFiles as $file) {

View File

@ -565,7 +565,7 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate
{ {
$navigation = new static(); $navigation = new static();
foreach ($array as $name => $properties) { foreach ($array as $name => $properties) {
$navigation->addItem($name, $properties); $navigation->addItem((string) $name, $properties);
} }
return $navigation; return $navigation;

View File

@ -791,7 +791,7 @@ class NavigationItem implements IteratorAggregate
public function getRender() public function getRender()
{ {
if ($this->render === null) { if ($this->render === null) {
return true; return $this->getUrl() !== null;
} }
return $this->render; return $this->render;

View File

@ -3,44 +3,70 @@
namespace Icinga\Web\Navigation\Renderer; namespace Icinga\Web\Navigation\Renderer;
use Icinga\Web\Navigation\Renderer\BadgeNavigationItemRenderer;
/** /**
* Summary badge adding up all badges in the navigation's children that have the same state * Badge renderer summing up the worst state of its children
*/ */
class SummaryNavigationItemRenderer extends BadgeNavigationItemRenderer class SummaryNavigationItemRenderer extends BadgeNavigationItemRenderer
{ {
/** /**
* The title of each summarized child * Cached count
*
* @var int
*/
protected $count;
/**
* State to severity map
* *
* @var array * @var array
*/ */
protected $titles; protected static $stateSeverityMap = array(
self::STATE_OK => 0,
self::STATE_PENDING => 1,
self::STATE_UNKNOWN => 2,
self::STATE_WARNING => 3,
self::STATE_CRITICAL => 4,
);
/**
* Severity to state map
*
* @var array
*/
protected static $severityStateMap = array(
self::STATE_OK,
self::STATE_PENDING,
self::STATE_UNKNOWN,
self::STATE_WARNING,
self::STATE_CRITICAL
);
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getCount() public function getCount()
{ {
$count = 0; if ($this->count === null) {
$countMap = array_fill(0, 5, 0);
$maxSeverity = 0;
$titles = array();
foreach ($this->getItem()->getChildren() as $child) { foreach ($this->getItem()->getChildren() as $child) {
$renderer = $child->getRenderer(); $renderer = $child->getRenderer();
if ($renderer instanceof BadgeNavigationItemRenderer) { if ($renderer instanceof BadgeNavigationItemRenderer) {
if ($renderer->getState() === $this->getState()) { $count = $renderer->getCount();
$this->titles[] = $renderer->getTitle(); if ($count) {
$count += $renderer->getCount(); $severity = static::$stateSeverityMap[$renderer->getState()];
$countMap[$severity] += $count;
$titles[] = $renderer->getTitle();
$maxSeverity = max($maxSeverity, $severity);
} }
} }
} }
$this->count = $countMap[$maxSeverity];
$this->state = static::$severityStateMap[$maxSeverity];
$this->title = implode('. ', $titles);
}
return $count; return $this->count;
}
/**
* {@inheritdoc}
*/
public function getTitle()
{
return ! empty($this->titles) ? join(', ', $this->titles) : '';
} }
} }

View File

@ -12,6 +12,13 @@ use Icinga\Web\Response\JsonResponse;
*/ */
class Response extends Zend_Controller_Response_Http class Response extends Zend_Controller_Response_Http
{ {
/**
* The default content type being used for responses
*
* @var string
*/
const DEFAULT_CONTENT_TYPE = 'text/html; charset=UTF-8';
/** /**
* Auto-refresh interval * Auto-refresh interval
* *
@ -146,6 +153,31 @@ class Response extends Zend_Controller_Response_Http
return $this; return $this;
} }
/**
* Get an array of all header values for the given name
*
* @param string $name The name of the header
* @param bool $lastOnly If this is true, the last value will be returned as a string
*
* @return null|array|string
*/
public function getHeader($name, $lastOnly = false)
{
$result = ($lastOnly ? null : array());
$headers = $this->getHeaders();
foreach ($headers as $header) {
if ($header['name'] === $name) {
if ($lastOnly) {
$result = $header['value'];
} else {
$result[] = $header['value'];
}
}
}
return $result;
}
/** /**
* Get the request * Get the request
* *
@ -210,9 +242,11 @@ class Response extends Zend_Controller_Response_Http
* *
* @return JsonResponse * @return JsonResponse
*/ */
public static function json() public function json()
{ {
return new JsonResponse(); $response = new JsonResponse();
$response->copyMetaDataFrom($this);
return $response;
} }
/** /**
@ -242,6 +276,10 @@ class Response extends Zend_Controller_Response_Http
$this->setRedirect($redirectUrl->getAbsoluteUrl()); $this->setRedirect($redirectUrl->getAbsoluteUrl());
} }
} }
if (! $this->getHeader('Content-Type', true)) {
$this->setHeader('Content-Type', static::DEFAULT_CONTENT_TYPE);
}
} }
/** /**
@ -292,4 +330,20 @@ class Response extends Zend_Controller_Response_Http
} }
return parent::sendHeaders(); return parent::sendHeaders();
} }
/**
* Copies non-body-related response data from $response
*
* @param Response $response
*
* @return $this
*/
protected function copyMetaDataFrom(self $response)
{
$this->_headers = $response->_headers;
$this->_headersRaw = $response->_headersRaw;
$this->_httpResponseCode = $response->_httpResponseCode;
$this->headersSentThrowsException = $response->headersSentThrowsException;
return $this;
}
} }

View File

@ -11,6 +11,11 @@ use Icinga\Web\Response;
*/ */
class JsonResponse extends Response class JsonResponse extends Response
{ {
/**
* {@inheritdoc}
*/
const DEFAULT_CONTENT_TYPE = 'application/json';
/** /**
* Status identifier for failed API calls due to an error on the server * Status identifier for failed API calls due to an error on the server
* *
@ -121,7 +126,7 @@ class JsonResponse extends Response
*/ */
public function getFailData() public function getFailData()
{ {
return $this->failData; return (! is_array($this->failData) || empty($this->failData)) ? null : $this->failData;
} }
/** /**
@ -173,9 +178,11 @@ class JsonResponse extends Response
switch ($this->status) { switch ($this->status) {
case static::STATUS_ERROR: case static::STATUS_ERROR:
$body['message'] = $this->getErrorMessage(); $body['message'] = $this->getErrorMessage();
break;
case static::STATUS_FAIL: case static::STATUS_FAIL:
$body['data'] = $this->getFailData(); $failData = $this->getFailData();
if ($failData !== null || $this->status === static::STATUS_FAIL) {
$body['data'] = $failData;
}
break; break;
case static::STATUS_SUCCESS: case static::STATUS_SUCCESS:
$body['data'] = $this->getSuccessData(); $body['data'] = $this->getSuccessData();
@ -184,15 +191,6 @@ class JsonResponse extends Response
echo json_encode($body, $this->getEncodingOptions()); echo json_encode($body, $this->getEncodingOptions());
} }
/**
* {@inheritdoc}
*/
public function sendHeaders()
{
$this->setHeader('Content-Type', 'application/json', true);
parent::sendHeaders();
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */

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