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. */
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`.
* This should be the common name from the API certificate.
*/
@ -13,3 +23,4 @@ const NodeName = "localhost"
/* Our local zone name. */
const ZoneName = NodeName
const TicketSalt = ""

View File

@ -1,5 +1,25 @@
# 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
#### Feature
@ -14,8 +34,6 @@
* 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
## What's New
### What's New in Version 2.3.1
#### 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;
use Icinga\Application\Icinga;
use Icinga\Web\Announcement\AnnouncementCookie;
use Icinga\Web\Announcement\AnnouncementIniRepository;
use Icinga\Web\Controller;
use Icinga\Web\Session;
@ -14,6 +16,7 @@ class ApplicationStateController extends Controller
{
public function indexAction()
{
$this->_helper->layout()->disableLayout();
if (isset($_COOKIE['icingaweb2-session'])) {
$last = (int) $_COOKIE['icingaweb2-session'];
} else {
@ -34,6 +37,23 @@ class ApplicationStateController extends Controller
);
$_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;
use Exception;
use Icinga\Application\Version;
use InvalidArgumentException;
use Icinga\Application\Config;
use Icinga\Application\Icinga;
@ -122,6 +123,7 @@ class ConfigController extends Controller
$this->view->module = $module;
$this->view->tabs = $module->getConfigTabs()->activate('info');
$this->view->moduleGitCommitId = Version::getGitHead($module->getBaseDir());
} else {
$this->view->module = false;
$this->view->tabs = null;
@ -213,7 +215,7 @@ class ConfigController extends Controller
$form->setOnSuccess(function (UserBackendConfigForm $form) {
try {
$form->add(array_filter($form->getValues()));
$form->add($form::transformEmptyValuesToNull($form->getValues()));
} catch (Exception $e) {
$form->error($e->getMessage());
return false;
@ -244,12 +246,7 @@ class ConfigController extends Controller
$form->setIniConfig(Config::app('authentication'));
$form->setOnSuccess(function (UserBackendConfigForm $form) use ($backendName) {
try {
$form->edit($backendName, array_map(
function ($v) {
return $v !== '' ? $v : null;
},
$form->getValues()
));
$form->edit($backendName, $form::transformEmptyValuesToNull($form->getValues()));
} catch (Exception $e) {
$form->error($e->getMessage());
return false;
@ -393,10 +390,10 @@ class ConfigController extends Controller
$authConfig = Config::app('authentication');
foreach ($authConfig as $backendName => $config) {
if ($config->get('resource') === $resource) {
$form->addDescription(sprintf(
$form->warning(sprintf(
$this->translate(
'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.'
'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.'
),
$resource,
$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->render('resource/remove');
}

View File

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

View File

@ -20,4 +20,9 @@ class LayoutController extends ActionController
$this->_helper->layout()->disableLayout();
$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()
->add(
'preferences',
'account',
array(
'title' => $this->translate('Adjust the preferences of Icinga Web 2 according to your needs'),
'label' => $this->translate('Preferences'),
'url' => 'preference'
'title' => $this->translate('Update your account'),
'label' => $this->translate('My Account'),
'url' => 'account'
)
)
->add(
@ -219,7 +219,7 @@ class NavigationController extends Controller
$form->setDefaultUrl(rawurldecode($this->params->get('url', '')));
$form->setOnSuccess(function (NavigationConfigForm $form) {
$data = array_filter($form->getValues());
$data = $form::transformEmptyValuesToNull($form->getValues());
try {
$form->add($data);
@ -266,12 +266,7 @@ class NavigationController extends Controller
$form->setUserConfig(Config::navigation($itemType, $itemOwner));
$form->setRedirectUrl($referrer === 'shared' ? 'navigation/shared' : 'navigation');
$form->setOnSuccess(function (NavigationConfigForm $form) use ($itemName) {
$data = array_map(
function ($v) {
return $v !== '' ? $v : null;
},
$form->getValues()
);
$data = $form::transformEmptyValuesToNull($form->getValues());
try {
$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->setOnSuccess(function (UserGroupBackendForm $form) {
try {
$form->add(array_filter($form->getValues()));
$form->add($form::transformEmptyValuesToNull($form->getValues()));
} catch (Exception $e) {
$form->error($e->getMessage());
return false;
@ -73,12 +73,7 @@ class UsergroupbackendController extends Controller
$form->setIniConfig(Config::app('groups'));
$form->setOnSuccess(function (UserGroupBackendForm $form) use ($backendName) {
try {
$form->edit($backendName, array_map(
function ($v) {
return $v !== '' ? $v : null;
},
$form->getValues()
));
$form->edit($backendName, $form::transformEmptyValuesToNull($form->getValues()));
} catch (Exception $e) {
$form->error($e->getMessage());
return false;

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

@ -3,7 +3,7 @@ Font license info
## Font Awesome
Copyright (C) 2012 by Dave Gandy
Copyright (C) 2016 by Dave Gandy
Author: Dave Gandy
License: SIL ()
@ -19,15 +19,6 @@ Font license info
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
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.
- Usually, it's enough to publish content of LICENSE.txt file somewhere on your
site in "About" section.
- 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.
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",
"css": "reschedule",
"code": 59492,
"code": 59524,
"src": "entypo"
},
{
@ -744,12 +744,6 @@
"code": 59512,
"src": "typicons"
},
{
"uid": "b90d80c250a9bbdd6cd3fe00e6351710",
"css": "ok",
"code": 59395,
"src": "iconic"
},
{
"uid": "11e664deed5b2587456a4f9c01d720b6",
"css": "cancel",
@ -775,10 +769,52 @@
"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",
"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-plug:before { content: '\e862'; } /* '' */
.icon-eye-off:before { content: '\e863'; } /* '' */
.icon-reschedule:before { content: '\e864'; } /* '' */
.icon-arrows-cw:before { content: '\e864'; } /* '' */
.icon-cw:before { content: '\e865'; } /* '' */
.icon-host:before { content: '\e866'; } /* '' */
.icon-thumbs-up:before { content: '\e867'; } /* '' */
@ -128,3 +128,9 @@
.icon-twitter:before { content: '\e87e'; } /* '' */
.icon-facebook-squared:before { content: '\e87f'; } /* '' */
.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-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-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-host { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe866;&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-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-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-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-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-host { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe866;&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-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-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-family: 'ifont';
src: url('../font/ifont.eot?65389432');
src: url('../font/ifont.eot?65389432#iefix') format('embedded-opentype'),
url('../font/ifont.woff?65389432') format('woff'),
url('../font/ifont.ttf?65389432') format('truetype'),
url('../font/ifont.svg?65389432#ifont') format('svg');
src: url('../font/ifont.eot?38679513');
src: url('../font/ifont.eot?38679513#iefix') format('embedded-opentype'),
url('../font/ifont.woff2?38679513') format('woff2'),
url('../font/ifont.woff?38679513') format('woff'),
url('../font/ifont.ttf?38679513') format('truetype'),
url('../font/ifont.svg?38679513#ifont') format('svg');
font-weight: normal;
font-style: normal;
}
@ -14,7 +15,7 @@
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
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-plug:before { content: '\e862'; } /* '' */
.icon-eye-off:before { content: '\e863'; } /* '' */
.icon-reschedule:before { content: '\e864'; } /* '' */
.icon-arrows-cw:before { content: '\e864'; } /* '' */
.icon-cw:before { content: '\e865'; } /* '' */
.icon-host:before { content: '\e866'; } /* '' */
.icon-thumbs-up:before { content: '\e867'; } /* '' */
@ -183,3 +184,9 @@
.icon-twitter:before { content: '\e87e'; } /* '' */
.icon-facebook-squared:before { content: '\e87f'; } /* '' */
.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-family: 'ifont';
src: url('./font/ifont.eot?43849680');
src: url('./font/ifont.eot?43849680#iefix') format('embedded-opentype'),
url('./font/ifont.woff?43849680') format('woff'),
url('./font/ifont.ttf?43849680') format('truetype'),
url('./font/ifont.svg?43849680#ifont') format('svg');
src: url('./font/ifont.eot?54126565');
src: url('./font/ifont.eot?54126565#iefix') format('embedded-opentype'),
url('./font/ifont.woff?54126565') format('woff'),
url('./font/ifont.ttf?54126565') format('truetype'),
url('./font/ifont.svg?54126565#ifont') format('svg');
font-weight: 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>
<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: 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>
@ -494,6 +494,14 @@ body {
</div>
<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: 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 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',
'username',
array(
'required' => true,
'autocapitalize' => 'off',
'class' => false === isset($formData['username']) ? 'autofocus' : '',
'label' => $this->translate('Username'),
'class' => false === isset($formData['username']) ? 'autofocus' : ''
'required' => true
)
);
$this->addElement(

View File

@ -4,6 +4,8 @@
namespace Icinga\Forms\Config\General;
use Icinga\Application\Logger;
use Icinga\Application\Logger\Writer\SyslogWriter;
use Icinga\Application\Platform;
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
* this configuration.
*/
// $this->addElement(
// 'select',
// 'logging_facility',
// array(
// 'required' => true,
// 'label' => $this->translate('Facility'),
// 'description' => $this->translate('The syslog facility to utilize.'),
// 'multiOptions' => array(
// 'user' => 'LOG_USER'
// )
// )
// );
if (! isset($formData['logging_log']) || $formData['logging_log'] === 'syslog') {
if (Platform::isWindows()) {
/* @see https://secure.php.net/manual/en/function.openlog.php */
$this->addElement(
'hidden',
'logging_facility',
array(
'value' => 'user',
'disabled' => true
)
);
} 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') {
$this->addElement(
'text',

View File

@ -3,6 +3,7 @@
namespace Icinga\Forms\Config;
use Icinga\Application\Config;
use InvalidArgumentException;
use Icinga\Application\Platform;
use Icinga\Exception\ConfigurationError;
@ -21,6 +22,13 @@ use Icinga\Web\Notification;
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
*/
@ -104,6 +112,16 @@ class ResourceConfigForm extends ConfigForm
$this->config->removeSection($name);
unset($values['name']);
$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;
}
@ -163,10 +181,10 @@ class ResourceConfigForm extends ConfigForm
return false;
}
}
$this->add(array_filter($this->getValues()));
$this->add(static::transformEmptyValuesToNull($this->getValues()));
$message = $this->translate('Resource "%s" has been successfully created');
} 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');
}
} catch (InvalidArgumentException $e) {
@ -376,4 +394,15 @@ class ResourceConfigForm extends ConfigForm
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);
foreach ($backendConfig->toArray() as $k => $v) {
if ($v === null) {
unset($backendConfig->$k);
}
}
$this->config->setSection($name, $backendConfig);
return $this;
}

View File

@ -92,6 +92,8 @@ class LdapUserGroupBackendForm extends Form
$this->createGroupConfigElements($defaults, $groupConfigDisabled);
if (count($userBackends) === 1 || (isset($formData['user_backend']) && $formData['user_backend'] === 'none')) {
$this->createUserConfigElements($defaults, $userConfigDisabled);
} else {
$this->createHiddenUserConfigElements();
}
$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
*

View File

@ -127,14 +127,7 @@ class UserGroupBackendForm extends ConfigForm
unset($data['name']);
}
$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->merge($data));
return $this;
}

View File

@ -58,7 +58,7 @@ class ConfigForm extends Form
{
$sections = array();
foreach ($this->getValues() as $sectionAndPropertyName => $value) {
if ($value === '') {
if (empty($value)) {
$value = null; // Causes the config writer to unset it
}
@ -127,4 +127,16 @@ class ConfigForm extends Form
{
$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;
use Icinga\Web\Widget\Dashboard;
use Icinga\Web\Form;
use Icinga\Web\Form\Validator\InternalUrlValidator;
use Icinga\Web\Form\Validator\UrlValidator;
use Icinga\Web\Url;
use Icinga\Web\Widget\Dashboard;
use Icinga\Web\Widget\Dashboard\Dashlet;
/**
@ -70,7 +71,7 @@ class DashletForm extends Form
'description' => $this->translate(
'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(

View File

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

View File

@ -53,9 +53,27 @@ class NavigationItemForm extends Form
'allowEmpty' => true,
'label' => $this->translate('Url'),
'description' => $this->translate(
'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)'
'The url of this navigation item. Leave blank if only the name should be displayed.'
. ' For urls with username and password and for all external urls,'
. ' 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);
if (isset($values['url']) && $values['url']) {
$url = Url::fromPath($values['url']);
if (! $url->isExternal() && ($relativePath = $url->getRelativeUrl())) {
$values['url'] = $relativePath;
if ($url->getBasePath() === $this->getRequest()->getBasePath()) {
$values['url'] = $url->getRelativeUrl();
} else {
$values['url'] = $url->getAbsoluteUrl();
}
}

View File

@ -156,6 +156,16 @@ class PreferenceForm extends Form
*/
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)) {
$themes = Icinga::app()->getThemes();
if (count($themes) > 1) {

View File

@ -42,6 +42,11 @@ class RoleForm extends ConfigForm
'application/stacktraces' => $this->translate(
'Allow to adjust in the preferences whether to show 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/*)'
);
@ -284,7 +289,7 @@ class RoleForm extends ConfigForm
*/
public function getValues($suppressArrayNotation = false)
{
$values = array_filter(parent::getValues($suppressArrayNotation));
$values = static::transformEmptyValuesToNull(parent::getValues($suppressArrayNotation));
if (isset($values['permissions'])) {
$values['permissions'] = implode(', ', $values['permissions']);
}

View File

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

View File

@ -24,13 +24,14 @@ $innerLayoutScript = $this->layout()->innerLayout . '.phtml';
<html class="no-js<?= $iframeClass ?>" lang="<?= $lang ?>"> <!--<![endif]-->
<head>
<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 http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta http-equiv="cleartype" content="on">
<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="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): ?>
<base target="_parent"/>
<?php else: ?>
@ -47,6 +48,7 @@ $innerLayoutScript = $this->layout()->innerLayout . '.phtml';
<script src="<?= $this->baseUrl('js/vendor/respond.min.js');?>"></script>
<![endif]-->
<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>
<body id="body" class="loading">
<pre id="responsive-debug"></pre>

View File

@ -31,7 +31,7 @@ if ( isset($pdf) )
}
</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">
<table>
<tr>

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -7,16 +7,16 @@ msgid ""
msgstr ""
"Project-Id-Version: Icinga Web 2 (None)\n"
"Report-Msgid-Bugs-To: dev@icinga.org\n"
"POT-Creation-Date: 2016-02-29 14:39+0000\n"
"PO-Revision-Date: 2016-02-29 22:16+0100\n"
"Last-Translator: Thomas Gelf <thomas@gelf.net>\n"
"POT-Creation-Date: 2016-09-27 09:09+0000\n"
"PO-Revision-Date: 2016-09-27 11:15+0200\n"
"Last-Translator: Eric Lippmann <eric.lippmann@netways.de>\n"
"Language: de_DE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\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
#, php-format
@ -143,9 +143,8 @@ msgid "Add"
msgstr "Hinzufügen"
#: /vagrant/library/Icinga/Web/Widget/Tabextension/DashboardSettings.php:25
#, fuzzy
msgid "Add Dashlet"
msgstr "Dashlet bearbeiten"
msgstr "Dashlet hinzufügen"
#: /vagrant/application/controllers/DashboardController.php:76
msgid "Add Dashlet To Dashboard"
@ -164,11 +163,11 @@ msgstr "Zum Dashboard hinzufügen"
msgid "Add To Menu"
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"
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"
msgstr "Neue Gruppe anlegen"
@ -180,13 +179,13 @@ msgstr "Neue Gruppe anlegen"
msgid "Add a new user"
msgstr "Neuen Benutzer anlegen"
#: /vagrant/library/Icinga/Web/Widget/FilterEditor.php:409
#: /vagrant/library/Icinga/Web/Widget/FilterEditor.php:410
msgid "Add another filter"
msgstr "Weiteren Filter hinzufügen..."
msgstr "Weiteren Filter hinzufügen"
#: /vagrant/library/Icinga/Web/Widget/FilterWidget.php:80
msgid "Add filter..."
msgstr "Filter hinzufügen..."
msgstr "Filter hinzufügen"
#: /vagrant/application/forms/Config/UserGroup/AddMemberForm.php:125
#, php-format
@ -231,6 +230,10 @@ msgstr "Erlaubt die Einstellung zur Anzeige von Stacktraces"
msgid "Allow to share navigation items"
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
msgid ""
"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."
#: /vagrant/library/Icinga/Application/Web.php:318
#: /vagrant/library/Icinga/Web/Menu.php:273
#: /vagrant/library/Icinga/Web/Menu.php:274
msgid "Application"
msgstr "Anwendung"
@ -268,7 +271,7 @@ msgstr "Anwenden"
#: /vagrant/application/controllers/ConfigController.php:50
#: /vagrant/library/Icinga/Application/Web.php:324
#: /vagrant/library/Icinga/Web/Menu.php:278
#: /vagrant/library/Icinga/Web/Menu.php:279
msgid "Authentication"
msgstr "Authentifizierung"
@ -365,7 +368,7 @@ msgstr "Abbrechen"
msgid "Cancel this membership"
msgstr "Diese Mitgliedschaft beenden"
#: /vagrant/library/Icinga/Web/Widget/FilterEditor.php:435
#: /vagrant/library/Icinga/Web/Widget/FilterEditor.php:436
msgid "Cancel this operation"
msgstr "Diese Operation abbrechen"
@ -446,7 +449,7 @@ msgstr ""
msgid "Click to remove this part of your filter"
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
msgid "Column \"%s\" cannot be queried"
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"
#: /vagrant/library/Icinga/Application/Web.php:312
#: /vagrant/library/Icinga/Web/Menu.php:268
#: /vagrant/library/Icinga/Web/Menu.php:269
msgid "Configuration"
msgstr "Konfiguration"
@ -486,9 +489,8 @@ msgstr ""
"2"
#: /vagrant/application/controllers/ConfigController.php:49
#, fuzzy
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
msgid "Configure which resources are being utilized by Icinga Web 2"
@ -522,7 +524,7 @@ msgid ""
"modules."
msgstr ""
"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 "
"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 "
"einigen der verfügbaren %s behoben werden."
#: /vagrant/application/controllers/DashboardController.php:257
#: /vagrant/application/forms/Dashboard/DashletForm.php:120
#: /vagrant/library/Icinga/Application/Web.php:290
#: /vagrant/library/Icinga/Web/Menu.php:243
@ -712,10 +715,9 @@ msgid "Debug"
msgstr "Debug"
#: /vagrant/application/forms/Config/General/ThemingConfigForm.php:42
#, fuzzy
msgctxt "Form element label"
msgid "Default Theme"
msgstr "Standardsprache"
msgstr "Standard-Theme"
#: /vagrant/application/views/scripts/config/module.phtml:46
msgid "Dependencies"
@ -742,6 +744,14 @@ msgstr "Automatische Aktualisierung deaktivieren"
msgid "Disable the %s module"
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
msgctxt "A button to discover LDAP capabilities"
msgid "Discover"
@ -756,11 +766,10 @@ msgid "Edit Dashlet"
msgstr "Dashlet bearbeiten"
#: /vagrant/application/views/scripts/user/show.phtml:16
#, fuzzy
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
msgid "Edit dashlet %s"
msgstr "Dashlet %s bearbeiten"
@ -831,7 +840,6 @@ msgid "Enter a title for the dashlet."
msgstr "Titel für das Dashlet eingeben"
#: /vagrant/application/forms/Dashboard/DashletForm.php:111
#, fuzzy
msgid "Enter a title for the new dashboard"
msgstr "Titel für das Dashboard eingeben"
@ -844,7 +852,6 @@ msgstr ""
"URLs mit Filtern möglich."
#: /vagrant/application/controllers/ErrorController.php:109
#, fuzzy
msgid "Error"
msgstr "Fehler"
@ -981,17 +988,17 @@ msgstr "Dateipfad"
msgid "Filter Pattern"
msgstr "Muster"
#: /vagrant/library/Icinga/Repository/Repository.php:1066
#: /vagrant/library/Icinga/Repository/Repository.php:1067
#, php-format
msgid "Filter column \"%s\" not found"
msgstr "Filterspalte “%s” nicht gefunden"
#: /vagrant/library/Icinga/Repository/Repository.php:1070
#: /vagrant/library/Icinga/Repository/Repository.php:1071
#, php-format
msgid "Filter column \"%s\" not found in table \"%s\""
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
msgid "Filter this list"
msgstr "Diese Liste filtern"
@ -1110,7 +1117,7 @@ msgstr "Ich bin bereit zur Suche. Warte auf Eingabe"
msgid "Icinga Documentation"
msgstr "Icinga Dokumentation"
#: /vagrant/application/views/scripts/authentication/login.phtml:11
#: /vagrant/application/views/scripts/authentication/login.phtml:18
msgid "Icinga Web 2 Documentation"
msgstr "Icinga Web 2 Dokumentation"
@ -1118,7 +1125,7 @@ msgstr "Icinga Web 2 Dokumentation"
msgid "Icinga Web 2 Login"
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"
msgstr "Icinga Web 2 Einrichtungsassistent"
@ -1127,18 +1134,17 @@ msgid "Icinga Wiki"
msgstr "Icinga Wiki"
#: /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"
msgstr "Icinga auf Facebook"
#: /vagrant/application/views/scripts/about/index.phtml:64
#: /vagrant/application/views/scripts/authentication/login.phtml:46
#, fuzzy
#: /vagrant/application/views/scripts/authentication/login.phtml:53
msgid "Icinga on Google+"
msgstr "Icinga auf Facebook"
msgstr "Icinga auf Google+"
#: /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"
msgstr "Icinga auf Twitter"
@ -1188,7 +1194,7 @@ msgstr "Ungültiger Backendtyp “%s” angegeben"
msgid "Invalid resource type \"%s\" provided"
msgstr "Ungültiger Ressourcentyp “%s” angegeben"
#: /vagrant/application/views/scripts/authentication/login.phtml:7
#: /vagrant/application/views/scripts/authentication/login.phtml:14
#, php-format
msgid ""
"It appears that you did not configure Icinga Web 2 yet so it's not possible "
@ -1322,7 +1328,7 @@ msgid "Login"
msgstr "Anmelden"
#: /vagrant/library/Icinga/Application/Web.php:355
#: /vagrant/library/Icinga/Web/Menu.php:313
#: /vagrant/library/Icinga/Web/Menu.php:314
msgid "Logout"
msgstr "Abmelden"
@ -1356,7 +1362,7 @@ msgstr "Menüeintrag"
msgid "Method Not Allowed"
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
msgid "Modify this filter"
msgstr "Diesen Filter bearbeiten"
@ -1396,7 +1402,7 @@ msgstr "Pfad zu den Modulen"
#: /vagrant/application/controllers/ConfigController.php:96
#: /vagrant/library/Icinga/Application/Web.php:336
#: /vagrant/library/Icinga/Web/Menu.php:298
#: /vagrant/library/Icinga/Web/Menu.php:299
msgid "Modules"
msgstr "Module"
@ -1473,9 +1479,8 @@ msgid "New Dashboard Title"
msgstr "Neuen Dashboardtitel vergeben"
#: /vagrant/application/controllers/DashboardController.php:42
#, fuzzy
msgid "New Dashlet"
msgstr "Dashlet aktualisieren"
msgstr "Neues Dashlet"
#: /vagrant/application/controllers/NavigationController.php:244
msgid "New Navigation Item"
@ -1550,15 +1555,15 @@ msgstr ""
"Keine Authentifizierungsmethode verfügbar. Wurde bei der Installation von "
"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"
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"
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"
msgstr "Dem Dashboard wurden keine Dashlets hinzugefügt"
@ -1577,13 +1582,13 @@ msgstr ""
msgid "No roles found."
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"
msgstr ""
"Es wurden keine Benutzergruppen gefunden, welche den Filterkriterien "
"entsprechen"
#: /vagrant/application/views/scripts/user/list.phtml:46
#: /vagrant/application/views/scripts/user/list.phtml:44
msgid "No users found matching the filter"
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/PreferenceController.php:33
#: /vagrant/library/Icinga/Application/Web.php:350
#: /vagrant/library/Icinga/Web/Menu.php:308
#: /vagrant/library/Icinga/Web/Menu.php:309
msgid "Preferences"
msgstr "Einstellungen"
@ -1768,12 +1773,12 @@ msgid "Push to fill in the chosen connection's default settings."
msgstr ""
"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
msgid "Query column \"%s\" not found"
msgstr "Abfragespalte “%s” wurde nicht gefunden"
#: /vagrant/library/Icinga/Repository/Repository.php:1026
#: /vagrant/library/Icinga/Repository/Repository.php:1027
#, php-format
msgid "Query column \"%s\" not found in table \"%s\""
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"
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/user/list.phtml:55
#: /vagrant/application/views/scripts/user/list.phtml:53
msgid "Remove"
msgstr "Entfernen"
@ -1793,9 +1798,8 @@ msgid "Remove Dashboard"
msgstr "Dashboard entfernen"
#: /vagrant/application/controllers/DashboardController.php:158
#, fuzzy
msgid "Remove Dashlet"
msgstr "Dashboard entfernen"
msgstr "Dashlet entfernen"
#: /vagrant/application/controllers/DashboardController.php:192
msgid "Remove Dashlet From Dashboard"
@ -1830,7 +1834,7 @@ msgstr "Benutzergruppe entfernen"
msgid "Remove User Group Backend"
msgstr "Backend für Benutzergruppen entfernen"
#: /vagrant/application/views/scripts/dashboard/settings.phtml:71
#: /vagrant/application/views/scripts/dashboard/settings.phtml:72
#, php-format
msgid "Remove dashlet %s from pane %s"
msgstr "Dashlet %s aus Dashboard %s entfernen"
@ -1845,7 +1849,7 @@ msgstr "Gruppe %s entfernen"
msgid "Remove navigation item %s"
msgstr "Navigationselement %s entfernen"
#: /vagrant/application/views/scripts/dashboard/settings.phtml:32
#: /vagrant/application/views/scripts/dashboard/settings.phtml:33
#, php-format
msgid "Remove pane %s"
msgstr "Dashboard %s entfernen"
@ -1873,11 +1877,11 @@ msgstr "Diesen Filter entfernen"
msgid "Remove this member"
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"
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
msgid "Remove user %s"
msgstr "Benutzer %s entfernen"
@ -1892,7 +1896,7 @@ msgstr "Benutzer %s entfernen"
msgid "Remove user backend %s"
msgstr "Benutzerbackend %s entfernen"
#: /vagrant/application/views/scripts/group/list.phtml:88
#: /vagrant/application/views/scripts/group/list.phtml:86
#, php-format
msgid "Remove user group %s"
msgstr "Benutzergruppe %s entfernen"
@ -1998,7 +2002,7 @@ msgstr "Rolle aktualisiert"
#: /vagrant/application/controllers/GroupController.php:344
#: /vagrant/application/controllers/RoleController.php:153
#: /vagrant/application/controllers/UserController.php:310
#: /vagrant/library/Icinga/Web/Menu.php:283
#: /vagrant/library/Icinga/Web/Menu.php:284
msgid "Roles"
msgstr "Rollen"
@ -2053,7 +2057,7 @@ msgstr "Domains durchsuchen"
msgid "Search this domain for records of available servers."
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..."
msgstr "Suche..."
@ -2062,9 +2066,8 @@ msgid "Searching"
msgstr "Suche"
#: /vagrant/application/forms/Dashboard/DashletForm.php:122
#, fuzzy
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
#, php-format
@ -2117,7 +2120,7 @@ msgstr "Freigegeben"
msgid "Shared Navigation"
msgstr "Freigegebene Navigation"
#: /vagrant/library/Icinga/Web/Widget/Dashboard.php:238
#: /vagrant/library/Icinga/Web/Widget/Dashboard.php:235
#, php-format
msgctxt "dashboard.pane.tooltip"
msgid "Show %s"
@ -2133,13 +2136,13 @@ msgstr "Suche anzeigen"
msgid "Show Stacktraces"
msgstr "Stacktrace anzeigen"
#: /vagrant/application/views/scripts/dashboard/settings.phtml:61
#: /vagrant/application/views/scripts/dashboard/settings.phtml:62
#, php-format
msgid "Show dashlet %s"
msgstr "Dashlet %s anzeigen"
#: /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
msgid "Show detailed information about %s"
msgstr "Zeige detaillierte Informationen über %s"
@ -2149,7 +2152,7 @@ msgstr "Zeige detaillierte Informationen über %s"
msgid "Show detailed information for group %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
msgid "Show detailed information for user group %s"
msgstr "Zeige detaillierte Informationen über die Benutzergruppe %s"
@ -2206,7 +2209,7 @@ msgstr "Sortieren nach"
msgid "State"
msgstr "Status"
#: /vagrant/library/Icinga/Web/Widget/FilterEditor.php:422
#: /vagrant/library/Icinga/Web/Widget/FilterEditor.php:423
msgid "Strip this filter"
msgstr "Diesen Filter bearbeiten"
@ -2224,7 +2227,7 @@ msgid "Target"
msgstr "Ziel"
#: /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"
msgstr "Das Icinga Projekt"
@ -2296,7 +2299,7 @@ msgstr ""
#: /vagrant/application/forms/Config/General/ThemingConfigForm.php:40
msgctxt "Form element description"
msgid "The default theme"
msgstr "Standardtheme"
msgstr "Standard-Theme"
#: /vagrant/application/views/scripts/showConfiguration.phtml:5
#, php-format
@ -2561,14 +2564,12 @@ msgstr "Der eindeutige Name dieser Ressource"
#: /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)"
"The url of this navigation item. Leave blank if only the name should be displayed. For urls with username and password and "
"for all external urls, make sure to prepend an appropriate protocol identifier (e.g. http://example.tld)"
msgstr ""
"Die URL dieses Navigationselementes. Dies kann leer gelassen werden, wenn "
"nur der Name angezeigt werden soll. Stellen Sie für externe URLs sicher, "
"dass Sie einen passenden Protokollnamen voran stellen (z.B. http://example."
"tld)"
"Die URL dieses Navigationselementes. Dies kann leer gelassen werden, wenn nur der Name angezeigt werden soll. Stelle für URLs "
"mit Benutzername und Passwort sowie alle externen URLs sicher, dass eine passender Protokollnamen voran gestellt ist (z.B. "
"http://example.tld)"
#: /vagrant/application/forms/Navigation/DashletForm.php:29
msgid ""
@ -2600,7 +2601,7 @@ msgstr "Der für die Authentifizierung zu benutzende Benutzername."
#: /vagrant/application/forms/PreferenceForm.php:170
msgctxt "Form element label"
msgid "Theme"
msgstr ""
msgstr "Theme"
#: /vagrant/application/views/scripts/navigation/shared.phtml:15
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"
#: /vagrant/library/Icinga/Application/Modules/Module.php:721
#: /vagrant/library/Icinga/Application/Modules/ModuleInfo.php:13
msgid "This module has no description"
msgstr "Dieses Modul hat keine Beschreibung"
@ -2651,6 +2653,14 @@ msgid "Tick this box to share this item with others"
msgstr ""
"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
msgid "To modify the private key you must recreate this resource."
msgstr ""
@ -2708,6 +2718,10 @@ msgstr "Freigabe aufheben"
msgid "Unshare this navigation item"
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:92
msgid "Update Dashlet"
@ -2752,8 +2766,7 @@ msgstr ""
#: /vagrant/library/Icinga/Web/Form.php:951
#: /vagrant/library/Icinga/Web/Form/Decorator/Autosubmit.php:100
msgid ""
"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."
msgstr ""
"Dieses Feld löst eine automatische Aktualisierung dieser Seite aus, wenn "
"sein Inhalt geändert wird."
@ -2821,7 +2834,7 @@ msgstr "Backends für Benutzer"
#: /vagrant/application/controllers/GroupController.php:69
#: /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:120
#: /vagrant/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php:465
@ -2833,7 +2846,6 @@ msgid "User Group Backend"
msgstr "Backend für Benutzergruppen"
#: /vagrant/application/views/scripts/config/userbackend/reorder.phtml:19
#, fuzzy
msgid "User Group Backends"
msgstr "Backend für Benutzergruppen"
@ -2905,7 +2917,7 @@ msgstr ""
"soll. Bitte beachten Sie, dass für diesen Benutzer ein schlüsselbasierter "
"Zugriff möglich sein muss."
#: /vagrant/library/Icinga/Web/Menu.php:293
#: /vagrant/library/Icinga/Web/Menu.php:294
msgid "Usergroups"
msgstr "Benutzergruppen"
@ -2915,7 +2927,7 @@ msgstr "Benutzergruppen"
#: /vagrant/application/forms/Config/Resource/DbResourceForm.php:127
#: /vagrant/application/forms/Config/User/UserForm.php:33
#: /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:118
#: /vagrant/library/Icinga/Authentication/User/LdapUserBackend.php:255
@ -2932,7 +2944,7 @@ msgstr "Benutzername"
#: /vagrant/application/forms/Navigation/NavigationConfigForm.php:613
#: /vagrant/application/forms/Security/RoleForm.php:117
#: /vagrant/application/views/scripts/role/list.phtml:23
#: /vagrant/library/Icinga/Web/Menu.php:288
#: /vagrant/library/Icinga/Web/Menu.php:289
msgid "Users"
msgstr "Benutzer"
@ -2967,6 +2979,17 @@ msgctxt "app.config.logging.level"
msgid "Warning"
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
msgid "Welcome to Icinga Web!"
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"
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
msgid ""
"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/Decorator/Autosubmit.php:100
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."
msgstr "Cambiando questo valore la pagina verrà ricaricata automaticamente."

View File

@ -2417,16 +2417,6 @@ msgstr ""
msgid "The unique name of this resource"
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
msgid ""
"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/Decorator/Autosubmit.php:100
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."
msgstr "В случае изменения значения поля страница будет обновлена"

View File

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

View File

@ -37,10 +37,20 @@
</td>
<tr>
<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>
<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>
<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());
if ($searchDashboard->search('dummy')->getPane('search')->hasDashlets()): ?>
<form action="<?= $this->href('search') ?>" method="get" role="search">
<input
type="text" name="q" id="search" class="search" placeholder="<?= $this->translate('Search') ?> &hellip;"
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
/>
<form action="<?= $this->href('search') ?>" method="get" role="search" class="search-control">
<input type="text" name="q" id="search" class="search search-input"
placeholder="<?= $this->translate('Search') ?> &hellip;"
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" required="required">
<button class="search-reset icon-cancel" type="reset"></button>
</form>
<?php endif; ?>
<?= $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
# 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
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):
if args.links:
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:
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:
if args.html:
return "<li>%s %d: %s</li>" % log_entry
return "<li>%s %d (%s): %s</li>" % log_entry
else:
return "* %s %d: %s" % log_entry
return "* %s %d (%s): %s" % log_entry
def print_category(category, entries):
if len(entries) > 0:
@ -60,7 +60,10 @@ def print_category(category, entries):
if args.html:
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)
if args.html:
@ -108,9 +111,10 @@ if changes:
offset = 0
features = []
bugfixes = []
support = []
features = {}
bugfixes = {}
support = {}
category = ""
while True:
# 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:
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":
features.append(entry)
try:
features[category].append(entry)
except KeyError:
features[category] = [ entry ]
elif issue["tracker"]["name"] == "Bug":
bugfixes.append(entry)
try:
bugfixes[category].append(entry)
except KeyError:
bugfixes[category] = [ entry ]
elif issue["tracker"]["name"] == "Support":
support.append(entry)
try:
support[category].append(entry)
except KeyError:
support[category] = [ entry ]
print_category("Feature", features)
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.
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
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
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).
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.
## <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.
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.
## <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.
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.
## <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
* The IDO table prefix must be icinga_ which is the default
* 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
@ -35,16 +36,16 @@ pagespeed Disallow "*/icingaweb2/*";
Below is a list of official package repositories for installing Icinga Web 2 for various operating systems.
Distribution | Repository
------------------------|---------------------------
Debian | [debmon](http://debmon.org/packages/debmon-wheezy/icingaweb2), [Icinga Repository](http://packages.icinga.org/debian/)
Ubuntu | [Icinga Repository](http://packages.icinga.org/ubuntu/)
RHEL/CentOS | [Icinga Repository](http://packages.icinga.org/epel/)
openSUSE | [Icinga Repository](http://packages.icinga.org/openSUSE/)
SLES | [Icinga Repository](http://packages.icinga.org/SUSE/)
Gentoo | -
FreeBSD | -
ArchLinux | [Upstream](https://aur.archlinux.org/packages/icingaweb2)
| Distribution | Repository |
| ------------- | ---------- |
| Debian | [Icinga Repository](http://packages.icinga.org/debian/) |
| Ubuntu | [Icinga Repository](http://packages.icinga.org/ubuntu/) |
| RHEL/CentOS | [Icinga Repository](http://packages.icinga.org/epel/) |
| openSUSE | [Icinga Repository](http://packages.icinga.org/openSUSE/) |
| SLES | [Icinga Repository](http://packages.icinga.org/SUSE/) |
| Gentoo | [Upstream](https://packages.gentoo.org/packages/www-apps/icingaweb2) |
| FreeBSD | [Upstream](http://portsmon.freebsd.org/portoverview.py?category=net-mgmt&portname=icingaweb2) |
| ArchLinux | [Upstream](https://aur.archlinux.org/packages/icingaweb2) |
Packages for distributions other than the ones listed above may also be available.
Please contact your distribution packagers.
@ -52,23 +53,29 @@ Please contact your distribution packagers.
### <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.
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)**:
```
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**:
**Debian Jessie**:
```
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
```
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**:
```
@ -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
[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
> 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/).
> Please note that installing Icinga Web 2 on **RHEL/CentOS 5** is not supported due to EOL versions of PHP and PostgreSQL.
### <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
```
For Debian wheezy please read the [package repositories notes](#package-repositories-wheezy-notes).
**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`.
# <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.
## <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
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`.
Users who changed the configuration manually and used the option `filter` instead
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
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**.
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:
@ -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
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
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,
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.
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`.

View File

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

View File

@ -1,12 +1,12 @@
# <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
different files, when the information about a data source changes.
## <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.
The available data source types are *db*, *ldap*, *ssh* and *livestatus* which will described in detail in the following
paragraphs.
@ -16,19 +16,19 @@ paragraphs.
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.
Directive | Description
----------------|------------
**type** | `db`
**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.
**port** | Port number to use. Mandatory for connections to a PostgreSQL database.
**username** | The username to use when connecting to the server.
**password** | The password to use when connecting to the server.
**dbname** | The database to use.
| Directive | Description |
| ------------- | ----------- |
| **type** | `db` |
| **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. |
| **port** | Port number to use. Mandatory for connections to a PostgreSQL database. |
| **username** | The username to use when connecting to the server. |
| **password** | The password to use when connecting to the server. |
| **dbname** | The database to use. |
**Example:**
#### <a id="resources-configuration-database-example"></a> Example
````
```
[icingaweb-mysql-tcp]
type = db
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.
Directive | Description
----------------|------------
**type** | `ldap`
**hostname** | Connect to the LDAP server on the given host.
**port** | Port number to use for the connection.
**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_pw** | The password to use when connecting to the server.
| Directive | Description |
| ----------------- | ----------- |
| **type** | `ldap` |
| **hostname** | Connect to the LDAP server on the given host. |
| **port** | Port number to use for the connection. |
| **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_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]
type = ldap
hostname = localhost
port = 389
root_dn = "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 SSH resource contains the information about the user and the private key location, which can be used for the key-based
ssh authentication.
Directive | Description
--------------------|------------
**type** | `ssh`
**user** | The username to use when connecting to the server.
**private_key** | The path to the private key of the user.
| Directive | Description |
| ----------------- | ----------- |
| **type** | `ssh` |
| **user** | The username to use when connecting to the server. |
| **private_key** | The path to the private key of the user. |
**Example:**
#### <a id="resources-configuration-ssh-example"></a> Example
```
````
[ssh]
type = "ssh"
user = "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
Directive | Description
------------------------|------------
**backend** | `ldap`
**resource** | The name of the LDAP resource defined in [resources.ini](04-Resources.md#resources).
**user_class** | LDAP user class.
**user_name_attribute** | LDAP attribute which contains the username.
**filter** | LDAP search filter.
| Directive | Description |
| ------------------------- | ----------- |
| **backend** | `ldap` |
| **resource** | The name of the LDAP resource defined in [resources.ini](04-Resources.md#resources). |
| **user_class** | LDAP user class. |
| **user_name_attribute** | LDAP attribute which contains the username. |
| **filter** | LDAP search filter. |
**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
Directive | Description
------------------------|------------
**backend** | `msldap`
**resource** | The name of the LDAP resource defined in [resources.ini](04-Resources.md#resources).
| Directive | Description |
| ------------- | ----------- |
| **backend** | `msldap` |
| **resource** | The name of the LDAP resource defined in [resources.ini](04-Resources.md#resources). |
**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
authentication method.
Directive | Description
------------------------|------------
**backend** | `db`
**resource** | The name of the database resource defined in [resources.ini](04-Resources.md#resources).
| Directive | Description |
| ------------------------| ----------- |
| **backend** | `db` |
| **resource** | The name of the database resource defined in [resources.ini](04-Resources.md#resources). |
**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
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
and restrictions with **users** and **groups**. There are two general kinds of
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,
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
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.
@ -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
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
**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.
</div>
#### Managing Users
#### <a id="security-basics-users-managing"></a>Managing Users
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
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
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).
#### 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),
it is possible to manage groups and group memberships directly in the frontend. This configuration
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
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
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
to the page **Configuration > Authentication > Roles**, or through editing the
configuration file:
/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:
@ -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:
Directive | Description
---------------------------|-----------------------------------------------------------------------------
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
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
| Directive | Description |
| ----------------------------- | ----------- |
| **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 |
| **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 |
@ -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
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
--------------------------|--------------------------------------------------------
* | Allow everything, including module-specific permissions
config/* | Allow all configuration actions
config/modules | Allow enabling or disabling modules
module/&lt;moduleName&gt; | Allow access to module &lt;moduleName&gt;
| Name | Permits |
| ----------------------------- | ------------ |
| **\*** | allow everything, including module-specific permissions |
| **config/\*** | allow all configuration actions |
| **config/modules** | allow enabling or disabling modules |
| **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
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
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
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
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]
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.
#### Example 2: Hostgroups
#### <a id="restrictions-filter-example2"></a>Example 2: Hostgroups
[unix-server]
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]
type = ini
````
```
### <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)
which will be referenced as resource for the preferences storage.
Directive | Description
------------------------|------------
**type** | `db`
**resource** | The name of the database resource defined in [resources.ini](04-Resources.md#resources).
| Directive | Description |
| ------------- | ----------- |
| **type** | `db` |
| **resource** | The name of the database resource defined in [resources.ini](04-Resources.md#resources). |
**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
* VirtualBox or Parallels Desktop
@ -13,7 +17,7 @@ Parallels requires the additional provider plugin
$ 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 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
have to do is install Vagrant and run:
````
```
vagrant up
````
```
> **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
@ -31,7 +35,7 @@ vagrant up
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:
@ -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.
In order to run all tests you have to execute the following command:
````
```
vagrant ssh -c "icingacli test php unit"
````
```

View File

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

View File

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

View File

@ -4,7 +4,8 @@
namespace Icinga\Application;
use Exception;
use Icinga\Application\Logger;
use Icinga\Authentication\Auth;
use Icinga\Application\Modules\Manager;
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
*
@ -213,17 +254,19 @@ class Hook
public static function all($name)
{
$name = self::normalizeHookName($name);
if (!self::has($name)) {
if (! self::has($name)) {
return array();
}
foreach (self::$hooks[$name] as $key => $hook) {
if (self::hasPermission($key)) {
if (self::createInstance($name, $key) === null) {
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);
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\Application\Logger;
use Icinga\Application\Logger\LogWriter;
use Icinga\Exception\ConfigurationError;
/**
* Log to the syslog service
@ -32,7 +33,15 @@ class SyslogWriter extends LogWriter
* @var 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)
{
$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);
$target = $this->installedBaseDirs[$name];
$link = $this->enableDir . DIRECTORY_SEPARATOR . $name;

View File

@ -362,25 +362,28 @@ class Module
public function getMenu()
{
$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
*
* @return Navigation
* @return array
*/
private function createMenu(array $items)
{
$navigation = new Navigation();
$navigation = array();
foreach ($items as $item) {
/** @var MenuItemContainer $item */
$navigationItem = $navigation->createItem($item->getName(), $item->getProperties());
$navigationItem->setChildren($this->createMenu($item->getChildren()));
$navigationItem->setLabel($this->translate($item->getName()));
$navigation->addItem($navigationItem);
$properties = $item->getProperties();
$properties['children'] = $this->createMenu($item->getChildren());
if (! isset($properties['label'])) {
$properties['label'] = $this->translate($item->getName());
}
$navigation[$item->getName()] = $properties;
}
return $navigation;
@ -671,6 +674,8 @@ class Module
$metadata->description .= $line;
continue;
}
} elseif (empty($line)) {
continue;
}
list($key, $val) = preg_split('/:\s+/', $line, 2);

View File

@ -8,7 +8,7 @@ namespace Icinga\Application;
*/
class Version
{
const VERSION = '2.3.2';
const VERSION = '2.3.4';
/**
* Get the version of this instance of Icinga Web 2
@ -25,24 +25,38 @@ class Version
}
}
$gitDir = Icinga::app()->getBaseDir('.git');
$gitHead = @file_get_contents($gitDir . DIRECTORY_SEPARATOR . 'HEAD');
if (false !== $gitHead) {
$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);
}
}
$gitCommitId = static::getGitHead(Icinga::app()->getBaseDir());
if ($gitCommitId !== false) {
$version['gitCommitID'] = $gitCommitId;
}
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(
'label' => t('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',
'priority' => 900,
'children' => array(
'preferences' => array(
'label' => t('Preferences'),
'account' => array(
'label' => t('My Account'),
'priority' => 100,
'url' => 'preference'
'url' => 'account'
),
'logout' => array(
'label' => t('Logout'),
@ -366,7 +371,7 @@ class Web extends EmbeddedWeb
'label' => t('Application Log'),
'url' => 'list/applicationlog',
'permission' => 'application/log',
'priority' => 710
'priority' => 900
);
}
} else {
@ -411,6 +416,9 @@ class Web extends EmbeddedWeb
private function setupUser()
{
$auth = Auth::getInstance();
if (! $this->request->isXmlHttpRequest() && $this->request->isApiRequest() && ! $auth->isAuthenticated()) {
$auth->authHttp();
}
if ($auth->isAuthenticated()) {
$user = $auth->getUser();
$this->getRequest()->setUser($user);

View File

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

View File

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

View File

@ -3,6 +3,7 @@
namespace Icinga\Authentication\User;
use Icinga\Application\Logger;
use Icinga\Data\ConfigObject;
use Icinga\User;
@ -11,6 +12,13 @@ use Icinga\User;
*/
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
*
@ -55,7 +63,7 @@ class ExternalBackend implements UserBackendInterface
/**
* 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
*/
@ -65,29 +73,46 @@ class ExternalBackend implements UserBackendInterface
if ($username !== false) {
return $username;
}
if (array_key_exists($variable, $_SERVER)) {
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}
*/
public function authenticate(User $user, $password = null)
{
$username = static::getRemoteUser();
list($username, $field) = static::getRemoteUserInformation();
if ($username !== null) {
$user->setExternalUserInformation($username, 'REMOTE_USER');
$user->setExternalUserInformation($username, $field);
if ($this->stripUsernameRegexp) {
$stripped = preg_replace($this->stripUsernameRegexp, '', $username);
if ($stripped !== false) {
// TODO(el): PHP issues a warning when PHP cannot compile the regular expression. Should we log an
// additional message in that case?
$username = $stripped;
$stripped = @preg_replace($this->stripUsernameRegexp, '', $username);
if ($stripped === false) {
Logger::error('Failed to strip external username. The configured regular expression is invalid.');
return false;
}
$username = $stripped;
}
$user->setUsername($username);

View File

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

View File

@ -9,8 +9,8 @@ use Icinga\Data\Inspection;
use PDO;
use Iterator;
use Zend_Db;
use Zend_Db_Expr;
use Icinga\Data\ConfigObject;
use Icinga\Data\Db\DbQuery;
use Icinga\Data\Extensible;
use Icinga\Data\Filter\Filter;
use Icinga\Data\Filter\FilterAnd;
@ -462,7 +462,7 @@ class DbConnection implements Selectable, Extensible, Updatable, Reducible, Insp
if ($sign === '=') {
return $column . ' IN (' . $this->dbAdapter->quote($value) . ')';
} 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(
@ -470,10 +470,10 @@ class DbConnection implements Selectable, Extensible, Updatable, Reducible, Insp
);
} elseif ($sign === '=' && strpos($value, '*') !== false) {
if ($value === '*') {
// We'll ignore such filters as it prevents index usage and because "*" means anything, anything means
// 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 '';
// We'll ignore such filters as it prevents index usage and because "*" means anything, so whether we're
// using a real column with a valid comparison here or just an expression which can only be evaluated to
// true makes no difference, except for performance reasons...
return new Zend_Db_Expr('TRUE');
}
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
// 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->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 {
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;
use Exception;
use Zend_Db_Expr;
use Zend_Db_Select;
use Icinga\Application\Logger;
use Icinga\Data\Filter\FilterAnd;
@ -300,30 +301,30 @@ class DbQuery extends SimpleQuery
if ($sign === '=') {
return $col . ' IN (' . $this->escapeForSql($expression) . ')';
} 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');
} elseif ($sign === '=' && strpos($expression, '*') !== false) {
if ($expression === '*') {
// We'll ignore such filters as it prevents index usage and because "*" means anything, anything means
// 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 new Zend_Db_Expr('TRUE');
}
return $col . ' LIKE ' . $this->escapeForSql($this->escapeWildcards($expression));
} elseif ($sign === '!=' && strpos($expression, '*') !== false) {
if ($expression === '*') {
// 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
// true makes no difference, except for performance reasons...
return $this->escapeForSql(0);
return new Zend_Db_Expr('FALSE');
}
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 {
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()
{
if ($this->isBooleanTrue()) {
return $this->column;
}
$expression = is_array($this->expression) ?
'( ' . implode(' | ', $this->expression) . ' )' :
$this->expression;
@ -121,6 +125,10 @@ class FilterExpression extends Filter
public function toQueryString()
{
if ($this->isBooleanTrue()) {
return $this->column;
}
$expression = is_array($this->expression) ?
'(' . implode('|', array_map('rawurlencode', $this->expression)) . ')' :
rawurlencode($this->expression);
@ -128,6 +136,11 @@ class FilterExpression extends Filter
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 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();
if ($type === null) {
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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@ namespace Icinga\Repository;
use Exception;
use Icinga\Application\Config;
use Icinga\Data\ConfigObject;
use Icinga\Data\Extensible;
use Icinga\Data\Filter\Filter;
use Icinga\Data\Updatable;
@ -18,33 +19,218 @@ use Icinga\Exception\StatementException;
* Additionally provided features:
* <ul>
* <li>Insert, update and delete capabilities</li>
* <li>Triggers for inserts, updates and deletions</li>
* <li>Lazy initialization of table specific configs</li>
* </ul>
*/
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
*
* @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
*/
public function __construct(Config $ds)
public function __construct(Config $ds = null)
{
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');
}
}
/**
* {@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
*
@ -57,17 +243,20 @@ abstract class IniRepository extends Repository implements Extensible, Updatable
*/
public function insert($target, array $data)
{
$ds = $this->getDataSource($target);
$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);
}
$this->ds->setSection($section, $newData);
$ds->setSection($section, $config);
try {
$this->ds->saveIni();
$ds->saveIni();
} catch (Exception $e) {
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)
{
$ds = $this->getDataSource($target);
$newData = $this->requireStatementColumns($target, $data);
$keyColumn = $this->ds->getConfigObject()->getKeyColumn();
$keyColumn = $ds->getConfigObject()->getKeyColumn();
if ($filter === null && isset($newData[$keyColumn])) {
throw new StatementException(
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) {
$filter = $this->requireFilter($target, $filter);
$query->addFilter($this->requireFilter($target, $filter));
}
/** @var ConfigObject $config */
$newSection = null;
foreach (iterator_to_array($this->ds) as $section => $config) {
if ($filter !== null && !$filter->matches($config)) {
continue;
}
foreach ($query as $section => $config) {
if ($newSection !== null) {
throw new StatementException(
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) {
if ($column === $keyColumn) {
$newSection = $value;
} 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 ($this->ds->hasSection($newSection)) {
if ($ds->hasSection($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 {
$this->ds->setSection($section, $config);
$ds->setSection(
$section,
$this->onUpdate($target, $config, $newConfig)
);
}
}
try {
$this->ds->saveIni();
$ds->saveIni();
} catch (Exception $e) {
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)
{
$ds = $this->getDataSource($target);
$query = $ds->select();
if ($filter !== null) {
$filter = $this->requireFilter($target, $filter);
$query->addFilter($this->requireFilter($target, $filter));
}
foreach (iterator_to_array($this->ds) as $section => $config) {
if ($filter === null || $filter->matches($config)) {
$this->ds->removeSection($section);
}
/** @var ConfigObject $config */
foreach ($query as $section => $config) {
$ds->removeSection($section);
$this->onDelete($target, $config);
}
try {
$this->ds->saveIni();
$ds->saveIni();
} catch (Exception $e) {
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
*
* @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 (! isset($data[$keyColumn])) {
throw new ProgrammingError('$data does not provide a value for key column "%s"', $keyColumn);
if (! is_array($config) && !$config instanceof ConfigObject) {
throw new ProgrammingError('$config is neither an array nor a ConfigObject');
} elseif (! isset($config[$keyColumn])) {
throw new ProgrammingError('$config does not provide a value for key column "%s"', $keyColumn);
}
$section = $data[$keyColumn];
unset($data[$keyColumn]);
$section = $config[$keyColumn];
unset($config[$keyColumn]);
return $section;
}
}

View File

@ -90,6 +90,13 @@ abstract class Repository implements Selectable
*/
protected $blacklistedQueryColumns;
/**
* Whether the blacklisted query columns are in the legacy format
*
* @var bool
*/
protected $legacyBlacklistedQueryColumns;
/**
* The filter columns being provided
*
@ -105,6 +112,13 @@ abstract class Repository implements Selectable
*/
protected $filterColumns;
/**
* Whether the provided filter columns are in the legacy format
*
* @var bool
*/
protected $legacyFilterColumns;
/**
* The search columns (or aliases) being provided
*
@ -112,6 +126,13 @@ abstract class Repository implements Selectable
*/
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
*
@ -140,6 +161,13 @@ abstract class Repository implements Selectable
*/
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
*
@ -186,9 +214,10 @@ abstract class Repository implements Selectable
/**
* 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->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
*
* @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;
}
@ -323,25 +363,45 @@ abstract class Repository implements Selectable
*
* Calls $this->initializeBlacklistedQueryColumns() in case $this->blacklistedQueryColumns is null.
*
* @param string $table
*
* @return array
*/
public function getBlacklistedQueryColumns()
public function getBlacklistedQueryColumns($table = 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;
} elseif (! isset($this->blacklistedQueryColumns[$table])) {
$this->blacklistedQueryColumns[$table] = $this->initializeBlacklistedQueryColumns($table);
}
return $this->blacklistedQueryColumns[$table];
}
/**
* Overwrite this in your repository implementation in case you
* need to initialize the blacklisted query columns lazily
* Overwrite this in your repository implementation in case you need to initialize the
* blacklisted query columns lazily or dependent on a query's current base table
*
* @param string $table
*
* @return array
*/
protected function initializeBlacklistedQueryColumns()
{
// $table is not part of the signature due to PHP strict standards
return array();
}
@ -350,24 +410,47 @@ abstract class Repository implements Selectable
*
* Calls $this->initializeFilterColumns() in case $this->filterColumns is null.
*
* @param string $table
*
* @return array
*/
public function getFilterColumns()
public function getFilterColumns($table = 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;
} 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
*/
protected function initializeFilterColumns()
{
// $table is not part of the signature due to PHP strict standards
return array();
}
@ -376,24 +459,45 @@ abstract class Repository implements Selectable
*
* Calls $this->initializeSearchColumns() in case $this->searchColumns is null.
*
* @param string $table
*
* @return array
*/
public function getSearchColumns()
public function getSearchColumns($table = 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;
} 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
*/
protected function initializeSearchColumns()
{
// $table is not part of the signature due to PHP strict standards
return array();
}
@ -402,24 +506,47 @@ abstract class Repository implements Selectable
*
* Calls $this->initializeSortRules() in case $this->sortRules is null.
*
* @param string $table
*
* @return array
*/
public function getSortRules()
public function getSortRules($table = 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;
} 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
*/
protected function initializeSortRules()
{
// $table is not part of the signature due to PHP strict standards
return array();
}
@ -900,7 +1027,7 @@ abstract class Repository implements Selectable
throw new ProgrammingError('Table name "%s" not found', $table);
}
$blacklist = $this->getBlacklistedQueryColumns();
$blacklist = $this->getBlacklistedQueryColumns($table);
$columns = array();
foreach ($queryColumns[$table] as $alias => $column) {
$name = is_string($alias) ? $alias : $column;
@ -994,7 +1121,7 @@ abstract class Repository implements Selectable
return false;
}
return !in_array($alias, $this->getBlacklistedQueryColumns())
return !in_array($alias, $this->getBlacklistedQueryColumns($table))
&& $this->validateQueryColumnAssociation($table, $name);
}
@ -1019,7 +1146,7 @@ abstract class Repository implements Selectable
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);
}
@ -1107,7 +1234,7 @@ abstract class Repository implements Selectable
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);
}

View File

@ -69,9 +69,13 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
*/
public function __clone()
{
if ($this->query !== null) {
$this->query = clone $this->query;
}
if ($this->iterator !== null) {
$this->iterator = clone $this->iterator;
}
}
/**
* 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)
{
$this->query = $this->repository->getDataSource()->select();
$this->query = $this->repository->getDataSource($target)->select();
$this->query->from($this->repository->requireTable($target, $this));
$this->query->columns($this->prepareQueryColumns($target, $columns));
$this->target = $target;
@ -200,7 +204,7 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
*/
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()
{
return $this->repository->getSearchColumns();
return $this->repository->getSearchColumns($this->target);
}
/**
@ -290,7 +294,7 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
*/
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) {
$iterator = $this->query;
} else {
$iterator = $this->repository->getDataSource()->query($this->query);
$iterator = $this->repository->getDataSource($this->target)->query($this->query);
}
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,
array $invokeArgs = array()
) {
/** @var \Icinga\Web\Request $request */
/** @var \Icinga\Web\Response $response */
$this->params = UrlParams::fromQueryString();
$this->setRequest($request)
@ -124,7 +126,11 @@ class ActionController extends Zend_Controller_Action
$this->_helper->layout()->disableLayout();
}
// $auth->authenticate($request, $response, $this->requiresLogin());
if ($this->requiresLogin()) {
if (! $request->isXmlHttpRequest() && $request->isApiRequest()) {
Auth::getInstance()->challengeHttp();
}
$this->redirectToLogin(Url::fromRequest());
}
@ -179,7 +185,7 @@ class ActionController extends Zend_Controller_Action
*/
public function assertPermission($permission)
{
if ($this->requiresAuthentication && ! $this->Auth()->hasPermission($permission)) {
if (! $this->Auth()->hasPermission($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
*
* @param string $name Permission name
* @return Array
* @param string $name Restriction name
*
* @return array
*/
public function getRestrictions($name)
{
@ -268,15 +275,14 @@ class ActionController extends Zend_Controller_Action
* user is currently not authenticated
*
* @return bool
* @see requiresAuthentication
*/
protected function requiresLogin()
{
if (!$this->requiresAuthentication) {
if (! $this->requiresAuthentication) {
return false;
}
return !$this->Auth()->isAuthenticated();
return ! $this->Auth()->isAuthenticated();
}
/**

View File

@ -26,7 +26,8 @@ class ModuleActionController extends ActionController
protected function prepareInit()
{
$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());
}
}

View File

@ -948,7 +948,7 @@ class Form extends Zend_Form
if ($this->getUseFormAutosubmit()) {
$warningId = 'autosubmit_warning_' . $el->getId();
$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(
'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 bool $markAsError Whether to prevent the form from being successfully validated or not
*
* @return $this
*/
public function error($message)
public function error($message, $markAsError = true)
{
$this->addNotification($message, self::NOTIFICATION_ERROR);
if ($markAsError) {
$this->markAsError();
}
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 bool $markAsError Whether to prevent the form from being successfully validated or not
*
* @return $this
*/
public function warning($message)
public function warning($message, $markAsError = true)
{
$this->addNotification($message, self::NOTIFICATION_WARNING);
if ($markAsError) {
$this->markAsError();
}
return $this;
}

View File

@ -97,7 +97,7 @@ class Autosubmit extends Zend_Form_Decorator_Abstract
$isForm = $this->getElement() instanceof Form;
$warning = $isForm
? 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(
'aria-hidden' => $isForm ? 'false' : 'true',
'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
foreach ($vendorFiles as $file) {
$out .= file_get_contents($file);
$out .= ';' . ltrim(trim(file_get_contents($file)), ';') . "\n";
}
foreach ($jsFiles as $file) {

View File

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

View File

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

View File

@ -3,44 +3,70 @@
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
{
/**
* The title of each summarized child
* Cached count
*
* @var int
*/
protected $count;
/**
* State to severity map
*
* @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}
*/
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) {
$renderer = $child->getRenderer();
if ($renderer instanceof BadgeNavigationItemRenderer) {
if ($renderer->getState() === $this->getState()) {
$this->titles[] = $renderer->getTitle();
$count += $renderer->getCount();
$count = $renderer->getCount();
if ($count) {
$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;
}
/**
* {@inheritdoc}
*/
public function getTitle()
{
return ! empty($this->titles) ? join(', ', $this->titles) : '';
return $this->count;
}
}

View File

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

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