diff --git a/.puppet/modules/openldap/manifests/init.pp b/.puppet/modules/openldap/manifests/init.pp
index a0480632e..ee62e9b64 100644
--- a/.puppet/modules/openldap/manifests/init.pp
+++ b/.puppet/modules/openldap/manifests/init.pp
@@ -14,11 +14,12 @@
#
class openldap {
- package { ['openldap-servers', 'openldap-clients']:
+ package { [ 'openldap-servers', 'openldap-clients', ]:
ensure => latest,
}
service { 'slapd':
+ enable => true,
ensure => running,
require => Package['openldap-servers'],
}
diff --git a/.puppet/modules/pgsql/manifests/init.pp b/.puppet/modules/pgsql/manifests/init.pp
index 4b48cf895..dfa105d65 100644
--- a/.puppet/modules/pgsql/manifests/init.pp
+++ b/.puppet/modules/pgsql/manifests/init.pp
@@ -1,7 +1,7 @@
# Class: pgsql
#
-# This class installs the postgresql server and client software.
-# Further it configures pg_hba.conf to trus the local icinga user.
+# This class installs the PostgreSQL server and client software.
+# Further it configures pg_hba.conf to trust the local icinga user.
#
# Parameters:
#
@@ -17,26 +17,25 @@ class pgsql {
Exec { path => '/sbin:/bin:/usr/bin' }
- package { [
- 'postgresql', 'postgresql-server'
- ]:
- ensure => latest,
+ package { [ 'postgresql', 'postgresql-server', ]:
+ ensure => latest,
}
exec { 'initdb':
- creates => '/var/lib/pgsql/data/pg_xlog',
command => 'service postgresql initdb',
- require => Package['postgresql-server']
+ creates => '/var/lib/pgsql/data/pg_xlog',
+ require => Package['postgresql-server'],
}
service { 'postgresql':
+ enable => true,
ensure => running,
- require => [Package['postgresql-server'], Exec['initdb']]
+ require => [ Package['postgresql-server'], Exec['initdb'], ]
}
file { '/var/lib/pgsql/data/pg_hba.conf':
content => template('pgsql/pg_hba.conf.erb'),
- require => [Package['postgresql-server'], Exec['initdb']],
- notify => Service['postgresql']
+ require => [ Package['postgresql-server'], Exec['initdb'], ],
+ notify => Service['postgresql'],
}
}
diff --git a/RELEASE.md b/RELEASE.md
index e8bcfeec0..da70d5bef 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -5,22 +5,47 @@ https://dev.icinga.org/projects/icingaweb2/roadmap
# Release Workflow
+## Authors
+
Update the [.mailmap](.mailmap) and [AUTHORS](AUTHORS) files:
$ git log --use-mailmap | grep ^Author: | cut -f2- -d' ' | sort | uniq > AUTHORS
-Update the version number in the [icingaweb2.spec] and [VERSION] files.
+## Version
+
+Update the version number in the following files:
+
+* [icingaweb2.spec] (ensure that the revision is properly set)
+* [VERSION]
+* Application Version: [library/Icinga/Application/Version.php]
+* Module Versions in modules/*/module.info
+
+Commands:
+
+ VERSION=2.0.0
+
+ vim icingaweb2.spec
+
+ echo "v$VERSION" > VERSION
+
+ sed -i '' "s/const VERSION = '.*'/const VERSION = '$VERSION'/g" library/Icinga/Application/Version.php
+
+ find . -type f -name '*.info' -exec sed -i '' "s/Version: .*/Version: $VERSION/g" {} \;
+
+## Changelog
Update the [ChangeLog](ChangeLog) file using
the changelog.py script.
Changelog:
- $ ./changelog.py --version 2.0.0-rc1
+ $ ./changelog.py --version 2.0.0
Wordpress:
- $ ./changelog.py --version 2.0.0-rc1 --html --links
+ $ ./changelog.py --version 2.0.0 --html --links
+
+## Git Tag
Commit these changes to the "master" branch:
diff --git a/application/VERSION b/application/VERSION
index 519b667a7..f504e66fd 100644
--- a/application/VERSION
+++ b/application/VERSION
@@ -1 +1 @@
-$Format:%H%d %ci$
+$Format:%H %ci$
diff --git a/application/controllers/AboutController.php b/application/controllers/AboutController.php
index fc1c78931..77d829679 100644
--- a/application/controllers/AboutController.php
+++ b/application/controllers/AboutController.php
@@ -3,13 +3,23 @@
namespace Icinga\Controllers;
+use Icinga\Application\Icinga;
use Icinga\Application\Version;
-use Icinga\Web\Controller\ActionController;
+use Icinga\Web\Controller;
-class AboutController extends ActionController
+class AboutController extends Controller
{
public function indexAction()
{
$this->view->version = Version::get();
+ $this->view->modules = Icinga::app()->getModuleManager()->getLoadedModules();
+ $this->view->tabs = $this->getTabs()->add(
+ 'about',
+ array(
+ 'label' => $this->translate('About'),
+ 'title' => $this->translate('About Icinga Web 2'),
+ 'url' => 'about'
+ )
+ )->activate('about');
}
}
diff --git a/application/controllers/ErrorController.php b/application/controllers/ErrorController.php
index 62198ec0c..ed35c3162 100644
--- a/application/controllers/ErrorController.php
+++ b/application/controllers/ErrorController.php
@@ -35,6 +35,10 @@ class ErrorController extends ActionController
Logger::error($exception);
Logger::error('Stacktrace: %s', $exception->getTraceAsString());
+ if (! ($isAuthenticated = $this->Auth()->isAuthenticated())) {
+ $this->innerLayout = 'error';
+ }
+
switch ($error->type) {
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE:
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
@@ -45,11 +49,13 @@ class ErrorController extends ActionController
$path = array_shift($path);
$this->getResponse()->setHttpResponseCode(404);
$this->view->message = $this->translate('Page not found.');
- if ($this->Auth()->isAuthenticated() && $modules->hasInstalled($path) && ! $modules->hasEnabled($path)) {
- $this->view->message .= ' ' . sprintf(
- $this->translate('Enabling the "%s" module might help!'),
- $path
- );
+ if ($isAuthenticated) {
+ if ($modules->hasInstalled($path) && ! $modules->hasEnabled($path)) {
+ $this->view->message .= ' ' . sprintf(
+ $this->translate('Enabling the "%s" module might help!'),
+ $path
+ );
+ }
}
break;
@@ -93,5 +99,6 @@ class ErrorController extends ActionController
}
$this->view->request = $error->request;
+ $this->view->hideControls = ! $isAuthenticated;
}
}
diff --git a/application/controllers/ListController.php b/application/controllers/ListController.php
index b39811c0c..b9408f17c 100644
--- a/application/controllers/ListController.php
+++ b/application/controllers/ListController.php
@@ -10,6 +10,7 @@ use Icinga\Protocol\File\FileReader;
use Icinga\Web\Controller;
use Icinga\Web\Url;
use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
use Icinga\Web\Widget\Tabextension\OutputFormat;
/**
@@ -30,7 +31,7 @@ class ListController extends Controller
'list/'
. str_replace(' ', '', $action)
)
- ))->extend(new OutputFormat())->extend(new DashboardAction())->activate($action);
+ ))->extend(new OutputFormat())->extend(new DashboardAction())->extend(new MenuAction())->activate($action);
}
/**
diff --git a/application/controllers/NavigationController.php b/application/controllers/NavigationController.php
index f5c1d785a..689a123cf 100644
--- a/application/controllers/NavigationController.php
+++ b/application/controllers/NavigationController.php
@@ -5,13 +5,13 @@ namespace Icinga\Controllers;
use Exception;
use Icinga\Application\Config;
-use Icinga\Application\Icinga;
use Icinga\Exception\NotFoundError;
use Icinga\Data\DataArray\ArrayDatasource;
use Icinga\Forms\ConfirmRemovalForm;
use Icinga\Forms\Navigation\NavigationConfigForm;
use Icinga\Web\Controller;
use Icinga\Web\Form;
+use Icinga\Web\Navigation\Navigation;
use Icinga\Web\Notification;
use Icinga\Web\Url;
@@ -21,11 +21,11 @@ use Icinga\Web\Url;
class NavigationController extends Controller
{
/**
- * The default item types provided by Icinga Web 2
+ * The global navigation item type configuration
*
* @var array
*/
- protected $defaultItemTypes;
+ protected $itemTypeConfig;
/**
* {@inheritdoc}
@@ -33,11 +33,19 @@ class NavigationController extends Controller
public function init()
{
parent::init();
+ $this->itemTypeConfig = Navigation::getItemTypeConfiguration();
+ }
- $this->defaultItemTypes = array(
- 'menu-item' => $this->translate('Menu Entry'),
- 'dashlet' => 'Dashlet'
- );
+ /**
+ * Return the label for the given navigation item type
+ *
+ * @param string $type
+ *
+ * @return string $type if no label can be found
+ */
+ protected function getItemLabel($type)
+ {
+ return isset($this->itemTypeConfig[$type]['label']) ? $this->itemTypeConfig[$type]['label'] : $type;
}
/**
@@ -47,33 +55,71 @@ class NavigationController extends Controller
*/
protected function listItemTypes()
{
- $moduleManager = Icinga::app()->getModuleManager();
-
- $types = $this->defaultItemTypes;
- foreach ($moduleManager->getLoadedModules() as $module) {
- if ($this->hasPermission($moduleManager::MODULE_PERMISSION_NS . $module->getName())) {
- $moduleTypes = $module->getNavigationItems();
- if (! empty($moduleTypes)) {
- $types = array_merge($types, $moduleTypes);
- }
- }
+ $types = array();
+ foreach ($this->itemTypeConfig as $type => $options) {
+ $types[$type] = isset($options['label']) ? $options['label'] : $type;
}
return $types;
}
+ /**
+ * Return all shared navigation item configurations
+ *
+ * @param string $owner A username if only items shared by a specific user are desired
+ *
+ * @return array
+ */
+ protected function fetchSharedNavigationItemConfigs($owner = null)
+ {
+ $configs = array();
+ foreach ($this->itemTypeConfig as $type => $_) {
+ $config = Config::navigation($type);
+ $config->getConfigObject()->setKeyColumn('name');
+ $query = $config->select();
+ if ($owner !== null) {
+ $query->where('owner', $owner);
+ }
+
+ foreach ($query as $itemConfig) {
+ $configs[] = $itemConfig;
+ }
+ }
+
+ return $configs;
+ }
+
+ /**
+ * Return all user navigation item configurations
+ *
+ * @param string $username
+ *
+ * @return array
+ */
+ protected function fetchUserNavigationItemConfigs($username)
+ {
+ $configs = array();
+ foreach ($this->itemTypeConfig as $type => $_) {
+ $config = Config::navigation($type, $username);
+ $config->getConfigObject()->setKeyColumn('name');
+ foreach ($config->select() as $itemConfig) {
+ $configs[] = $itemConfig;
+ }
+ }
+
+ return $configs;
+ }
+
/**
* Show the current user a list of his/her navigation items
*/
public function indexAction()
{
$user = $this->Auth()->getUser();
-
$ds = new ArrayDatasource(array_merge(
- Config::app('navigation')->select()->where('owner', $user->getUsername())->fetchAll(),
- iterator_to_array($user->loadNavigationConfig())
+ $this->fetchSharedNavigationItemConfigs($user->getUsername()),
+ $this->fetchUserNavigationItemConfigs($user->getUsername())
));
- $ds->setKeyColumn('name');
$query = $ds->select();
$this->view->types = $this->listItemTypes();
@@ -91,7 +137,7 @@ class NavigationController extends Controller
array(
'type' => $this->translate('Type'),
'owner' => $this->translate('Shared'),
- 'name' => $this->translate('Shared Navigation')
+ 'name' => $this->translate('Navigation')
),
$query
);
@@ -103,13 +149,11 @@ class NavigationController extends Controller
public function sharedAction()
{
$this->assertPermission('config/application/navigation');
- $config = Config::app('navigation');
- $config->getConfigObject()->setKeyColumn('name');
- $query = $config->select();
+ $ds = new ArrayDatasource($this->fetchSharedNavigationItemConfigs());
+ $query = $ds->select();
$removeForm = new Form();
$removeForm->setUidDisabled();
- $removeForm->setAction(Url::fromPath('navigation/unshare'));
$removeForm->addElement('hidden', 'name', array(
'decorators' => array('ViewHelper')
));
@@ -156,11 +200,14 @@ class NavigationController extends Controller
{
$form = new NavigationConfigForm();
$form->setRedirectUrl('navigation');
+ $form->setUser($this->Auth()->getUser());
$form->setItemTypes($this->listItemTypes());
$form->setTitle($this->translate('Create New Navigation Item'));
$form->addDescription($this->translate('Create a new navigation item, such as a menu entry or dashlet.'));
- $form->setUser($this->Auth()->getUser());
- $form->setShareConfig(Config::app('navigation'));
+
+ // TODO: Fetch all "safe" parameters from the url and populate them
+ $form->populate(array('url' => rawurldecode($this->params->get('url', ''))));
+
$form->setOnSuccess(function (NavigationConfigForm $form) {
$data = array_filter($form->getValues());
@@ -172,7 +219,7 @@ class NavigationController extends Controller
}
if ($form->save()) {
- if (isset($data['type']) && $data['type'] === 'menu-item') {
+ if ($data['type'] === 'menu-item') {
$form->getResponse()->setRerenderLayout();
}
@@ -194,14 +241,22 @@ class NavigationController extends Controller
public function editAction()
{
$itemName = $this->params->getRequired('name');
+ $itemType = $this->params->getRequired('type');
$referrer = $this->params->get('referrer', 'index');
+ $user = $this->Auth()->getUser();
+ if ($user->can('config/application/navigation')) {
+ $itemOwner = $this->params->get('owner', $user->getUsername());
+ } else {
+ $itemOwner = $user->getUsername();
+ }
+
$form = new NavigationConfigForm();
+ $form->setUser($user);
+ $form->setShareConfig(Config::navigation($itemType));
+ $form->setUserConfig(Config::navigation($itemType, $itemOwner));
$form->setRedirectUrl($referrer === 'shared' ? 'navigation/shared' : 'navigation');
- $form->setItemTypes($this->listItemTypes());
- $form->setTitle(sprintf($this->translate('Edit Navigation Item %s'), $itemName));
- $form->setUser($this->Auth()->getUser());
- $form->setShareConfig(Config::app('navigation'));
+ $form->setTitle(sprintf($this->translate('Edit %s %s'), $this->getItemLabel($itemType), $itemName));
$form->setOnSuccess(function (NavigationConfigForm $form) use ($itemName) {
$data = array_map(
function ($v) {
@@ -248,13 +303,17 @@ class NavigationController extends Controller
public function removeAction()
{
$itemName = $this->params->getRequired('name');
+ $itemType = $this->params->getRequired('type');
+ $user = $this->Auth()->getUser();
$navigationConfigForm = new NavigationConfigForm();
- $navigationConfigForm->setUser($this->Auth()->getUser());
- $navigationConfigForm->setShareConfig(Config::app('navigation'));
+ $navigationConfigForm->setUser($user);
+ $navigationConfigForm->setShareConfig(Config::navigation($itemType));
+ $navigationConfigForm->setUserConfig(Config::navigation($itemType, $user->getUsername()));
+
$form = new ConfirmRemovalForm();
$form->setRedirectUrl('navigation');
- $form->setTitle(sprintf($this->translate('Remove Navigation Item %s'), $itemName));
+ $form->setTitle(sprintf($this->translate('Remove %s %s'), $this->getItemLabel($itemType), $itemName));
$form->setOnSuccess(function (ConfirmRemovalForm $form) use ($itemName, $navigationConfigForm) {
try {
$itemConfig = $navigationConfigForm->delete($itemName);
@@ -291,9 +350,14 @@ class NavigationController extends Controller
$this->assertPermission('config/application/navigation');
$this->assertHttpMethod('POST');
+ // TODO: I'd like these being form fields
+ $itemType = $this->params->getRequired('type');
+ $itemOwner = $this->params->getRequired('owner');
+
$navigationConfigForm = new NavigationConfigForm();
$navigationConfigForm->setUser($this->Auth()->getUser());
- $navigationConfigForm->setShareConfig(Config::app('navigation'));
+ $navigationConfigForm->setShareConfig(Config::navigation($itemType));
+ $navigationConfigForm->setUserConfig(Config::navigation($itemType, $itemOwner));
$form = new Form(array(
'onSuccess' => function ($form) use ($navigationConfigForm) {
diff --git a/application/forms/Config/Resource/DbResourceForm.php b/application/forms/Config/Resource/DbResourceForm.php
index d79ad248b..c60ba65a9 100644
--- a/application/forms/Config/Resource/DbResourceForm.php
+++ b/application/forms/Config/Resource/DbResourceForm.php
@@ -3,8 +3,8 @@
namespace Icinga\Forms\Config\Resource;
-use Icinga\Web\Form;
use Icinga\Application\Platform;
+use Icinga\Web\Form;
/**
* Form class for adding/modifying database resources
@@ -43,12 +43,30 @@ class DbResourceForm extends Form
$dbChoices['oci'] = 'Oracle (OCI8)';
}
$offerPostgres = false;
+ $offerMysql = false;
if (isset($formData['db'])) {
if ($formData['db'] === 'pgsql') {
$offerPostgres = true;
+ } elseif ($formData['db'] === 'mysql') {
+ $offerMysql = true;
}
- } elseif (key($dbChoices) === 'pgsql') {
- $offerPostgres = true;
+ } else {
+ $dbChoice = key($dbChoices);
+ if ($dbChoice === 'pgsql') {
+ $offerPostgres = true;
+ } elseif ($dbChoices === 'mysql') {
+ $offerMysql = true;
+ }
+ }
+ $socketInfo = '';
+ if ($offerPostgres) {
+ $socketInfo = $this->translate(
+ 'For using unix domain sockets, specify the path to the unix domain socket directory'
+ );
+ } elseif ($offerMysql) {
+ $socketInfo = $this->translate(
+ 'For using unix domain sockets, specify localhost'
+ );
}
$this->addElement(
'text',
@@ -76,7 +94,8 @@ class DbResourceForm extends Form
array (
'required' => true,
'label' => $this->translate('Host'),
- 'description' => $this->translate('The hostname of the database'),
+ 'description' => $this->translate('The hostname of the database')
+ . ($socketInfo ? '. ' . $socketInfo : ''),
'value' => 'localhost'
)
);
@@ -119,6 +138,14 @@ class DbResourceForm extends Form
'description' => $this->translate('The password to use for authentication')
)
);
+ $this->addElement(
+ 'text',
+ 'charset',
+ array (
+ 'description' => $this->translate('The character set for the database'),
+ 'label' => $this->translate('Character Set')
+ )
+ );
$this->addElement(
'checkbox',
'persistent',
diff --git a/application/forms/Navigation/NavigationConfigForm.php b/application/forms/Navigation/NavigationConfigForm.php
index ba1f20d30..94ff860ae 100644
--- a/application/forms/Navigation/NavigationConfigForm.php
+++ b/application/forms/Navigation/NavigationConfigForm.php
@@ -123,12 +123,18 @@ class NavigationConfigForm extends ConfigForm
/**
* Return the user's navigation configuration
*
+ * @param string $type
+ *
* @return Config
*/
- public function getUserConfig()
+ public function getUserConfig($type = null)
{
if ($this->userConfig === null) {
- $this->setUserConfig($this->getUser()->loadNavigationConfig());
+ if ($type === null) {
+ throw new ProgrammingError('You need to pass a type if no user configuration is set');
+ }
+
+ $this->setUserConfig(Config::navigation($type, $this->getUser()->getUsername()));
}
return $this->userConfig;
@@ -151,10 +157,20 @@ class NavigationConfigForm extends ConfigForm
/**
* Return the shared navigation configuration
*
+ * @param string $type
+ *
* @return Config
*/
- public function getShareConfig()
+ public function getShareConfig($type = null)
{
+ if ($this->shareConfig === null) {
+ if ($type === null) {
+ throw new ProgrammingError('You need to pass a type if no share configuration is set');
+ }
+
+ $this->setShareConfig(Config::navigation($type));
+ }
+
return $this->shareConfig;
}
@@ -194,10 +210,9 @@ class NavigationConfigForm extends ConfigForm
$children = $this->itemToLoad ? $this->getFlattenedChildren($this->itemToLoad) : array();
$names = array();
- foreach ($this->getShareConfig() as $sectionName => $sectionConfig) {
+ foreach ($this->getShareConfig($type) as $sectionName => $sectionConfig) {
if (
$sectionName !== $this->itemToLoad
- && $sectionConfig->type === $type
&& $sectionConfig->owner === ($owner ?: $this->getUser()->getUsername())
&& !in_array($sectionName, $children, true)
) {
@@ -205,10 +220,9 @@ class NavigationConfigForm extends ConfigForm
}
}
- foreach ($this->getUserConfig() as $sectionName => $sectionConfig) {
+ foreach ($this->getUserConfig($type) as $sectionName => $sectionConfig) {
if (
$sectionName !== $this->itemToLoad
- && $sectionConfig->type === $type
&& !in_array($sectionName, $children, true)
) {
$names[] = $sectionName;
@@ -271,29 +285,31 @@ class NavigationConfigForm extends ConfigForm
*
* @return $this
*
- * @throws InvalidArgumentException In case $data does not contain a navigation item name
+ * @throws InvalidArgumentException In case $data does not contain a navigation item name or type
* @throws IcingaException In case a navigation item with the same name already exists
*/
public function add(array $data)
{
if (! isset($data['name'])) {
throw new InvalidArgumentException('Key \'name\' missing');
+ } elseif (! isset($data['type'])) {
+ throw new InvalidArgumentException('Key \'type\' missing');
}
$shared = false;
- $config = $this->getUserConfig();
+ $config = $this->getUserConfig($data['type']);
if ((isset($data['users']) && $data['users']) || (isset($data['groups']) && $data['groups'])) {
if ($this->getUser()->can('application/share/navigation')) {
$data['owner'] = $this->getUser()->getUsername();
- $config = $this->getShareConfig();
+ $config = $this->getShareConfig($data['type']);
$shared = true;
} else {
unset($data['users']);
unset($data['groups']);
}
- } elseif (isset($data['parent']) && $data['parent'] && $this->hasBeenShared($data['parent'])) {
+ } elseif (isset($data['parent']) && $data['parent'] && $this->hasBeenShared($data['parent'], $data['type'])) {
$data['owner'] = $this->getUser()->getUsername();
- $config = $this->getShareConfig();
+ $config = $this->getShareConfig($data['type']);
$shared = true;
}
@@ -301,9 +317,9 @@ class NavigationConfigForm extends ConfigForm
$exists = $config->hasSection($itemName);
if (! $exists) {
if ($shared) {
- $exists = $this->getUserConfig()->hasSection($itemName);
+ $exists = $this->getUserConfig($data['type'])->hasSection($itemName);
} else {
- $exists = (bool) $this->getShareConfig()
+ $exists = (bool) $this->getShareConfig($data['type'])
->select()
->where('name', $itemName)
->where('owner', $this->getUser()->getUsername())
@@ -385,8 +401,7 @@ class NavigationConfigForm extends ConfigForm
if ($ownerName === $this->getUser()->getUsername()) {
$exists = $this->getUserConfig()->hasSection($name);
} else {
- $owner = new User($ownerName);
- $exists = $owner->loadNavigationConfig()->hasSection($name);
+ $exists = Config::navigation($itemConfig->type, $ownerName)->hasSection($name);
}
} else {
$exists = (bool) $this->getShareConfig()
@@ -521,8 +536,7 @@ class NavigationConfigForm extends ConfigForm
if (! $itemConfig->owner || $itemConfig->owner === $this->getUser()->getUsername()) {
$config = $this->getUserConfig();
} else {
- $owner = new User($itemConfig->owner);
- $config = $owner->loadNavigationConfig();
+ $config = Config::navigation($itemConfig->type, $itemConfig->owner);
}
foreach ($children as $child) {
@@ -549,6 +563,13 @@ class NavigationConfigForm extends ConfigForm
$shared = false;
$itemTypes = $this->getItemTypes();
$itemType = isset($formData['type']) ? $formData['type'] : key($itemTypes);
+ if ($itemType === null) {
+ throw new ProgrammingError(
+ 'This should actually not happen. Create a bug report at dev.icinga.org'
+ . ' or remove this assertion if you know what you\'re doing'
+ );
+ }
+
$itemForm = $this->getItemForm($itemType);
$this->addElement(
@@ -606,17 +627,27 @@ class NavigationConfigForm extends ConfigForm
}
}
- $this->addElement(
- 'select',
- 'type',
- array(
- 'required' => true,
- 'autosubmit' => true,
- 'label' => $this->translate('Type'),
- 'description' => $this->translate('The type of this navigation item'),
- 'multiOptions' => $itemTypes
- )
- );
+ if (empty($itemTypes) || count($itemTypes) === 1) {
+ $this->addElement(
+ 'hidden',
+ 'type',
+ array(
+ 'value' => $itemType
+ )
+ );
+ } else {
+ $this->addElement(
+ 'select',
+ 'type',
+ array(
+ 'required' => true,
+ 'autosubmit' => true,
+ 'label' => $this->translate('Type'),
+ 'description' => $this->translate('The type of this navigation item'),
+ 'multiOptions' => $itemTypes
+ )
+ );
+ }
if (! $shared && $itemForm->requiresParentSelection()) {
if ($this->itemToLoad && $this->hasBeenShared($this->itemToLoad)) {
@@ -767,12 +798,13 @@ class NavigationConfigForm extends ConfigForm
* Return whether the given navigation item has been shared
*
* @param string $name
+ * @param string $type
*
* @return bool
*/
- protected function hasBeenShared($name)
+ protected function hasBeenShared($name, $type = null)
{
- return $this->getConfigForItem($name) === $this->getShareConfig();
+ return $this->getShareConfig($type) === $this->getConfigForItem($name);
}
/**
diff --git a/application/forms/Navigation/NavigationItemForm.php b/application/forms/Navigation/NavigationItemForm.php
index 0011b044c..cd6eca70d 100644
--- a/application/forms/Navigation/NavigationItemForm.php
+++ b/application/forms/Navigation/NavigationItemForm.php
@@ -4,6 +4,7 @@
namespace Icinga\Forms\Navigation;
use Icinga\Web\Form;
+use Icinga\Web\Url;
class NavigationItemForm extends Form
{
@@ -71,4 +72,20 @@ class NavigationItemForm extends Form
)
);
}
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getValues($suppressArrayNotation = false)
+ {
+ $values = parent::getValues($suppressArrayNotation);
+ if (isset($values['url']) && $values['url']) {
+ $url = Url::fromPath($values['url']);
+ if (! $url->isExternal() && ($relativePath = $url->getRelativeUrl())) {
+ $values['url'] = $relativePath;
+ }
+ }
+
+ return $values;
+ }
}
diff --git a/application/layouts/scripts/error.phtml b/application/layouts/scripts/error.phtml
new file mode 100644
index 000000000..c0ffd3394
--- /dev/null
+++ b/application/layouts/scripts/error.phtml
@@ -0,0 +1,8 @@
+
+
+
; ?>)
+
+
+
+ = $this->render('inline.phtml') ?>
+
diff --git a/application/views/scripts/about/index.phtml b/application/views/scripts/about/index.phtml
index e270a8eb9..2ffc47e76 100644
--- a/application/views/scripts/about/index.phtml
+++ b/application/views/scripts/about/index.phtml
@@ -1,25 +1,128 @@
-
-
Icinga Web 2
- $this->translate('Version: %s'),
- 'gitCommitID' => $this->translate('Git commit ID: %s'),
- 'gitCommitDate' => $this->translate('Git commit date: %s')
- ) as $key => $label) {
- if (array_key_exists($key, $version) && null !== ($value = $version[$key])) {
- $versionInfo[] = sprintf($label, htmlspecialchars($value));
- }
- }
- }
-
- echo (
- 0 === count($versionInfo)
- ? '
' . $this->translate(
- 'Can\'t determine Icinga Web 2\'s version'
- )
- : '
' . nl2br(implode("\n", $versionInfo), false)
- ) . '
';
- ?>
+
+ = $tabs; ?>
+
+
+ = $this->img(
+ 'img/logo_icinga_big_dark.png',
+ null,
+ array(
+ 'width' => 400,
+ 'class' => 'about-logo'
+ )
+ ); ?>
+
+
+ = $this->translate('Version'); ?>: = $this->escape($version['appVersion']); ?>
+
+
+
+ = $this->translate('Git commit ID'); ?>: = $this->escape($version['gitCommitID']); ?>
+
+
+
+ = $this->translate('Git commit date'); ?>: = $this->escape($version['gitCommitDate']); ?>
+
+
+
+ = $this->translate('Copyright'); ?>: © 2013-= date('Y'); ?> = $this->qlink(
+ $this->translate('The Icinga Project'),
+ 'https://www.icinga.org',
+ null,
+ array(
+ 'target' => '_blank'
+ )
+ ); ?>
+
+ = $this->translate('License'); ?>: GNU GPL v2+
+
+
+ = $this->qlink(
+ null,
+ 'https://www.twitter.com/icinga',
+ null,
+ array(
+ 'target' => '_blank',
+ 'icon' => 'twitter',
+ 'title' => $this->translate('Icinga on Twitter')
+ )
+ ); ?> = $this->qlink(
+ null,
+ 'https://www.facebook.com/icinga',
+ null,
+ array(
+ 'target' => '_blank',
+ 'icon' => 'facebook-squared',
+ 'title' => $this->translate('Icinga on Facebook')
+ )
+ ); ?>
+
+
+
= $this->qlink(
+ null,
+ 'https://dev.icinga.org/projects/icingaweb2',
+ null,
+ array(
+ 'target' => '_blank',
+ 'img' => 'img/bugreport.png',
+ 'title' => $this->translate('Report a bug')
+ )
+ ); ?> = $this->qlink(
+ null,
+ 'https://www.icinga.org/services/support',
+ null,
+ array(
+ 'target' => '_blank',
+ 'img' => 'img/support.png',
+ 'title' => $this->translate('Support / Mailinglists')
+ )
+ ); ?>
+
= $this->qlink(
+ null,
+ 'https://wiki.icinga.org',
+ null,
+ array(
+ 'target' => '_blank',
+ 'img' => 'img/wiki.png',
+ 'title' => $this->translate('Icinga Wiki')
+ )
+ ); ?> = $this->qlink(
+ null,
+ 'https://docs.icinga.org/',
+ null,
+ array(
+ 'target' => '_blank',
+ 'img' => 'img/docs.png',
+ 'title' => $this->translate('Icinga Documentation')
+ )
+ ); ?>
+
+
= $this->translate('Loaded modules') ?>
+
+
+
+ | = $this->translate('Name') ?> |
+ = $this->translate('Version') ?> |
+
+
+
+
+
+ |
+ hasPermission('config/modules')): ?>
+ = $this->qlink(
+ $module->getName(),
+ 'config/module/',
+ array('name' => $module->getName()),
+ array('title' => sprintf($this->translate('Show the overview of the %s module'), $module->getName()))
+ ); ?>
+
+ = $this->escape($module->getName()); ?>
+
+ |
+ = $this->escape($module->getVersion()); ?>
+ |
+
+
+
+
diff --git a/application/views/scripts/error/error.phtml b/application/views/scripts/error/error.phtml
index 5b3480922..67429573c 100644
--- a/application/views/scripts/error/error.phtml
+++ b/application/views/scripts/error/error.phtml
@@ -1,10 +1,12 @@
+
-= $this->tabs->showOnlyCloseButton() ?>
+ = $tabs->showOnlyCloseButton(); ?>
-
-
= nl2br($this->escape($message)) ?>
-
-
-
= $this->escape($stackTrace) ?>
-
+
+
= nl2br($this->escape($message)); ?>
+
+
+
= $this->escape($stackTrace) ?>
+
+
\ No newline at end of file
diff --git a/application/views/scripts/group/show.phtml b/application/views/scripts/group/show.phtml
index 725f22a27..a7c3abed7 100644
--- a/application/views/scripts/group/show.phtml
+++ b/application/views/scripts/group/show.phtml
@@ -2,6 +2,7 @@
use Icinga\Data\Extensible;
use Icinga\Data\Updatable;
+use Icinga\Data\Selectable;
$extensible = $this->hasPermission('config/authentication/groups/add') && $backend instanceof Extensible;
@@ -67,7 +68,22 @@ foreach ($members as $member): ?>
- | = $this->escape($member->user_name); ?> |
+
+ hasPermission('config/authentication/users/show')
+ && ($userBackend = $backend->getUserBackend()) !== null
+ && $userBackend instanceof Selectable
+ ): ?>
+ = $this->qlink($member->user_name, 'user/show', array(
+ 'backend' => $userBackend->getName(),
+ 'user' => $member->user_name
+ ), array(
+ 'title' => sprintf($this->translate('Show detailed information about %s'), $member->user_name)
+ )); ?>
+
+ = $this->escape($member->user_name); ?>
+
+ |
getElement('user_name')->setValue($member->user_name); echo $removeForm; ?>
diff --git a/application/views/scripts/navigation/index.phtml b/application/views/scripts/navigation/index.phtml
index 7c81ba5a8..2f77d0995 100644
--- a/application/views/scripts/navigation/index.phtml
+++ b/application/views/scripts/navigation/index.phtml
@@ -22,14 +22,17 @@
| = $this->translate('Remove'); ?> |
- $item): ?>
+
| = $this->qlink(
- $name,
+ $item->name,
'navigation/edit',
- array('name' => $name),
array(
- 'title' => sprintf($this->translate('Edit navigation item %s'), $name)
+ 'name' => $item->name,
+ 'type' => $item->type
+ ),
+ array(
+ 'title' => sprintf($this->translate('Edit navigation item %s'), $item->name)
)
); ?> |
= $item->type && isset($types[$item->type])
@@ -39,10 +42,13 @@
| = $this->qlink(
'',
'navigation/remove',
- array('name' => $name),
+ array(
+ 'name' => $item->name,
+ 'type' => $item->type
+ ),
array(
'icon' => 'trash',
- 'title' => sprintf($this->translate('Remove navigation item %s'), $name)
+ 'title' => sprintf($this->translate('Remove navigation item %s'), $item->name)
)
); ?> |
diff --git a/application/views/scripts/navigation/shared.phtml b/application/views/scripts/navigation/shared.phtml
index 939249f84..5d0a3107f 100644
--- a/application/views/scripts/navigation/shared.phtml
+++ b/application/views/scripts/navigation/shared.phtml
@@ -1,4 +1,8 @@
-compact): ?>
+compact): ?>
= $this->tabs; ?>
= $this->sortBox; ?>
@@ -19,17 +23,19 @@
= $this->translate('Unshare'); ?> |
- $item): ?>
+
| = $this->qlink(
- $name,
+ $item->name,
'navigation/edit',
array(
- 'name' => $name,
+ 'name' => $item->name,
+ 'type' => $item->type,
+ 'owner' => $item->owner,
'referrer' => 'shared'
),
array(
- 'title' => sprintf($this->translate('Edit shared navigation item %s'), $name)
+ 'title' => sprintf($this->translate('Edit shared navigation item %s'), $item->name)
)
); ?> |
= $item->type && isset($types[$item->type])
@@ -48,7 +54,12 @@
)
); ?> |
- = $removeForm->setDefault('name', $name); ?> |
+ = $removeForm
+ ->setDefault('name', $item->name)
+ ->setAction(Url::fromPath(
+ 'navigation/unshare',
+ array('type' => $item->type, 'owner' => $item->owner)
+ )); ?> |
diff --git a/doc/resources.md b/doc/resources.md
index a2bfb66af..6b43ca3d3 100644
--- a/doc/resources.md
+++ b/doc/resources.md
@@ -19,21 +19,38 @@ to handle authentication and authorization, monitoring data or user preferences.
Directive | Description
----------------|------------
**type** | `db`
-**db** | Database management system. Either `mysql` or `pgsql`.
-**host** | Connect to the database server on the given host.
-**port** | Port number to use for the connection.
+**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:**
-```
-[icingaweb]
+````
+[icingaweb-mysql-tcp]
+type = db
+db = mysql
+host = 127.0.0.1
+port = 3306
+username = icingaweb
+password = icingaweb
+dbname = icingaweb
+
+[icingaweb-mysql-socket]
type = db
db = mysql
host = localhost
-port = 3306
+username = icingaweb
+password = icingaweb
+dbname = icingaweb
+
+[icingaweb-pgsql-socket]
+type = db
+db = pgsql
+host = /var/run/postgresql
+port = 5432
username = icingaweb
password = icingaweb
dbname = icingaweb
diff --git a/library/Icinga/Application/Config.php b/library/Icinga/Application/Config.php
index bfe2ddd60..d25ec748c 100644
--- a/library/Icinga/Application/Config.php
+++ b/library/Icinga/Application/Config.php
@@ -13,7 +13,9 @@ use Icinga\Data\Selectable;
use Icinga\Data\SimpleQuery;
use Icinga\File\Ini\IniWriter;
use Icinga\File\Ini\IniParser;
+use Icinga\Exception\IcingaException;
use Icinga\Exception\NotReadableError;
+use Icinga\Web\Navigation\Navigation;
/**
* Container for INI like configuration and global registry of application and module related configuration.
@@ -41,6 +43,13 @@ class Config implements Countable, Iterator, Selectable
*/
protected static $modules = array();
+ /**
+ * Navigation config instances per type
+ *
+ * @var array
+ */
+ protected static $navigation = array();
+
/**
* The internal ConfigObject
*
@@ -416,6 +425,60 @@ class Config implements Countable, Iterator, Selectable
return $moduleConfigs[$configname];
}
+ /**
+ * Retrieve a navigation config
+ *
+ * @param string $type The type identifier of the navigation item for which to return its config
+ * @param string $username A user's name or null if the shared config is desired
+ * @param bool $fromDisk If true, the configuration will be read from disk
+ *
+ * @return Config The requested configuration
+ */
+ public static function navigation($type, $username = null, $fromDisk = false)
+ {
+ if (! isset(self::$navigation[$type])) {
+ self::$navigation[$type] = array();
+ }
+
+ $branch = $username ?: 'shared';
+ $typeConfigs = self::$navigation[$type];
+ if (! isset($typeConfigs[$branch]) || $fromDisk) {
+ $typeConfigs[$branch] = static::fromIni(static::getNavigationConfigPath($type, $username));
+ }
+
+ return $typeConfigs[$branch];
+ }
+
+ /**
+ * Return the path to the configuration file for the given navigation item type and user
+ *
+ * @param string $type
+ * @param string $username
+ *
+ * @return string
+ *
+ * @throws IcingaException In case the given type is unknown
+ */
+ protected static function getNavigationConfigPath($type, $username = null)
+ {
+ $itemTypeConfig = Navigation::getItemTypeConfiguration();
+ if (! isset($itemTypeConfig[$type])) {
+ throw new IcingaException('Invalid navigation item type %s provided', $type);
+ }
+
+ if (isset($itemTypeConfig[$type]['config'])) {
+ $filename = $itemTypeConfig[$type]['config'] . '.ini';
+ } else {
+ $filename = $type . 's.ini';
+ }
+
+ return static::resolvePath(
+ ($username ? 'preferences' . DIRECTORY_SEPARATOR . $username : 'navigation')
+ . DIRECTORY_SEPARATOR
+ . $filename
+ );
+ }
+
/**
* Return this config rendered as a INI structured string
*
diff --git a/library/Icinga/Application/Modules/Module.php b/library/Icinga/Application/Modules/Module.php
index ebbfc3d98..842a581a9 100644
--- a/library/Icinga/Application/Modules/Module.php
+++ b/library/Icinga/Application/Modules/Module.php
@@ -1014,16 +1014,21 @@ class Module
}
/**
- * Provide a new type of configurable navigation item with a optional label
+ * Provide a new type of configurable navigation item with a optional label and config filename
*
* @param string $type
* @param string $label
+ * @param string $config
*
* @return $this
*/
- protected function provideNavigationItem($type, $label = null)
+ protected function provideNavigationItem($type, $label = null, $config = null)
{
- $this->navigationItems[$type] = $label ?: $type;
+ $this->navigationItems[$type] = array(
+ 'label' => $label,
+ 'config' => $config
+ );
+
return $this;
}
diff --git a/library/Icinga/Application/Version.php b/library/Icinga/Application/Version.php
index c06eca2c2..e110031ea 100644
--- a/library/Icinga/Application/Version.php
+++ b/library/Icinga/Application/Version.php
@@ -8,33 +8,41 @@ namespace Icinga\Application;
*/
class Version
{
+ const VERSION = '2.0.0-rc1';
+
/**
* Get the version of this instance of Icinga Web 2
*
- * @return array|false array on success, false otherwise
+ * @return array
*/
public static function get()
{
- if (false === ($appVersion = @file_get_contents(
- Icinga::app()->getApplicationDir() . DIRECTORY_SEPARATOR . 'VERSION'
- ))) {
- return false;
- }
-
- $matches = array();
- if (false === ($res = preg_match(
- '/(?\w+)(?:\s*\(.*?(?:(?<=[\(,])\s*tag\s*:\s*v(?P.+?)\s*(?=[\),]).*?)?\))?\s*(?P\S+)/ms',
- $appVersion,
- $matches
- )) || $res === 0) {
- return false;
- }
-
- foreach ($matches as $key => $value) {
- if (is_int($key) || $value === '') {
- unset($matches[$key]);
+ $version = array('appVersion' => self::VERSION);
+ if (false !== ($appVersion = @file_get_contents(Icinga::app()->getApplicationDir('VERSION')))) {
+ $matches = array();
+ if (@preg_match('/^(?P\w+) (?P\S+)/', $appVersion, $matches)) {
+ return array_merge($version, $matches);
}
}
- return $matches;
+
+ $gitDir = Icinga::app()->getBaseDir('.git');
+ $gitHead = @file_get_contents($gitDir . DIRECTORY_SEPARATOR . 'HEAD');
+ if (false !== $gitHead) {
+ $matches = array();
+ if (@preg_match('/(?[0-9a-f]+)$/ms', $gitCommitID, $matches)) {
+ return array_merge($version, $matches);
+ }
+ }
+ }
+
+ return $version;
}
}
diff --git a/library/Icinga/Application/Web.php b/library/Icinga/Application/Web.php
index f91edc1d8..bb316a537 100644
--- a/library/Icinga/Application/Web.php
+++ b/library/Icinga/Application/Web.php
@@ -179,12 +179,11 @@ class Web extends EmbeddedWeb
*/
public function getSharedNavigation($type)
{
- $config = Config::app('navigation')->getConfigObject();
- $config->setKeyColumn('name');
+ $config = Config::navigation($type === 'dashboard-pane' ? 'dashlet' : $type);
if ($type === 'dashboard-pane') {
$panes = array();
- foreach ($config->select()->where('type', 'dashlet') as $dashletName => $dashletConfig) {
+ foreach ($config as $dashletName => $dashletConfig) {
if ($this->hasAccessToSharedNavigationItem($dashletConfig)) {
// TODO: Throw ConfigurationError if pane or url is missing
$panes[$dashletConfig->pane][$dashletName] = $dashletConfig->url;
@@ -203,7 +202,7 @@ class Web extends EmbeddedWeb
}
} else {
$items = array();
- foreach ($config->select()->where('type', $type) as $name => $typeConfig) {
+ foreach ($config as $name => $typeConfig) {
if ($this->hasAccessToSharedNavigationItem($typeConfig)) {
$items[$name] = $typeConfig;
}
diff --git a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php
index 1d29e018d..2a737d535 100644
--- a/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php
+++ b/library/Icinga/Authentication/UserGroup/LdapUserGroupBackend.php
@@ -12,10 +12,16 @@ use Icinga\Protocol\Ldap\Expression;
use Icinga\Repository\LdapRepository;
use Icinga\Repository\RepositoryQuery;
use Icinga\User;
-use Icinga\Application\Logger;
-class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBackendInterface
+class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInterface
{
+ /**
+ * The user backend being associated with this user group backend
+ *
+ * @var LdapUserBackend
+ */
+ protected $userBackend;
+
/**
* The base DN to use for a user query
*
@@ -105,84 +111,26 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken
);
/**
- * Normed attribute names based on known LDAP environments
+ * Set the user backend to be associated with this user group backend
*
- * @var array
- */
- protected $normedAttributes = array(
- 'uid' => 'uid',
- 'gid' => 'gid',
- 'user' => 'user',
- 'group' => 'group',
- 'member' => 'member',
- 'inetorgperson' => 'inetOrgPerson',
- 'samaccountname' => 'sAMAccountName'
- );
-
- /**
- * The name of this repository
- *
- * @var string
- */
- protected $name;
-
- /**
- * The datasource being used
- *
- * @var Connection
- */
- protected $ds;
-
- /**
- * Create a new LDAP repository object
- *
- * @param Connection $ds The data source to use
- */
- public function __construct($ds)
- {
- $this->ds = $ds;
- }
-
- /**
- * Return the given attribute name normed to known LDAP enviroments, if possible
- *
- * @param string $name
- *
- * @return string
- */
- protected function getNormedAttribute($name)
- {
- $loweredName = strtolower($name);
- if (array_key_exists($loweredName, $this->normedAttributes)) {
- return $this->normedAttributes[$loweredName];
- }
-
- return $name;
- }
-
- /**
- * Set this repository's name
- *
- * @param string $name
+ * @param LdapUserBackend $backend
*
* @return $this
*/
- public function setName($name)
+ public function setUserBackend(LdapUserBackend $backend)
{
- $this->name = $name;
+ $this->userBackend = $backend;
return $this;
}
/**
- * Return this repository's name
+ * Return the user backend being associated with this user group backend
*
- * In case no name has been explicitly set yet, the class name is returned.
- *
- * @return string
+ * @return LdapUserBackend
*/
- public function getName()
+ public function getUserBackend()
{
- return $this->name;
+ return $this->userBackend;
}
/**
@@ -453,7 +401,6 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken
$lastModifiedAttribute = 'modifyTimestamp';
}
- // TODO(jom): Fetching memberships does not work currently, we'll need some aggregate functionality!
$columns = array(
'group' => $this->groupNameAttribute,
'group_name' => $this->groupNameAttribute,
@@ -492,13 +439,37 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken
if ($this->groupClass === null) {
throw new ProgrammingError('It is required to set the objectClass where to look for groups first');
}
+ if ($this->groupMemberAttribute === null) {
+ throw new ProgrammingError('It is required to set a attribute name where to find a group\'s members first');
+ }
- return array(
+ $rules = array(
$this->groupClass => array(
'created_at' => 'generalized_time',
'last_modified' => 'generalized_time'
)
);
+ if (! $this->isAmbiguous($this->groupClass, $this->groupMemberAttribute)) {
+ $rules[$this->groupClass][] = 'user_name';
+ }
+
+ return $rules;
+ }
+
+ /**
+ * Return the uid for the given distinguished name
+ *
+ * @param string $username
+ *
+ * @param string
+ */
+ protected function retrieveUserName($dn)
+ {
+ return $this->ds
+ ->select()
+ ->from('*', array($this->userNameAttribute))
+ ->setBase($dn)
+ ->fetchOne();
}
/**
@@ -524,6 +495,27 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken
return $table;
}
+ /**
+ * Validate that the given column is a valid query target and return it or the actual name if it's an alias
+ *
+ * @param string $table The table where to look for the column or alias
+ * @param string $name The name or alias of the column to validate
+ * @param RepositoryQuery $query An optional query to pass as context
+ *
+ * @return string The given column's name
+ *
+ * @throws QueryException In case the given column is not a valid query column
+ */
+ public function requireQueryColumn($table, $name, RepositoryQuery $query = null)
+ {
+ $column = parent::requireQueryColumn($table, $name, $query);
+ if ($name === 'user_name' && $query !== null) {
+ $query->getQuery()->setUnfoldAttribute('user_name');
+ }
+
+ return $column;
+ }
+
/**
* Return the groups the given user is a member of
*
@@ -533,43 +525,37 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken
*/
public function getMemberships(User $user)
{
- if ($this->groupClass === 'posixGroup') {
- // Posix group only uses simple user name
- $userDn = $user->getUsername();
- } else {
- // LDAP groups use the complete DN
- if (($userDn = $user->getAdditional('ldap_dn')) === null) {
- $userQuery = $this->ds
- ->select()
- ->from($this->userClass)
- ->where($this->userNameAttribute, $user->getUsername())
- ->setBase($this->userBaseDn)
- ->setUsePagedResults(false);
- if ($this->userFilter) {
- $userQuery->where(new Expression($this->userFilter));
- }
+ if ($this->isAmbiguous($this->groupClass, $this->groupMemberAttribute)) {
+ $queryValue = $user->getUsername();
+ } elseif (($queryValue = $user->getAdditional('ldap_dn')) === null) {
+ $userQuery = $this->ds
+ ->select()
+ ->from($this->userClass)
+ ->where($this->userNameAttribute, $user->getUsername())
+ ->setBase($this->userBaseDn)
+ ->setUsePagedResults(false);
+ if ($this->userFilter) {
+ $userQuery->where(new Expression($this->userFilter));
+ }
- if (($userDn = $userQuery->fetchDn()) === null) {
- return array();
- }
+ if (($queryValue = $userQuery->fetchDn()) === null) {
+ return array();
}
}
$groupQuery = $this->ds
->select()
->from($this->groupClass, array($this->groupNameAttribute))
- ->where($this->groupMemberAttribute, $userDn)
+ ->where($this->groupMemberAttribute, $queryValue)
->setBase($this->groupBaseDn);
if ($this->groupFilter) {
$groupQuery->where(new Expression($this->groupFilter));
}
- Logger::debug('Fetching groups for user %s using filter %s.', $user->getUsername(), $groupQuery->__toString());
$groups = array();
foreach ($groupQuery as $row) {
$groups[] = $row->{$this->groupNameAttribute};
}
- Logger::debug('Fetched %d groups: %s.', count($groups), join(', ', $groups));
return $groups;
}
@@ -610,6 +596,7 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken
);
}
+ $this->setUserBackend($userBackend);
$defaults->merge(array(
'user_base_dn' => $userBackend->getBaseDn(),
'user_class' => $userBackend->getUserClass(),
@@ -661,4 +648,4 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken
'group_member_attribute' => 'member'
));
}
-}
+}
\ No newline at end of file
diff --git a/library/Icinga/Data/DataArray/ArrayDatasource.php b/library/Icinga/Data/DataArray/ArrayDatasource.php
index ef5b4e3ed..a91c1ceff 100644
--- a/library/Icinga/Data/DataArray/ArrayDatasource.php
+++ b/library/Icinga/Data/DataArray/ArrayDatasource.php
@@ -80,7 +80,7 @@ class ArrayDatasource implements Selectable
*/
public function select()
{
- return new SimpleQuery($this);
+ return new SimpleQuery(clone $this);
}
/**
diff --git a/library/Icinga/Protocol/Ldap/LdapConnection.php b/library/Icinga/Protocol/Ldap/LdapConnection.php
index ca83c4ff7..3c5a3e276 100644
--- a/library/Icinga/Protocol/Ldap/LdapConnection.php
+++ b/library/Icinga/Protocol/Ldap/LdapConnection.php
@@ -358,9 +358,25 @@ class LdapConnection implements Selectable, Inspectable
*/
public function count(LdapQuery $query)
{
- $ds = $this->getConnection();
$this->bind();
+ if (($unfoldAttribute = $query->getUnfoldAttribute()) !== null) {
+ $desiredColumns = $query->getColumns();
+ if (isset($desiredColumns[$unfoldAttribute])) {
+ $fields = array($unfoldAttribute => $desiredColumns[$unfoldAttribute]);
+ } elseif (in_array($unfoldAttribute, $desiredColumns, true)) {
+ $fields = array($unfoldAttribute);
+ } else {
+ throw new ProgrammingError(
+ 'The attribute used to unfold a query\'s result must be selected'
+ );
+ }
+
+ $res = $this->runQuery($query, $fields);
+ return count($res);
+ }
+
+ $ds = $this->getConnection();
$results = @ldap_search(
$ds,
$query->getBase() ?: $this->getDn(),
@@ -658,7 +674,7 @@ class LdapConnection implements Selectable, Inspectable
protected function runQuery(LdapQuery $query, array $fields = null)
{
$limit = $query->getLimit();
- $offset = $query->hasOffset() ? $query->getOffset() - 1 : 0;
+ $offset = $query->hasOffset() ? $query->getOffset() : 0;
if ($fields === null) {
$fields = $query->getColumns();
@@ -711,13 +727,41 @@ class LdapConnection implements Selectable, Inspectable
$count = 0;
$entries = array();
$entry = ldap_first_entry($ds, $results);
+ $unfoldAttribute = $query->getUnfoldAttribute();
do {
- $count += 1;
- if (! $serverSorting || $offset === 0 || $offset < $count) {
- $entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(
+ if ($unfoldAttribute) {
+ $rows = $this->cleanupAttributes(
ldap_get_attributes($ds, $entry),
- array_flip($fields)
+ array_flip($fields),
+ $unfoldAttribute
);
+
+ if (is_array($rows)) {
+ // TODO: Register the DN the same way as a section name in the ArrayDatasource!
+ foreach ($rows as $row) {
+ $count += 1;
+ if (! $serverSorting || $offset === 0 || $offset < $count) {
+ $entries[] = $row;
+ }
+
+ if ($serverSorting && $limit > 0 && $limit === count($entries)) {
+ break;
+ }
+ }
+ } else {
+ $count += 1;
+ if (! $serverSorting || $offset === 0 || $offset < $count) {
+ $entries[ldap_get_dn($ds, $entry)] = $rows;
+ }
+ }
+ } else {
+ $count += 1;
+ if (! $serverSorting || $offset === 0 || $offset < $count) {
+ $entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(
+ ldap_get_attributes($ds, $entry),
+ array_flip($fields)
+ );
+ }
}
} while ((! $serverSorting || $limit === 0 || $limit !== count($entries))
&& ($entry = ldap_next_entry($ds, $entry))
@@ -754,7 +798,7 @@ class LdapConnection implements Selectable, Inspectable
}
$limit = $query->getLimit();
- $offset = $query->hasOffset() ? $query->getOffset() - 1 : 0;
+ $offset = $query->hasOffset() ? $query->getOffset() : 0;
$queryString = (string) $query;
$base = $query->getBase() ?: $this->rootDn;
@@ -776,6 +820,7 @@ class LdapConnection implements Selectable, Inspectable
$count = 0;
$cookie = '';
$entries = array();
+ $unfoldAttribute = $query->getUnfoldAttribute();
do {
// Do not request the pagination control as a critical extension, as we want the
// server to return results even if the paged search request cannot be satisfied
@@ -826,12 +871,39 @@ class LdapConnection implements Selectable, Inspectable
$entry = ldap_first_entry($ds, $results);
do {
- $count += 1;
- if (! $serverSorting || $offset === 0 || $offset < $count) {
- $entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(
+ if ($unfoldAttribute) {
+ $rows = $this->cleanupAttributes(
ldap_get_attributes($ds, $entry),
- array_flip($fields)
+ array_flip($fields),
+ $unfoldAttribute
);
+
+ if (is_array($rows)) {
+ // TODO: Register the DN the same way as a section name in the ArrayDatasource!
+ foreach ($rows as $row) {
+ $count += 1;
+ if (! $serverSorting || $offset === 0 || $offset < $count) {
+ $entries[] = $row;
+ }
+
+ if ($serverSorting && $limit > 0 && $limit === count($entries)) {
+ break;
+ }
+ }
+ } else {
+ $count += 1;
+ if (! $serverSorting || $offset === 0 || $offset < $count) {
+ $entries[ldap_get_dn($ds, $entry)] = $rows;
+ }
+ }
+ } else {
+ $count += 1;
+ if (! $serverSorting || $offset === 0 || $offset < $count) {
+ $entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(
+ ldap_get_attributes($ds, $entry),
+ array_flip($fields)
+ );
+ }
}
} while (
(! $serverSorting || $limit === 0 || $limit !== count($entries))
@@ -861,9 +933,6 @@ class LdapConnection implements Selectable, Inspectable
// the server: https://www.ietf.org/rfc/rfc2696.txt
ldap_control_paged_result($ds, 0, false, $cookie);
ldap_search($ds, $base, $queryString); // Returns no entries, due to the page size
- } else {
- // Reset the paged search request so that subsequent requests succeed
- ldap_control_paged_result($ds, 0);
}
if (! $serverSorting && $query->hasOrder()) {
@@ -879,14 +948,16 @@ class LdapConnection implements Selectable, Inspectable
/**
* Clean up the given attributes and return them as simple object
*
- * Applies column aliases, aggregates multi-value attributes as array and sets null for each missing attribute.
+ * Applies column aliases, aggregates/unfolds multi-value attributes
+ * as array and sets null for each missing attribute.
*
* @param array $attributes
* @param array $requestedFields
+ * @param string $unfoldAttribute
*
- * @return object
+ * @return object|array An array in case the object has been unfolded
*/
- public function cleanupAttributes($attributes, array $requestedFields)
+ public function cleanupAttributes($attributes, array $requestedFields, $unfoldAttribute = null)
{
// In case the result contains attributes with a differing case than the requested fields, it is
// necessary to create another array to map attributes case insensitively to their requested counterparts.
@@ -927,6 +998,24 @@ class LdapConnection implements Selectable, Inspectable
}
}
+ if (
+ $unfoldAttribute !== null
+ && isset($cleanedAttributes[$unfoldAttribute])
+ && is_array($cleanedAttributes[$unfoldAttribute])
+ ) {
+ $values = $cleanedAttributes[$unfoldAttribute];
+ unset($cleanedAttributes[$unfoldAttribute]);
+ $baseRow = (object) $cleanedAttributes;
+ $rows = array();
+ foreach ($values as $value) {
+ $row = clone $baseRow;
+ $row->{$unfoldAttribute} = $value;
+ $rows[] = $row;
+ }
+
+ return $rows;
+ }
+
return (object) $cleanedAttributes;
}
diff --git a/library/Icinga/Protocol/Ldap/LdapQuery.php b/library/Icinga/Protocol/Ldap/LdapQuery.php
index 1af5467d7..6d76bb897 100644
--- a/library/Icinga/Protocol/Ldap/LdapQuery.php
+++ b/library/Icinga/Protocol/Ldap/LdapQuery.php
@@ -35,6 +35,13 @@ class LdapQuery extends SimpleQuery
*/
protected $usePagedResults;
+ /**
+ * The name of the attribute used to unfold the result
+ *
+ * @var string
+ */
+ protected $unfoldAttribute;
+
/**
* Initialize this query
*/
@@ -90,6 +97,29 @@ class LdapQuery extends SimpleQuery
return $this->usePagedResults;
}
+ /**
+ * Set the attribute to be used to unfold the result
+ *
+ * @param string $attributeName
+ *
+ * @return $this
+ */
+ public function setUnfoldAttribute($attributeName)
+ {
+ $this->unfoldAttribute = $attributeName;
+ return $this;
+ }
+
+ /**
+ * Return the attribute to use to unfold the result
+ *
+ * @return string
+ */
+ public function getUnfoldAttribute()
+ {
+ return $this->unfoldAttribute;
+ }
+
/**
* Choose an objectClass and the columns you are interested in
*
diff --git a/library/Icinga/Repository/LdapRepository.php b/library/Icinga/Repository/LdapRepository.php
index ac210516d..5e79c41bb 100644
--- a/library/Icinga/Repository/LdapRepository.php
+++ b/library/Icinga/Repository/LdapRepository.php
@@ -28,13 +28,27 @@ abstract class LdapRepository extends Repository
* @var array
*/
protected $normedAttributes = array(
- 'uid' => 'uid',
- 'gid' => 'gid',
- 'user' => 'user',
- 'group' => 'group',
- 'member' => 'member',
- 'inetorgperson' => 'inetOrgPerson',
- 'samaccountname' => 'sAMAccountName'
+ 'uid' => 'uid',
+ 'gid' => 'gid',
+ 'user' => 'user',
+ 'group' => 'group',
+ 'member' => 'member',
+ 'memberuid' => 'memberUid',
+ 'posixgroup' => 'posixGroup',
+ 'uniquemember' => 'uniqueMember',
+ 'groupofnames' => 'groupOfNames',
+ 'inetorgperson' => 'inetOrgPerson',
+ 'samaccountname' => 'sAMAccountName',
+ 'groupofuniquenames' => 'groupOfUniqueNames'
+ );
+
+ /**
+ * Object attributes whose value is not distinguished name
+ *
+ * @var array
+ */
+ protected $ambiguousAttributes = array(
+ 'posixGroup' => 'memberUid'
);
/**
@@ -63,4 +77,17 @@ abstract class LdapRepository extends Repository
return $name;
}
-}
\ No newline at end of file
+
+ /**
+ * Return whether the given object attribute's value is not a distinguished name
+ *
+ * @param string $objectClass
+ * @param string $attributeName
+ *
+ * @return bool
+ */
+ protected function isAmbiguous($objectClass, $attributeName)
+ {
+ return isset($this->ambiguousAttributes[$objectClass][$attributeName]);
+ }
+}
diff --git a/library/Icinga/User.php b/library/Icinga/User.php
index 114e59755..626905e9c 100644
--- a/library/Icinga/User.php
+++ b/library/Icinga/User.php
@@ -479,22 +479,6 @@ class User
return false;
}
- /**
- * Load and return this user's navigation configuration
- *
- * @return Config
- */
- public function loadNavigationConfig()
- {
- return Config::fromIni(
- Config::resolvePath('preferences')
- . DIRECTORY_SEPARATOR
- . $this->getUsername()
- . DIRECTORY_SEPARATOR
- . 'navigation.ini'
- );
- }
-
/**
* Load and return this user's configured navigation of the given type
*
@@ -504,12 +488,11 @@ class User
*/
public function getNavigation($type)
{
- $config = $this->loadNavigationConfig();
- $config->getConfigObject()->setKeyColumn('name');
+ $config = Config::navigation($type === 'dashboard-pane' ? 'dashlet' : $type, $this->getUsername());
if ($type === 'dashboard-pane') {
$panes = array();
- foreach ($config->select()->where('type', 'dashlet') as $dashletName => $dashletConfig) {
+ foreach ($config as $dashletName => $dashletConfig) {
// TODO: Throw ConfigurationError if pane or url is missing
$panes[$dashletConfig->pane][$dashletName] = $dashletConfig->url;
}
@@ -525,7 +508,7 @@ class User
);
}
} else {
- $navigation = Navigation::fromConfig($config->select()->where('type', $type));
+ $navigation = Navigation::fromConfig($config);
}
return $navigation;
diff --git a/library/Icinga/Web/FileCache.php b/library/Icinga/Web/FileCache.php
index 57c0c0964..208640868 100644
--- a/library/Icinga/Web/FileCache.php
+++ b/library/Icinga/Web/FileCache.php
@@ -1,5 +1,6 @@
merge(Icinga::app()->getSharedNavigation($type));
-
- // User Preferences
$user = Auth::getInstance()->getUser();
- $this->merge($user->getNavigation($type));
+ if ($type !== 'dashboard-pane') {
+ // Shareables
+ $this->merge(Icinga::app()->getSharedNavigation($type));
+
+ // User Preferences
+ $this->merge($user->getNavigation($type));
+ }
// Modules
$moduleManager = Icinga::app()->getModuleManager();
@@ -451,6 +453,39 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate
return $this;
}
+ /**
+ * Return the global navigation item type configuration
+ *
+ * @return array
+ */
+ public static function getItemTypeConfiguration()
+ {
+ $defaultItemTypes = array(
+ 'menu-item' => array(
+ 'label' => t('Menu Entry'),
+ 'config' => 'menu'
+ )/*, // Disabled, until it is able to fully replace the old implementation
+ 'dashlet' => array(
+ 'label' => 'Dashlet',
+ 'config' => 'dashboard'
+ )*/
+ );
+
+ $moduleItemTypes = array();
+ $moduleManager = Icinga::app()->getModuleManager();
+ foreach ($moduleManager->getLoadedModules() as $module) {
+ if (Auth::getInstance()->hasPermission($moduleManager::MODULE_PERMISSION_NS . $module->getName())) {
+ foreach ($module->getNavigationItems() as $type => $options) {
+ if (! isset($moduleItemTypes[$type])) {
+ $moduleItemTypes[$type] = $options;
+ }
+ }
+ }
+ }
+
+ return array_merge($defaultItemTypes, $moduleItemTypes);
+ }
+
/**
* Create and return a new set of navigation items for the given configuration
*
diff --git a/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php
index 832e61064..3b739c320 100644
--- a/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php
+++ b/library/Icinga/Web/Navigation/Renderer/NavigationItemRenderer.php
@@ -36,6 +36,13 @@ class NavigationItemRenderer
*/
protected $internalLinkTargets;
+ /**
+ * Whether to escape the label
+ *
+ * @var bool
+ */
+ protected $escapeLabel;
+
/**
* Create a new NavigationItemRenderer
*
@@ -126,6 +133,29 @@ class NavigationItemRenderer
return $this->item;
}
+ /**
+ * Set whether to escape the label
+ *
+ * @param bool $state
+ *
+ * @return $this
+ */
+ public function setEscapeLabel($state = true)
+ {
+ $this->escapeLabel = (bool) $state;
+ return $this;
+ }
+
+ /**
+ * Return whether to escape the label
+ *
+ * @return bool
+ */
+ public function getEscapeLabel()
+ {
+ return $this->escapeLabel !== null ? $this->escapeLabel : true;
+ }
+
/**
* Render the given navigation item as HTML anchor
*
@@ -144,7 +174,9 @@ class NavigationItemRenderer
);
}
- $label = $this->view()->escape($item->getLabel());
+ $label = $this->getEscapeLabel()
+ ? $this->view()->escape($item->getLabel())
+ : $item->getLabel();
if (($icon = $item->getIcon()) !== null) {
$label = $this->view()->icon($icon) . $label;
}
diff --git a/library/Icinga/Web/StyleSheet.php b/library/Icinga/Web/StyleSheet.php
index d1aab6877..0b1eac56c 100644
--- a/library/Icinga/Web/StyleSheet.php
+++ b/library/Icinga/Web/StyleSheet.php
@@ -37,7 +37,9 @@ class StyleSheet
'css/icinga/selection-toolbar.less',
'css/icinga/login.less',
'css/icinga/controls.less',
- 'css/icinga/dev.less'
+ 'css/icinga/dev.less',
+ 'css/icinga/logo.less',
+ 'css/icinga/about.less'
);
public static function compileForPdf()
diff --git a/library/Icinga/Web/Url.php b/library/Icinga/Web/Url.php
index 9f31447ea..37791986c 100644
--- a/library/Icinga/Web/Url.php
+++ b/library/Icinga/Web/Url.php
@@ -75,7 +75,7 @@ class Url
}
$url = new Url();
- $url->setPath($request->getPathInfo());
+ $url->setPath(ltrim($request->getPathInfo(), '/'));
// $urlParams = UrlParams::fromQueryString($request->getQuery());
if (isset($_SERVER['QUERY_STRING'])) {
@@ -159,16 +159,17 @@ class Url
if (isset($urlParts['path'])) {
$urlPath = $urlParts['path'];
if ($urlPath && $urlPath[0] === '/') {
- $baseUrl = '';
+ if ($baseUrl) {
+ $urlPath = substr($urlPath, 1);
+ } elseif (strpos($urlPath, $request->getBaseUrl()) === 0) {
+ $baseUrl = $request->getBaseUrl();
+ $urlPath = substr($urlPath, strlen($baseUrl) + 1);
+ }
} elseif (! $baseUrl) {
$baseUrl = $request->getBaseUrl();
}
- if ($baseUrl && !$urlObject->isExternal() && strpos($urlPath, $baseUrl) === 0) {
- $urlObject->setPath(substr($urlPath, strlen($baseUrl)));
- } else {
- $urlObject->setPath($urlPath);
- }
+ $urlObject->setPath($urlPath);
} elseif (! $baseUrl) {
$baseUrl = $request->getBaseUrl();
}
@@ -255,7 +256,7 @@ class Url
*/
public function setPath($path)
{
- $this->path = ltrim($path, '/');
+ $this->path = $path;
return $this;
}
diff --git a/library/Icinga/Web/View/helpers/url.php b/library/Icinga/Web/View/helpers/url.php
index 06ace7f19..962f3cb6e 100644
--- a/library/Icinga/Web/View/helpers/url.php
+++ b/library/Icinga/Web/View/helpers/url.php
@@ -43,6 +43,11 @@ $this->addHelperFunction('qlink', function ($title, $url, $params = null, $prope
$icon = $view->icon($properties['icon']);
unset($properties['icon']);
}
+
+ if (array_key_exists('img', $properties)) {
+ $icon = $view->img($properties['img']);
+ unset($properties['img']);
+ }
}
return sprintf(
diff --git a/library/Icinga/Web/Widget/Dashboard.php b/library/Icinga/Web/Widget/Dashboard.php
index c69fb4477..4ff192122 100644
--- a/library/Icinga/Web/Widget/Dashboard.php
+++ b/library/Icinga/Web/Widget/Dashboard.php
@@ -85,6 +85,7 @@ class Dashboard extends AbstractWidget
}
$this->mergePanes($panes);
+ $this->loadUserDashboards();
return $this;
}
diff --git a/library/Icinga/Web/Widget/Tabextension/MenuAction.php b/library/Icinga/Web/Widget/Tabextension/MenuAction.php
new file mode 100644
index 000000000..7291f7595
--- /dev/null
+++ b/library/Icinga/Web/Widget/Tabextension/MenuAction.php
@@ -0,0 +1,35 @@
+addAsDropdown(
+ 'menu-entry',
+ array(
+ 'icon' => 'menu',
+ 'label' => t('Add To Menu'),
+ 'url' => Url::fromPath('navigation/add'),
+ 'urlParams' => array(
+ 'url' => rawurlencode(Url::fromRequest()->getRelativeUrl())
+ )
+ )
+ );
+ }
+}
diff --git a/modules/monitoring/application/controllers/AlertsummaryController.php b/modules/monitoring/application/controllers/AlertsummaryController.php
index dc28d9035..969131074 100644
--- a/modules/monitoring/application/controllers/AlertsummaryController.php
+++ b/modules/monitoring/application/controllers/AlertsummaryController.php
@@ -16,6 +16,7 @@ use Icinga\Module\Monitoring\Controller;
use Icinga\Module\Monitoring\Web\Widget\SelectBox;
use Icinga\Web\Url;
use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
class AlertsummaryController extends Controller
{
@@ -53,7 +54,7 @@ class AlertsummaryController extends Controller
'label' => $this->translate('Alert Summary'),
'url' => Url::fromRequest()
)
- )->extend(new DashboardAction())->activate('alertsummary');
+ )->extend(new DashboardAction())->extend(new MenuAction())->activate('alertsummary');
$this->view->title = $this->translate('Alert Summary');
$this->view->intervalBox = $this->createIntervalBox();
diff --git a/modules/monitoring/application/controllers/CommentController.php b/modules/monitoring/application/controllers/CommentController.php
index df170f1fa..5f61fb79d 100644
--- a/modules/monitoring/application/controllers/CommentController.php
+++ b/modules/monitoring/application/controllers/CommentController.php
@@ -7,6 +7,7 @@ use Icinga\Module\Monitoring\Controller;
use Icinga\Module\Monitoring\Forms\Command\Object\DeleteCommentCommandForm;
use Icinga\Web\Url;
use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
/**
* Display detailed information about a comment
@@ -55,7 +56,7 @@ class CommentController extends Controller
'title' => $this->translate('Display detailed information about a comment.'),
'url' =>'monitoring/comments/show'
)
- )->activate('comment')->extend(new DashboardAction());
+ )->activate('comment')->extend(new DashboardAction())->extend(new MenuAction());
}
/**
diff --git a/modules/monitoring/application/controllers/DowntimeController.php b/modules/monitoring/application/controllers/DowntimeController.php
index 946b69985..2b8c9599b 100644
--- a/modules/monitoring/application/controllers/DowntimeController.php
+++ b/modules/monitoring/application/controllers/DowntimeController.php
@@ -9,6 +9,7 @@ use Icinga\Module\Monitoring\Object\Host;
use Icinga\Module\Monitoring\Object\Service;
use Icinga\Web\Url;
use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
/**
* Display detailed information about a downtime
@@ -65,7 +66,7 @@ class DowntimeController extends Controller
'title' => $this->translate('Display detailed information about a downtime.'),
'url' =>'monitoring/downtimes/show'
)
- )->activate('downtime')->extend(new DashboardAction());
+ )->activate('downtime')->extend(new DashboardAction())->extend(new MenuAction());
}
/**
diff --git a/modules/monitoring/application/controllers/HealthController.php b/modules/monitoring/application/controllers/HealthController.php
index bbfdcb030..5905decd5 100644
--- a/modules/monitoring/application/controllers/HealthController.php
+++ b/modules/monitoring/application/controllers/HealthController.php
@@ -7,6 +7,7 @@ use Icinga\Module\Monitoring\Controller;
use Icinga\Module\Monitoring\Forms\Command\Instance\DisableNotificationsExpireCommandForm;
use Icinga\Module\Monitoring\Forms\Command\Instance\ToggleInstanceFeaturesCommandForm;
use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
/**
* Display process and performance information of the monitoring host and program-wide commands
@@ -43,7 +44,7 @@ class HealthController extends Controller
'url' =>'monitoring/health/stats'
)
)
- ->extend(new DashboardAction());
+ ->extend(new DashboardAction())->extend(new MenuAction());
}
/**
diff --git a/modules/monitoring/application/controllers/HostController.php b/modules/monitoring/application/controllers/HostController.php
index 4a3f77a6a..6f0fe720a 100644
--- a/modules/monitoring/application/controllers/HostController.php
+++ b/modules/monitoring/application/controllers/HostController.php
@@ -77,6 +77,7 @@ class HostController extends MonitoredObjectController
'host_state_type',
'host_last_state_change',
'host_address',
+ 'host_address6',
'host_handled',
'service_description',
'service_display_name',
diff --git a/modules/monitoring/application/controllers/HostsController.php b/modules/monitoring/application/controllers/HostsController.php
index 96b78da62..bffbecc05 100644
--- a/modules/monitoring/application/controllers/HostsController.php
+++ b/modules/monitoring/application/controllers/HostsController.php
@@ -18,6 +18,7 @@ use Icinga\Module\Monitoring\Forms\Command\Object\SendCustomNotificationCommandF
use Icinga\Module\Monitoring\Object\HostList;
use Icinga\Web\Url;
use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
class HostsController extends Controller
{
@@ -44,7 +45,7 @@ class HostsController extends Controller
'url' => Url::fromRequest(),
'icon' => 'host'
)
- )->extend(new DashboardAction())->activate('show');
+ )->extend(new DashboardAction())->extend(new MenuAction())->activate('show');
$this->view->listAllLink = Url::fromRequest()->setPath('monitoring/list/hosts');
}
@@ -55,6 +56,7 @@ class HostsController extends Controller
'host_icon_image_alt',
'host_name',
'host_address',
+ 'host_address6',
'host_state',
'host_problem',
'host_handled',
@@ -92,6 +94,7 @@ class HostsController extends Controller
'host_icon_image_alt',
'host_name',
'host_address',
+ 'host_address6',
'host_state',
'host_problem',
'host_handled',
diff --git a/modules/monitoring/application/controllers/ListController.php b/modules/monitoring/application/controllers/ListController.php
index adbe78678..d08257557 100644
--- a/modules/monitoring/application/controllers/ListController.php
+++ b/modules/monitoring/application/controllers/ListController.php
@@ -13,6 +13,7 @@ use Icinga\Module\Monitoring\Forms\Command\Object\DeleteDowntimeCommandForm;
use Icinga\Module\Monitoring\Forms\StatehistoryForm;
use Icinga\Web\Url;
use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
use Icinga\Web\Widget\Tabextension\OutputFormat;
use Icinga\Web\Widget\Tabs;
@@ -58,7 +59,6 @@ class ListController extends Controller
'host_name',
'host_display_name',
'host_state' => $stateColumn,
- 'host_address',
'host_acknowledged',
'host_output',
'host_attempt',
@@ -66,15 +66,10 @@ class ListController extends Controller
'host_is_flapping',
'host_state_type',
'host_handled',
- 'host_last_check',
'host_last_state_change' => $stateChangeColumn,
'host_notifications_enabled',
- 'host_action_url',
- 'host_notes_url',
'host_active_checks_enabled',
- 'host_passive_checks_enabled',
- 'host_current_check_attempt',
- 'host_max_check_attempts'
+ 'host_passive_checks_enabled'
), $this->addColumns()));
$this->applyRestriction('monitoring/filter/objects', $query);
$this->filterQuery($query);
@@ -132,10 +127,6 @@ class ListController extends Controller
'host_name',
'host_display_name',
'host_state',
- 'host_state_type',
- 'host_last_state_change',
- 'host_address',
- 'host_handled',
'service_description',
'service_display_name',
'service_state' => $stateColumn,
@@ -152,14 +143,9 @@ class ListController extends Controller
'service_state_type',
'service_handled',
'service_severity',
- 'service_last_check',
'service_notifications_enabled',
- 'service_action_url',
- 'service_notes_url',
'service_active_checks_enabled',
- 'service_passive_checks_enabled',
- 'current_check_attempt' => 'service_current_check_attempt',
- 'max_check_attempts' => 'service_max_check_attempts'
+ 'service_passive_checks_enabled'
), $this->addColumns());
$query = $this->backend->select()->from('servicestatus', $columns);
$this->applyRestriction('monitoring/filter/objects', $query);
@@ -628,6 +614,6 @@ class ListController extends Controller
*/
private function createTabs()
{
- $this->getTabs()->extend(new OutputFormat())->extend(new DashboardAction());
+ $this->getTabs()->extend(new OutputFormat())->extend(new DashboardAction())->extend(new MenuAction());
}
}
diff --git a/modules/monitoring/application/controllers/ServicesController.php b/modules/monitoring/application/controllers/ServicesController.php
index 26561f8ff..bb51a2108 100644
--- a/modules/monitoring/application/controllers/ServicesController.php
+++ b/modules/monitoring/application/controllers/ServicesController.php
@@ -17,6 +17,7 @@ use Icinga\Module\Monitoring\Forms\Command\Object\SendCustomNotificationCommandF
use Icinga\Module\Monitoring\Object\ServiceList;
use Icinga\Web\Url;
use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
class ServicesController extends Controller
{
@@ -46,7 +47,7 @@ class ServicesController extends Controller
'url' => Url::fromRequest(),
'icon' => 'services'
)
- )->extend(new DashboardAction())->activate('show');
+ )->extend(new DashboardAction())->extend(new MenuAction())->activate('show');
}
protected function handleCommandForm(ObjectsCommandForm $form)
@@ -56,6 +57,7 @@ class ServicesController extends Controller
'host_icon_image_alt',
'host_name',
'host_address',
+ 'host_address6',
'host_output',
'host_state',
'host_problem',
@@ -101,6 +103,7 @@ class ServicesController extends Controller
'host_icon_image_alt',
'host_name',
'host_address',
+ 'host_address6',
'host_output',
'host_state',
'host_problem',
diff --git a/modules/monitoring/application/controllers/TacticalController.php b/modules/monitoring/application/controllers/TacticalController.php
index 40641128b..03f2ff37b 100644
--- a/modules/monitoring/application/controllers/TacticalController.php
+++ b/modules/monitoring/application/controllers/TacticalController.php
@@ -6,6 +6,7 @@ namespace Icinga\Module\Monitoring\Controllers;
use Icinga\Module\Monitoring\Controller;
use Icinga\Web\Url;
use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
class TacticalController extends Controller
{
@@ -22,7 +23,7 @@ class TacticalController extends Controller
'label' => $this->translate('Tactical Overview'),
'url' => Url::fromRequest()
)
- )->extend(new DashboardAction())->activate('tactical_overview');
+ )->extend(new DashboardAction())->extend(new MenuAction())->activate('tactical_overview');
$stats = $this->backend->select()->from(
'statussummary',
array(
diff --git a/modules/monitoring/application/controllers/TimelineController.php b/modules/monitoring/application/controllers/TimelineController.php
index 440513e63..39d640f6f 100644
--- a/modules/monitoring/application/controllers/TimelineController.php
+++ b/modules/monitoring/application/controllers/TimelineController.php
@@ -12,6 +12,7 @@ use Icinga\Module\Monitoring\Web\Widget\SelectBox;
use Icinga\Util\Format;
use Icinga\Web\Url;
use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
class TimelineController extends Controller
{
@@ -24,7 +25,7 @@ class TimelineController extends Controller
'label' => $this->translate('Timeline'),
'url' => Url::fromRequest()
)
- )->extend(new DashboardAction())->activate('timeline');
+ )->extend(new DashboardAction())->extend(new MenuAction())->activate('timeline');
$this->view->title = $this->translate('Timeline');
// TODO: filter for hard_states (precedence adjustments necessary!)
diff --git a/modules/monitoring/application/views/scripts/show/components/actions.phtml b/modules/monitoring/application/views/scripts/show/components/actions.phtml
index 2e55db68c..bffd949dd 100644
--- a/modules/monitoring/application/views/scripts/show/components/actions.phtml
+++ b/modules/monitoring/application/views/scripts/show/components/actions.phtml
@@ -16,7 +16,11 @@ foreach ($object->getActionUrls() as $i => $link) {
'Action ' . ($i + 1) . $newTabInfo,
array(
'url' => $link,
- 'target' => '_blank'
+ 'target' => '_blank',
+ 'renderer' => array(
+ 'NavigationItemRenderer',
+ 'escape_label' => false
+ )
)
);
}
diff --git a/modules/monitoring/application/views/scripts/show/components/customvars.phtml b/modules/monitoring/application/views/scripts/show/components/customvars.phtml
index 6ef2c678b..77a611952 100644
--- a/modules/monitoring/application/views/scripts/show/components/customvars.phtml
+++ b/modules/monitoring/application/views/scripts/show/components/customvars.phtml
@@ -1,6 +1,6 @@
customvars as $name => $value): ?>
- | = $this->escape($name) ?> |
+ = $this->escape(ucwords(str_replace('_', ' ', strtolower($name)))) ?> |
= $this->customvar($value) ?> |
diff --git a/modules/monitoring/application/views/scripts/show/components/notes.phtml b/modules/monitoring/application/views/scripts/show/components/notes.phtml
index 1b3dafc52..4aa8b6bfc 100644
--- a/modules/monitoring/application/views/scripts/show/components/notes.phtml
+++ b/modules/monitoring/application/views/scripts/show/components/notes.phtml
@@ -3,7 +3,7 @@
use Icinga\Web\Navigation\Navigation;
$navigation = new Navigation();
-$navigation->load($object->getType() . '-note');
+//$navigation->load($object->getType() . '-note');
foreach ($navigation as $item) {
$item->setObject($object);
}
@@ -26,7 +26,11 @@ if (! empty($links)) {
$this->escape($link) . $newTabInfo,
array(
'url' => $link,
- 'target' => '_blank'
+ 'target' => '_blank',
+ 'renderer' => array(
+ 'NavigationItemRenderer',
+ 'escape_label' => false
+ )
)
);
}
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatusQuery.php
index 02a2fb8fb..d888daa78 100644
--- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatusQuery.php
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/HoststatusQuery.php
@@ -28,6 +28,7 @@ class HoststatusQuery extends IdoQuery
'host' => 'ho.name1 COLLATE latin1_general_ci',
'host_action_url' => 'h.action_url',
'host_address' => 'h.address',
+ 'host_address6' => 'h.address6',
'host_alias' => 'h.alias',
'host_display_name' => 'h.display_name COLLATE latin1_general_ci',
'host_icon_image' => 'h.icon_image',
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php
index 122e13dc0..1480e2834 100644
--- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/IdoQuery.php
@@ -832,7 +832,7 @@ abstract class IdoQuery extends DbQuery
list($type, $name) = $this->customvarNameToTypeName($customvar);
$alias = ($type === 'host' ? 'hcv_' : 'scv_') . $name;
- $this->customVars[$customvar] = $alias;
+ $this->customVars[strtolower($customvar)] = $alias;
if ($this->hasJoinedVirtualTable('services')) {
$leftcol = 's.' . $type . '_object_id';
diff --git a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatusQuery.php b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatusQuery.php
index a4b1d7272..f2582d211 100644
--- a/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatusQuery.php
+++ b/modules/monitoring/library/Monitoring/Backend/Ido/Query/ServicestatusQuery.php
@@ -27,6 +27,7 @@ class ServicestatusQuery extends IdoQuery
'hosts' => array(
'host_action_url' => 'h.action_url',
'host_address' => 'h.address',
+ 'host_address6' => 'h.address6',
'host_alias' => 'h.alias COLLATE latin1_general_ci',
'host_display_name' => 'h.display_name COLLATE latin1_general_ci',
'host_icon_image' => 'h.icon_image',
diff --git a/modules/monitoring/library/Monitoring/Controller.php b/modules/monitoring/library/Monitoring/Controller.php
index 1dc1d28eb..e1c16c854 100644
--- a/modules/monitoring/library/Monitoring/Controller.php
+++ b/modules/monitoring/library/Monitoring/Controller.php
@@ -82,7 +82,7 @@ class Controller extends IcingaWebController
'service_description',
'servicegroup_name',
function ($c) {
- return preg_match('/^_(?:host|service)_/', $c);
+ return preg_match('/^_(?:host|service)_/i', $c);
}
));
foreach ($this->getRestrictions($name) as $filter) {
diff --git a/modules/monitoring/library/Monitoring/DataView/DataView.php b/modules/monitoring/library/Monitoring/DataView/DataView.php
index 845aa4db4..50cd436fd 100644
--- a/modules/monitoring/library/Monitoring/DataView/DataView.php
+++ b/modules/monitoring/library/Monitoring/DataView/DataView.php
@@ -185,7 +185,11 @@ abstract class DataView implements QueryInterface, SortRules, FilterColumns, Ite
*/
public function isValidFilterTarget($column)
{
- return in_array($column, $this->getFilterColumns());
+ // Customvar
+ if ($column[0] === '_' && preg_match('/^_(?:host|service)_/i', $column)) {
+ return true;
+ }
+ return in_array($column, $this->getColumns()) || in_array($column, $this->getStaticFilterColumns());
}
/**
diff --git a/modules/monitoring/library/Monitoring/DataView/Hoststatus.php b/modules/monitoring/library/Monitoring/DataView/Hoststatus.php
index f30e00193..f2fe139ec 100644
--- a/modules/monitoring/library/Monitoring/DataView/Hoststatus.php
+++ b/modules/monitoring/library/Monitoring/DataView/Hoststatus.php
@@ -16,6 +16,7 @@ class HostStatus extends DataView
'host_display_name',
'host_alias',
'host_address',
+ 'host_address6',
'host_state',
'host_state_type',
'host_handled',
diff --git a/modules/monitoring/library/Monitoring/DataView/Servicestatus.php b/modules/monitoring/library/Monitoring/DataView/Servicestatus.php
index 8c19cb1ab..ff63e8604 100644
--- a/modules/monitoring/library/Monitoring/DataView/Servicestatus.php
+++ b/modules/monitoring/library/Monitoring/DataView/Servicestatus.php
@@ -18,6 +18,7 @@ class ServiceStatus extends DataView
'host_state_type',
'host_last_state_change',
'host_address',
+ 'host_address6',
'host_problem',
'host_handled',
'service_description',
diff --git a/modules/monitoring/library/Monitoring/Object/Host.php b/modules/monitoring/library/Monitoring/Object/Host.php
index 75c31c3ea..49f2f4306 100644
--- a/modules/monitoring/library/Monitoring/Object/Host.php
+++ b/modules/monitoring/library/Monitoring/Object/Host.php
@@ -95,6 +95,7 @@ class Host extends MonitoredObject
'host_active_checks_enabled',
'host_active_checks_enabled_changed',
'host_address',
+ 'host_address6',
'host_alias',
'host_attempt',
'host_check_command',
diff --git a/modules/monitoring/library/Monitoring/Object/Macro.php b/modules/monitoring/library/Monitoring/Object/Macro.php
index 0a6f3a8c0..73e982b15 100644
--- a/modules/monitoring/library/Monitoring/Object/Macro.php
+++ b/modules/monitoring/library/Monitoring/Object/Macro.php
@@ -14,11 +14,13 @@ class Macro
* @var array
*/
private static $icingaMacros = array(
- 'HOSTNAME' => 'host_name',
- 'HOSTADDRESS' => 'host_address',
- 'SERVICEDESC' => 'service_description',
- 'host.name' => 'host_name',
- 'host.address' => 'host_address',
+ 'HOSTNAME' => 'host_name',
+ 'HOSTADDRESS' => 'host_address',
+ 'HOSTADDRESS6' => 'host_address6',
+ 'SERVICEDESC' => 'service_description',
+ 'host.name' => 'host_name',
+ 'host.address' => 'host_address',
+ 'host.address6' => 'host_address6',
'service.description' => 'service_description'
);
@@ -58,8 +60,9 @@ class Macro
if (isset(self::$icingaMacros[$macro]) && isset($object->{self::$icingaMacros[$macro]})) {
return $object->{self::$icingaMacros[$macro]};
}
- if (isset($object->customvars[$macro])) {
- return $object->customvars[$macro];
+ $customVar = strtolower($macro);
+ if (isset($object->customvars[$customVar])) {
+ return $object->customvars[$customVar];
}
return $macro;
diff --git a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php
index 98ada34de..866e57c60 100644
--- a/modules/monitoring/library/Monitoring/Object/MonitoredObject.php
+++ b/modules/monitoring/library/Monitoring/Object/MonitoredObject.php
@@ -239,7 +239,7 @@ abstract class MonitoredObject implements Filterable
foreach ($this->customvars as $name => $value) {
if (! is_object($value)) {
- $row->{'_' . $this->getType() . '_' . strtolower(str_replace(' ', '_', $name))} = $value;
+ $row->{'_' . $this->getType() . '_' . $name} = $value;
}
}
}
@@ -477,8 +477,8 @@ abstract class MonitoredObject implements Filterable
$this->customvars = array();
$customvars = $query->getQuery()->fetchAll();
- foreach ($customvars as $name => $cv) {
- $name = ucwords(str_replace('_', ' ', strtolower($cv->varname)));
+ foreach ($customvars as $cv) {
+ $name = strtolower($cv->varname);
if ($blacklistPattern && preg_match($blacklistPattern, $cv->varname)) {
$this->customvars[$name] = '***';
} elseif ($cv->is_json) {
diff --git a/modules/monitoring/library/Monitoring/Object/Service.php b/modules/monitoring/library/Monitoring/Object/Service.php
index f4f3ce748..ee75706ea 100644
--- a/modules/monitoring/library/Monitoring/Object/Service.php
+++ b/modules/monitoring/library/Monitoring/Object/Service.php
@@ -112,6 +112,7 @@ class Service extends MonitoredObject
'host_acknowledged',
'host_active_checks_enabled',
'host_address',
+ 'host_address6',
'host_alias',
'host_display_name',
'host_handled',
diff --git a/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php b/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php
index 5ccadf5a6..9fdaff259 100644
--- a/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php
+++ b/modules/monitoring/library/Monitoring/Web/Controller/MonitoredObjectController.php
@@ -13,6 +13,7 @@ use Icinga\Module\Monitoring\Forms\Command\Object\ToggleObjectFeaturesCommandFor
use Icinga\Web\Hook;
use Icinga\Web\Url;
use Icinga\Web\Widget\Tabextension\DashboardAction;
+use Icinga\Web\Widget\Tabextension\MenuAction;
/**
* Base class for the host and service controller
@@ -232,6 +233,6 @@ abstract class MonitoredObjectController extends Controller
)
);
}
- $tabs->extend(new DashboardAction());
+ $tabs->extend(new DashboardAction())->extend(new MenuAction());
}
}
diff --git a/modules/monitoring/test/php/library/Monitoring/Object/MacroTest.php b/modules/monitoring/test/php/library/Monitoring/Object/MacroTest.php
index e1b3595e3..89dae3b4d 100644
--- a/modules/monitoring/test/php/library/Monitoring/Object/MacroTest.php
+++ b/modules/monitoring/test/php/library/Monitoring/Object/MacroTest.php
@@ -16,11 +16,14 @@ class MacroTest extends BaseTestCase
$hostMock = Mockery::mock('host');
$hostMock->host_name = 'test';
$hostMock->host_address = '1.1.1.1';
+ $hostMock->host_address6 = '::1';
$this->assertEquals(Macro::resolveMacros('$HOSTNAME$', $hostMock), $hostMock->host_name);
$this->assertEquals(Macro::resolveMacros('$HOSTADDRESS$', $hostMock), $hostMock->host_address);
+ $this->assertEquals(Macro::resolveMacros('$HOSTADDRESS6$', $hostMock), $hostMock->host_address6);
$this->assertEquals(Macro::resolveMacros('$host.name$', $hostMock), $hostMock->host_name);
$this->assertEquals(Macro::resolveMacros('$host.address$', $hostMock), $hostMock->host_address);
+ $this->assertEquals(Macro::resolveMacros('$host.address6$', $hostMock), $hostMock->host_address6);
}
public function testServiceMacros()
@@ -28,13 +31,16 @@ class MacroTest extends BaseTestCase
$svcMock = Mockery::mock('service');
$svcMock->host_name = 'test';
$svcMock->host_address = '1.1.1.1';
+ $svcMock->host_address6 = '::1';
$svcMock->service_description = 'a service';
$this->assertEquals(Macro::resolveMacros('$HOSTNAME$', $svcMock), $svcMock->host_name);
$this->assertEquals(Macro::resolveMacros('$HOSTADDRESS$', $svcMock), $svcMock->host_address);
+ $this->assertEquals(Macro::resolveMacros('$HOSTADDRESS6$', $svcMock), $svcMock->host_address6);
$this->assertEquals(Macro::resolveMacros('$SERVICEDESC$', $svcMock), $svcMock->service_description);
$this->assertEquals(Macro::resolveMacros('$host.name$', $svcMock), $svcMock->host_name);
$this->assertEquals(Macro::resolveMacros('$host.address$', $svcMock), $svcMock->host_address);
+ $this->assertEquals(Macro::resolveMacros('$host.address6$', $svcMock), $svcMock->host_address6);
$this->assertEquals(Macro::resolveMacros('$service.description$', $svcMock), $svcMock->service_description);
}
diff --git a/modules/setup/application/views/scripts/index/parts/finish.phtml b/modules/setup/application/views/scripts/index/parts/finish.phtml
index a56c07c3a..847ae5b92 100644
--- a/modules/setup/application/views/scripts/index/parts/finish.phtml
+++ b/modules/setup/application/views/scripts/index/parts/finish.phtml
@@ -8,7 +8,7 @@
= $this->qlink(
$this->translate('Login to Icinga Web 2'),
- 'authentication/login',
+ 'authentication/login?renderLayout',
null,
array(
'class' => 'button-like login',
@@ -30,4 +30,4 @@
= join("\n\n", array_map(function($a) {
return join("\n", $a);
}, $report)); ?>
-
\ No newline at end of file
+
diff --git a/public/css/icinga/about.less b/public/css/icinga/about.less
new file mode 100644
index 000000000..320f2f4de
--- /dev/null
+++ b/public/css/icinga/about.less
@@ -0,0 +1,9 @@
+div.about {
+ width: 600px;
+ margin: 0 auto;
+ text-align: center;
+
+ .about-modules {
+ text-align: initial;
+ }
+}
\ No newline at end of file
diff --git a/public/css/icinga/layout-structure.less b/public/css/icinga/layout-structure.less
index 3d2cb8774..7a850f8b6 100644
--- a/public/css/icinga/layout-structure.less
+++ b/public/css/icinga/layout-structure.less
@@ -271,14 +271,7 @@ html {
}
#login {
- .logo .image img {
- width: 70%;
- }
- .form {
- width: 100%;
- margin: auto;
- }
- .form label {
+ .below-logo label {
width: 100%;
margin: 0;
text-align: center;
diff --git a/public/css/icinga/logo.less b/public/css/icinga/logo.less
new file mode 100644
index 000000000..68fb81619
--- /dev/null
+++ b/public/css/icinga/logo.less
@@ -0,0 +1,47 @@
+/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
+
+.logo {
+ background-color: @colorPetrol;
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 60%;
+ border-bottom: 1px solid #d9d9d9d;
+ text-align: center;
+ -webkit-box-shadow: 0 3px 7px -3px #000;
+ -moz-box-shadow: 0 3px 7px -3px #000;
+ box-shadow: 0 3px 7px -3px #000;
+
+ .image {
+ position: absolute;
+ bottom: 1em;
+ left: 0px;
+ right: 0px;
+ text-align: center;
+
+ img {
+ width: 375px;
+ }
+ }
+}
+
+.below-logo {
+ position: absolute;
+ font-size: 0.9em;
+ top: 45%;
+ left: 0;
+ bottom: 0;
+ right: 0;
+}
+
+#layout.minimal-layout {
+ .logo .image img {
+ width: 70%;
+ }
+
+ .below-logo {
+ width: 100%;
+ margin: auto;
+ }
+}
diff --git a/public/img/bugreport.png b/public/img/bugreport.png
new file mode 100644
index 000000000..23d913291
Binary files /dev/null and b/public/img/bugreport.png differ
diff --git a/public/img/docs.png b/public/img/docs.png
new file mode 100644
index 000000000..051db6cce
Binary files /dev/null and b/public/img/docs.png differ
diff --git a/public/img/support.png b/public/img/support.png
new file mode 100644
index 000000000..17a0c9233
Binary files /dev/null and b/public/img/support.png differ
diff --git a/public/img/wiki.png b/public/img/wiki.png
new file mode 100644
index 000000000..02df73af6
Binary files /dev/null and b/public/img/wiki.png differ
diff --git a/public/js/icinga/behavior/selectable.js b/public/js/icinga/behavior/selectable.js
new file mode 100644
index 000000000..a628ed729
--- /dev/null
+++ b/public/js/icinga/behavior/selectable.js
@@ -0,0 +1,36 @@
+/*! Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
+
+;(function(Icinga, $) {
+ 'use strict';
+
+ Icinga.Behaviors = Icinga.Behaviors || {};
+
+ var Selectable = function(icinga) {
+ Icinga.EventListener.call(this, icinga);
+ this.on('rendered', this.onRendered, this);
+ };
+
+ $.extend(Selectable.prototype, new Icinga.EventListener(), {
+ onRendered: function(e) {
+ $('.selectable', e.target).on('dblclick', e.data.self.selectText);
+ },
+
+ selectText: function(e) {
+ var b = document.body,
+ r;
+ if (b.createTextRange) {
+ r = b.createTextRange();
+ r.moveToElementText(e.target);
+ r.select();
+ } else if (window.getSelection) {
+ var s = window.getSelection();
+ r = document.createRange();
+ r.selectNodeContents(e.target);
+ s.removeAllRanges();
+ s.addRange(r);
+ }
+ }
+ });
+
+ Icinga.Behaviors.Selectable = Selectable;
+})(Icinga, jQuery);