Merge branch 'master' into feature/dope-layout-5543

This commit is contained in:
Eric Lippmann 2015-09-30 18:52:52 +02:00
commit 231c63b703
72 changed files with 1147 additions and 382 deletions

View File

@ -14,11 +14,12 @@
# #
class openldap { class openldap {
package { ['openldap-servers', 'openldap-clients']: package { [ 'openldap-servers', 'openldap-clients', ]:
ensure => latest, ensure => latest,
} }
service { 'slapd': service { 'slapd':
enable => true,
ensure => running, ensure => running,
require => Package['openldap-servers'], require => Package['openldap-servers'],
} }

View File

@ -1,7 +1,7 @@
# Class: pgsql # Class: pgsql
# #
# This class installs the postgresql server and client software. # This class installs the PostgreSQL server and client software.
# Further it configures pg_hba.conf to trus the local icinga user. # Further it configures pg_hba.conf to trust the local icinga user.
# #
# Parameters: # Parameters:
# #
@ -17,26 +17,25 @@ class pgsql {
Exec { path => '/sbin:/bin:/usr/bin' } Exec { path => '/sbin:/bin:/usr/bin' }
package { [ package { [ 'postgresql', 'postgresql-server', ]:
'postgresql', 'postgresql-server' ensure => latest,
]:
ensure => latest,
} }
exec { 'initdb': exec { 'initdb':
creates => '/var/lib/pgsql/data/pg_xlog',
command => 'service postgresql initdb', command => 'service postgresql initdb',
require => Package['postgresql-server'] creates => '/var/lib/pgsql/data/pg_xlog',
require => Package['postgresql-server'],
} }
service { 'postgresql': service { 'postgresql':
enable => true,
ensure => running, ensure => running,
require => [Package['postgresql-server'], Exec['initdb']] require => [ Package['postgresql-server'], Exec['initdb'], ]
} }
file { '/var/lib/pgsql/data/pg_hba.conf': file { '/var/lib/pgsql/data/pg_hba.conf':
content => template('pgsql/pg_hba.conf.erb'), content => template('pgsql/pg_hba.conf.erb'),
require => [Package['postgresql-server'], Exec['initdb']], require => [ Package['postgresql-server'], Exec['initdb'], ],
notify => Service['postgresql'] notify => Service['postgresql'],
} }
} }

View File

@ -5,22 +5,47 @@ https://dev.icinga.org/projects/icingaweb2/roadmap
# Release Workflow # Release Workflow
## Authors
Update the [.mailmap](.mailmap) and [AUTHORS](AUTHORS) files: Update the [.mailmap](.mailmap) and [AUTHORS](AUTHORS) files:
$ git log --use-mailmap | grep ^Author: | cut -f2- -d' ' | sort | uniq > AUTHORS $ 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 Update the [ChangeLog](ChangeLog) file using
the changelog.py script. the changelog.py script.
Changelog: Changelog:
$ ./changelog.py --version 2.0.0-rc1 $ ./changelog.py --version 2.0.0
Wordpress: 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: Commit these changes to the "master" branch:

View File

@ -1 +1 @@
$Format:%H%d %ci$ $Format:%H %ci$

View File

@ -3,13 +3,23 @@
namespace Icinga\Controllers; namespace Icinga\Controllers;
use Icinga\Application\Icinga;
use Icinga\Application\Version; use Icinga\Application\Version;
use Icinga\Web\Controller\ActionController; use Icinga\Web\Controller;
class AboutController extends ActionController class AboutController extends Controller
{ {
public function indexAction() public function indexAction()
{ {
$this->view->version = Version::get(); $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');
} }
} }

View File

@ -35,6 +35,10 @@ class ErrorController extends ActionController
Logger::error($exception); Logger::error($exception);
Logger::error('Stacktrace: %s', $exception->getTraceAsString()); Logger::error('Stacktrace: %s', $exception->getTraceAsString());
if (! ($isAuthenticated = $this->Auth()->isAuthenticated())) {
$this->innerLayout = 'error';
}
switch ($error->type) { switch ($error->type) {
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE: case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ROUTE:
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER: case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
@ -45,11 +49,13 @@ class ErrorController extends ActionController
$path = array_shift($path); $path = array_shift($path);
$this->getResponse()->setHttpResponseCode(404); $this->getResponse()->setHttpResponseCode(404);
$this->view->message = $this->translate('Page not found.'); $this->view->message = $this->translate('Page not found.');
if ($this->Auth()->isAuthenticated() && $modules->hasInstalled($path) && ! $modules->hasEnabled($path)) { if ($isAuthenticated) {
$this->view->message .= ' ' . sprintf( if ($modules->hasInstalled($path) && ! $modules->hasEnabled($path)) {
$this->translate('Enabling the "%s" module might help!'), $this->view->message .= ' ' . sprintf(
$path $this->translate('Enabling the "%s" module might help!'),
); $path
);
}
} }
break; break;
@ -93,5 +99,6 @@ class ErrorController extends ActionController
} }
$this->view->request = $error->request; $this->view->request = $error->request;
$this->view->hideControls = ! $isAuthenticated;
} }
} }

View File

@ -10,6 +10,7 @@ use Icinga\Protocol\File\FileReader;
use Icinga\Web\Controller; use Icinga\Web\Controller;
use Icinga\Web\Url; use Icinga\Web\Url;
use Icinga\Web\Widget\Tabextension\DashboardAction; use Icinga\Web\Widget\Tabextension\DashboardAction;
use Icinga\Web\Widget\Tabextension\MenuAction;
use Icinga\Web\Widget\Tabextension\OutputFormat; use Icinga\Web\Widget\Tabextension\OutputFormat;
/** /**
@ -30,7 +31,7 @@ class ListController extends Controller
'list/' 'list/'
. str_replace(' ', '', $action) . str_replace(' ', '', $action)
) )
))->extend(new OutputFormat())->extend(new DashboardAction())->activate($action); ))->extend(new OutputFormat())->extend(new DashboardAction())->extend(new MenuAction())->activate($action);
} }
/** /**

View File

@ -5,13 +5,13 @@ namespace Icinga\Controllers;
use Exception; use Exception;
use Icinga\Application\Config; use Icinga\Application\Config;
use Icinga\Application\Icinga;
use Icinga\Exception\NotFoundError; use Icinga\Exception\NotFoundError;
use Icinga\Data\DataArray\ArrayDatasource; use Icinga\Data\DataArray\ArrayDatasource;
use Icinga\Forms\ConfirmRemovalForm; use Icinga\Forms\ConfirmRemovalForm;
use Icinga\Forms\Navigation\NavigationConfigForm; use Icinga\Forms\Navigation\NavigationConfigForm;
use Icinga\Web\Controller; use Icinga\Web\Controller;
use Icinga\Web\Form; use Icinga\Web\Form;
use Icinga\Web\Navigation\Navigation;
use Icinga\Web\Notification; use Icinga\Web\Notification;
use Icinga\Web\Url; use Icinga\Web\Url;
@ -21,11 +21,11 @@ use Icinga\Web\Url;
class NavigationController extends Controller class NavigationController extends Controller
{ {
/** /**
* The default item types provided by Icinga Web 2 * The global navigation item type configuration
* *
* @var array * @var array
*/ */
protected $defaultItemTypes; protected $itemTypeConfig;
/** /**
* {@inheritdoc} * {@inheritdoc}
@ -33,11 +33,19 @@ class NavigationController extends Controller
public function init() public function init()
{ {
parent::init(); parent::init();
$this->itemTypeConfig = Navigation::getItemTypeConfiguration();
}
$this->defaultItemTypes = array( /**
'menu-item' => $this->translate('Menu Entry'), * Return the label for the given navigation item type
'dashlet' => 'Dashlet' *
); * @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() protected function listItemTypes()
{ {
$moduleManager = Icinga::app()->getModuleManager(); $types = array();
foreach ($this->itemTypeConfig as $type => $options) {
$types = $this->defaultItemTypes; $types[$type] = isset($options['label']) ? $options['label'] : $type;
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);
}
}
} }
return $types; 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 * Show the current user a list of his/her navigation items
*/ */
public function indexAction() public function indexAction()
{ {
$user = $this->Auth()->getUser(); $user = $this->Auth()->getUser();
$ds = new ArrayDatasource(array_merge( $ds = new ArrayDatasource(array_merge(
Config::app('navigation')->select()->where('owner', $user->getUsername())->fetchAll(), $this->fetchSharedNavigationItemConfigs($user->getUsername()),
iterator_to_array($user->loadNavigationConfig()) $this->fetchUserNavigationItemConfigs($user->getUsername())
)); ));
$ds->setKeyColumn('name');
$query = $ds->select(); $query = $ds->select();
$this->view->types = $this->listItemTypes(); $this->view->types = $this->listItemTypes();
@ -91,7 +137,7 @@ class NavigationController extends Controller
array( array(
'type' => $this->translate('Type'), 'type' => $this->translate('Type'),
'owner' => $this->translate('Shared'), 'owner' => $this->translate('Shared'),
'name' => $this->translate('Shared Navigation') 'name' => $this->translate('Navigation')
), ),
$query $query
); );
@ -103,13 +149,11 @@ class NavigationController extends Controller
public function sharedAction() public function sharedAction()
{ {
$this->assertPermission('config/application/navigation'); $this->assertPermission('config/application/navigation');
$config = Config::app('navigation'); $ds = new ArrayDatasource($this->fetchSharedNavigationItemConfigs());
$config->getConfigObject()->setKeyColumn('name'); $query = $ds->select();
$query = $config->select();
$removeForm = new Form(); $removeForm = new Form();
$removeForm->setUidDisabled(); $removeForm->setUidDisabled();
$removeForm->setAction(Url::fromPath('navigation/unshare'));
$removeForm->addElement('hidden', 'name', array( $removeForm->addElement('hidden', 'name', array(
'decorators' => array('ViewHelper') 'decorators' => array('ViewHelper')
)); ));
@ -156,11 +200,14 @@ class NavigationController extends Controller
{ {
$form = new NavigationConfigForm(); $form = new NavigationConfigForm();
$form->setRedirectUrl('navigation'); $form->setRedirectUrl('navigation');
$form->setUser($this->Auth()->getUser());
$form->setItemTypes($this->listItemTypes()); $form->setItemTypes($this->listItemTypes());
$form->setTitle($this->translate('Create New Navigation Item')); $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->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) { $form->setOnSuccess(function (NavigationConfigForm $form) {
$data = array_filter($form->getValues()); $data = array_filter($form->getValues());
@ -172,7 +219,7 @@ class NavigationController extends Controller
} }
if ($form->save()) { if ($form->save()) {
if (isset($data['type']) && $data['type'] === 'menu-item') { if ($data['type'] === 'menu-item') {
$form->getResponse()->setRerenderLayout(); $form->getResponse()->setRerenderLayout();
} }
@ -194,14 +241,22 @@ class NavigationController extends Controller
public function editAction() public function editAction()
{ {
$itemName = $this->params->getRequired('name'); $itemName = $this->params->getRequired('name');
$itemType = $this->params->getRequired('type');
$referrer = $this->params->get('referrer', 'index'); $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 = new NavigationConfigForm();
$form->setUser($user);
$form->setShareConfig(Config::navigation($itemType));
$form->setUserConfig(Config::navigation($itemType, $itemOwner));
$form->setRedirectUrl($referrer === 'shared' ? 'navigation/shared' : 'navigation'); $form->setRedirectUrl($referrer === 'shared' ? 'navigation/shared' : 'navigation');
$form->setItemTypes($this->listItemTypes()); $form->setTitle(sprintf($this->translate('Edit %s %s'), $this->getItemLabel($itemType), $itemName));
$form->setTitle(sprintf($this->translate('Edit Navigation Item %s'), $itemName));
$form->setUser($this->Auth()->getUser());
$form->setShareConfig(Config::app('navigation'));
$form->setOnSuccess(function (NavigationConfigForm $form) use ($itemName) { $form->setOnSuccess(function (NavigationConfigForm $form) use ($itemName) {
$data = array_map( $data = array_map(
function ($v) { function ($v) {
@ -248,13 +303,17 @@ class NavigationController extends Controller
public function removeAction() public function removeAction()
{ {
$itemName = $this->params->getRequired('name'); $itemName = $this->params->getRequired('name');
$itemType = $this->params->getRequired('type');
$user = $this->Auth()->getUser();
$navigationConfigForm = new NavigationConfigForm(); $navigationConfigForm = new NavigationConfigForm();
$navigationConfigForm->setUser($this->Auth()->getUser()); $navigationConfigForm->setUser($user);
$navigationConfigForm->setShareConfig(Config::app('navigation')); $navigationConfigForm->setShareConfig(Config::navigation($itemType));
$navigationConfigForm->setUserConfig(Config::navigation($itemType, $user->getUsername()));
$form = new ConfirmRemovalForm(); $form = new ConfirmRemovalForm();
$form->setRedirectUrl('navigation'); $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) { $form->setOnSuccess(function (ConfirmRemovalForm $form) use ($itemName, $navigationConfigForm) {
try { try {
$itemConfig = $navigationConfigForm->delete($itemName); $itemConfig = $navigationConfigForm->delete($itemName);
@ -291,9 +350,14 @@ class NavigationController extends Controller
$this->assertPermission('config/application/navigation'); $this->assertPermission('config/application/navigation');
$this->assertHttpMethod('POST'); $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 = new NavigationConfigForm();
$navigationConfigForm->setUser($this->Auth()->getUser()); $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( $form = new Form(array(
'onSuccess' => function ($form) use ($navigationConfigForm) { 'onSuccess' => function ($form) use ($navigationConfigForm) {

View File

@ -3,8 +3,8 @@
namespace Icinga\Forms\Config\Resource; namespace Icinga\Forms\Config\Resource;
use Icinga\Web\Form;
use Icinga\Application\Platform; use Icinga\Application\Platform;
use Icinga\Web\Form;
/** /**
* Form class for adding/modifying database resources * Form class for adding/modifying database resources
@ -43,12 +43,30 @@ class DbResourceForm extends Form
$dbChoices['oci'] = 'Oracle (OCI8)'; $dbChoices['oci'] = 'Oracle (OCI8)';
} }
$offerPostgres = false; $offerPostgres = false;
$offerMysql = false;
if (isset($formData['db'])) { if (isset($formData['db'])) {
if ($formData['db'] === 'pgsql') { if ($formData['db'] === 'pgsql') {
$offerPostgres = true; $offerPostgres = true;
} elseif ($formData['db'] === 'mysql') {
$offerMysql = true;
} }
} elseif (key($dbChoices) === 'pgsql') { } else {
$offerPostgres = true; $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( $this->addElement(
'text', 'text',
@ -76,7 +94,8 @@ class DbResourceForm extends Form
array ( array (
'required' => true, 'required' => true,
'label' => $this->translate('Host'), 'label' => $this->translate('Host'),
'description' => $this->translate('The hostname of the database'), 'description' => $this->translate('The hostname of the database')
. ($socketInfo ? '. ' . $socketInfo : ''),
'value' => 'localhost' 'value' => 'localhost'
) )
); );
@ -119,6 +138,14 @@ class DbResourceForm extends Form
'description' => $this->translate('The password to use for authentication') '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( $this->addElement(
'checkbox', 'checkbox',
'persistent', 'persistent',

View File

@ -123,12 +123,18 @@ class NavigationConfigForm extends ConfigForm
/** /**
* Return the user's navigation configuration * Return the user's navigation configuration
* *
* @param string $type
*
* @return Config * @return Config
*/ */
public function getUserConfig() public function getUserConfig($type = null)
{ {
if ($this->userConfig === 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; return $this->userConfig;
@ -151,10 +157,20 @@ class NavigationConfigForm extends ConfigForm
/** /**
* Return the shared navigation configuration * Return the shared navigation configuration
* *
* @param string $type
*
* @return Config * @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; return $this->shareConfig;
} }
@ -194,10 +210,9 @@ class NavigationConfigForm extends ConfigForm
$children = $this->itemToLoad ? $this->getFlattenedChildren($this->itemToLoad) : array(); $children = $this->itemToLoad ? $this->getFlattenedChildren($this->itemToLoad) : array();
$names = array(); $names = array();
foreach ($this->getShareConfig() as $sectionName => $sectionConfig) { foreach ($this->getShareConfig($type) as $sectionName => $sectionConfig) {
if ( if (
$sectionName !== $this->itemToLoad $sectionName !== $this->itemToLoad
&& $sectionConfig->type === $type
&& $sectionConfig->owner === ($owner ?: $this->getUser()->getUsername()) && $sectionConfig->owner === ($owner ?: $this->getUser()->getUsername())
&& !in_array($sectionName, $children, true) && !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 ( if (
$sectionName !== $this->itemToLoad $sectionName !== $this->itemToLoad
&& $sectionConfig->type === $type
&& !in_array($sectionName, $children, true) && !in_array($sectionName, $children, true)
) { ) {
$names[] = $sectionName; $names[] = $sectionName;
@ -271,29 +285,31 @@ class NavigationConfigForm extends ConfigForm
* *
* @return $this * @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 * @throws IcingaException In case a navigation item with the same name already exists
*/ */
public function add(array $data) public function add(array $data)
{ {
if (! isset($data['name'])) { if (! isset($data['name'])) {
throw new InvalidArgumentException('Key \'name\' missing'); throw new InvalidArgumentException('Key \'name\' missing');
} elseif (! isset($data['type'])) {
throw new InvalidArgumentException('Key \'type\' missing');
} }
$shared = false; $shared = false;
$config = $this->getUserConfig(); $config = $this->getUserConfig($data['type']);
if ((isset($data['users']) && $data['users']) || (isset($data['groups']) && $data['groups'])) { if ((isset($data['users']) && $data['users']) || (isset($data['groups']) && $data['groups'])) {
if ($this->getUser()->can('application/share/navigation')) { if ($this->getUser()->can('application/share/navigation')) {
$data['owner'] = $this->getUser()->getUsername(); $data['owner'] = $this->getUser()->getUsername();
$config = $this->getShareConfig(); $config = $this->getShareConfig($data['type']);
$shared = true; $shared = true;
} else { } else {
unset($data['users']); unset($data['users']);
unset($data['groups']); 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(); $data['owner'] = $this->getUser()->getUsername();
$config = $this->getShareConfig(); $config = $this->getShareConfig($data['type']);
$shared = true; $shared = true;
} }
@ -301,9 +317,9 @@ class NavigationConfigForm extends ConfigForm
$exists = $config->hasSection($itemName); $exists = $config->hasSection($itemName);
if (! $exists) { if (! $exists) {
if ($shared) { if ($shared) {
$exists = $this->getUserConfig()->hasSection($itemName); $exists = $this->getUserConfig($data['type'])->hasSection($itemName);
} else { } else {
$exists = (bool) $this->getShareConfig() $exists = (bool) $this->getShareConfig($data['type'])
->select() ->select()
->where('name', $itemName) ->where('name', $itemName)
->where('owner', $this->getUser()->getUsername()) ->where('owner', $this->getUser()->getUsername())
@ -385,8 +401,7 @@ class NavigationConfigForm extends ConfigForm
if ($ownerName === $this->getUser()->getUsername()) { if ($ownerName === $this->getUser()->getUsername()) {
$exists = $this->getUserConfig()->hasSection($name); $exists = $this->getUserConfig()->hasSection($name);
} else { } else {
$owner = new User($ownerName); $exists = Config::navigation($itemConfig->type, $ownerName)->hasSection($name);
$exists = $owner->loadNavigationConfig()->hasSection($name);
} }
} else { } else {
$exists = (bool) $this->getShareConfig() $exists = (bool) $this->getShareConfig()
@ -521,8 +536,7 @@ class NavigationConfigForm extends ConfigForm
if (! $itemConfig->owner || $itemConfig->owner === $this->getUser()->getUsername()) { if (! $itemConfig->owner || $itemConfig->owner === $this->getUser()->getUsername()) {
$config = $this->getUserConfig(); $config = $this->getUserConfig();
} else { } else {
$owner = new User($itemConfig->owner); $config = Config::navigation($itemConfig->type, $itemConfig->owner);
$config = $owner->loadNavigationConfig();
} }
foreach ($children as $child) { foreach ($children as $child) {
@ -549,6 +563,13 @@ class NavigationConfigForm extends ConfigForm
$shared = false; $shared = false;
$itemTypes = $this->getItemTypes(); $itemTypes = $this->getItemTypes();
$itemType = isset($formData['type']) ? $formData['type'] : key($itemTypes); $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); $itemForm = $this->getItemForm($itemType);
$this->addElement( $this->addElement(
@ -606,17 +627,27 @@ class NavigationConfigForm extends ConfigForm
} }
} }
$this->addElement( if (empty($itemTypes) || count($itemTypes) === 1) {
'select', $this->addElement(
'type', 'hidden',
array( 'type',
'required' => true, array(
'autosubmit' => true, 'value' => $itemType
'label' => $this->translate('Type'), )
'description' => $this->translate('The type of this navigation item'), );
'multiOptions' => $itemTypes } 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 (! $shared && $itemForm->requiresParentSelection()) {
if ($this->itemToLoad && $this->hasBeenShared($this->itemToLoad)) { if ($this->itemToLoad && $this->hasBeenShared($this->itemToLoad)) {
@ -767,12 +798,13 @@ class NavigationConfigForm extends ConfigForm
* Return whether the given navigation item has been shared * Return whether the given navigation item has been shared
* *
* @param string $name * @param string $name
* @param string $type
* *
* @return bool * @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);
} }
/** /**

View File

@ -4,6 +4,7 @@
namespace Icinga\Forms\Navigation; namespace Icinga\Forms\Navigation;
use Icinga\Web\Form; use Icinga\Web\Form;
use Icinga\Web\Url;
class NavigationItemForm extends Form 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;
}
} }

View File

@ -0,0 +1,8 @@
<div class="logo">
<div class="image">
<img aria-hidden="true" src="<?= $this->baseUrl('img/logo_icinga_big.png'); ?>" alt="<?= $this->translate('The Icinga logo'); ?>" >
</div>
</div>
<div class="below-logo">
<?= $this->render('inline.phtml') ?>
</div>

View File

@ -1,25 +1,128 @@
<div class="content"> <div class="controls">
<h1>Icinga Web 2</h1> <?= $tabs; ?>
<?php </div>
$versionInfo = array(); <div class="content about">
if ($version !== false) { <?= $this->img(
foreach (array( 'img/logo_icinga_big_dark.png',
'appVersion' => $this->translate('Version: %s'), null,
'gitCommitID' => $this->translate('Git commit ID: %s'), array(
'gitCommitDate' => $this->translate('Git commit date: %s') 'width' => 400,
) as $key => $label) { 'class' => 'about-logo'
if (array_key_exists($key, $version) && null !== ($value = $version[$key])) { )
$versionInfo[] = sprintf($label, htmlspecialchars($value)); ); ?>
} <p class="about-version">
} <?php if (isset($version['appVersion'])): ?>
} <strong><?= $this->translate('Version'); ?>:</strong> <?= $this->escape($version['appVersion']); ?>
<?php endif ?>
echo ( <?php if (isset($version['gitCommitID'])): ?>
0 === count($versionInfo) <br>
? '<p class="message-error">' . $this->translate( <strong><?= $this->translate('Git commit ID'); ?>:</strong> <?= $this->escape($version['gitCommitID']); ?>
'Can\'t determine Icinga Web 2\'s version' <?php endif ?>
) <?php if (isset($version['gitCommitDate'])): ?>
: '<p>' . nl2br(implode("\n", $versionInfo), false) <br>
) . '</p>'; <strong><?= $this->translate('Git commit date'); ?>:</strong> <?= $this->escape($version['gitCommitDate']); ?>
?> <?php endif ?>
</p>
<p class="about-license">
<strong><?= $this->translate('Copyright'); ?></strong>: &copy; 2013-<?= date('Y'); ?> <?= $this->qlink(
$this->translate('The Icinga Project'),
'https://www.icinga.org',
null,
array(
'target' => '_blank'
)
); ?>
<br>
<strong><?= $this->translate('License'); ?></strong>: GNU GPL v2+
</p>
<p class="about-social">
<?= $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')
)
); ?>
</p>
<div class="about-urls">
<div><?= $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')
)
); ?></div>
<div><?= $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')
)
); ?></div>
</div>
<h2><?= $this->translate('Loaded modules') ?></h2>
<table class="action alternating about-modules" data-base-target="_next">
<thead>
<tr>
<th><?= $this->translate('Name') ?></th>
<th><?= $this->translate('Version') ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($modules as $module): ?>
<tr>
<td>
<?php if ($this->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()))
); ?>
<?php else: ?>
<?= $this->escape($module->getName()); ?>
<?php endif ?>
<td>
<?= $this->escape($module->getVersion()); ?>
</td>
</tr>
<?php endforeach ?>
</tbody>
</table>
</div> </div>

View File

@ -1,10 +1,12 @@
<?php if (! $hideControls): ?>
<div class="controls"> <div class="controls">
<?= $this->tabs->showOnlyCloseButton() ?> <?= $tabs->showOnlyCloseButton(); ?>
</div> </div>
<div class="content">
<p><strong><?= nl2br($this->escape($message)) ?></strong></p>
<?php if (isset($stackTrace)) : ?>
<hr />
<pre><?= $this->escape($stackTrace) ?></pre>
<?php endif ?> <?php endif ?>
</div> <div class="content">
<p><strong><?= nl2br($this->escape($message)); ?></strong></p>
<?php if (isset($stackTrace)): ?>
<hr />
<pre><?= $this->escape($stackTrace) ?></pre>
<?php endif ?>
</div>

View File

@ -2,6 +2,7 @@
use Icinga\Data\Extensible; use Icinga\Data\Extensible;
use Icinga\Data\Updatable; use Icinga\Data\Updatable;
use Icinga\Data\Selectable;
$extensible = $this->hasPermission('config/authentication/groups/add') && $backend instanceof Extensible; $extensible = $this->hasPermission('config/authentication/groups/add') && $backend instanceof Extensible;
@ -67,7 +68,22 @@ foreach ($members as $member): ?>
<tbody> <tbody>
<?php endif ?> <?php endif ?>
<tr> <tr>
<td class="member-name"><?= $this->escape($member->user_name); ?></td> <td class="member-name">
<?php if (
$this->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)
)); ?>
<?php else: ?>
<?= $this->escape($member->user_name); ?>
<?php endif ?>
</td>
<?php if (isset($removeForm)): ?> <?php if (isset($removeForm)): ?>
<td class="member-remove" data-base-target="_self"> <td class="member-remove" data-base-target="_self">
<?php $removeForm->getElement('user_name')->setValue($member->user_name); echo $removeForm; ?> <?php $removeForm->getElement('user_name')->setValue($member->user_name); echo $removeForm; ?>

View File

@ -22,14 +22,17 @@
<th style="width: 5em"><?= $this->translate('Remove'); ?></th> <th style="width: 5em"><?= $this->translate('Remove'); ?></th>
</thead> </thead>
<tbody> <tbody>
<?php foreach ($items as $name => $item): ?> <?php foreach ($items as $item): ?>
<tr> <tr>
<td><?= $this->qlink( <td><?= $this->qlink(
$name, $item->name,
'navigation/edit', 'navigation/edit',
array('name' => $name),
array( 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)
) )
); ?></td> ); ?></td>
<td><?= $item->type && isset($types[$item->type]) <td><?= $item->type && isset($types[$item->type])
@ -39,10 +42,13 @@
<td><?= $this->qlink( <td><?= $this->qlink(
'', '',
'navigation/remove', 'navigation/remove',
array('name' => $name), array(
'name' => $item->name,
'type' => $item->type
),
array( array(
'icon' => 'trash', 'icon' => 'trash',
'title' => sprintf($this->translate('Remove navigation item %s'), $name) 'title' => sprintf($this->translate('Remove navigation item %s'), $item->name)
) )
); ?></td> ); ?></td>
</tr> </tr>

View File

@ -1,4 +1,8 @@
<?php if (! $this->compact): ?> <?php
use Icinga\Web\Url;
if (! $this->compact): ?>
<div class="controls"> <div class="controls">
<?= $this->tabs; ?> <?= $this->tabs; ?>
<?= $this->sortBox; ?> <?= $this->sortBox; ?>
@ -19,17 +23,19 @@
<th style="width: 5em"><?= $this->translate('Unshare'); ?></th> <th style="width: 5em"><?= $this->translate('Unshare'); ?></th>
</thead> </thead>
<tbody> <tbody>
<?php foreach ($items as $name => $item): ?> <?php foreach ($items as $item): ?>
<tr> <tr>
<td><?= $this->qlink( <td><?= $this->qlink(
$name, $item->name,
'navigation/edit', 'navigation/edit',
array( array(
'name' => $name, 'name' => $item->name,
'type' => $item->type,
'owner' => $item->owner,
'referrer' => 'shared' 'referrer' => 'shared'
), ),
array( array(
'title' => sprintf($this->translate('Edit shared navigation item %s'), $name) 'title' => sprintf($this->translate('Edit shared navigation item %s'), $item->name)
) )
); ?></td> ); ?></td>
<td><?= $item->type && isset($types[$item->type]) <td><?= $item->type && isset($types[$item->type])
@ -48,7 +54,12 @@
) )
); ?></td> ); ?></td>
<?php else: ?> <?php else: ?>
<td data-base-target="_self"><?= $removeForm->setDefault('name', $name); ?></td> <td data-base-target="_self"><?= $removeForm
->setDefault('name', $item->name)
->setAction(Url::fromPath(
'navigation/unshare',
array('type' => $item->type, 'owner' => $item->owner)
)); ?></td>
<?php endif ?> <?php endif ?>
</tr> </tr>
<?php endforeach ?> <?php endforeach ?>

View File

@ -19,21 +19,38 @@ to handle authentication and authorization, monitoring data or user preferences.
Directive | Description Directive | Description
----------------|------------ ----------------|------------
**type** | `db` **type** | `db`
**db** | Database management system. Either `mysql` or `pgsql`. **db** | Database management system. In most cases `mysql` or `pgsql`.
**host** | Connect to the database server on the given host. **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 for the connection. **port** | Port number to use. Mandatory for connections to a PostgreSQL database.
**username** | The username to use when connecting to the server. **username** | The username to use when connecting to the server.
**password** | The password to use when connecting to the server. **password** | The password to use when connecting to the server.
**dbname** | The database to use. **dbname** | The database to use.
**Example:** **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 type = db
db = mysql db = mysql
host = localhost 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 username = icingaweb
password = icingaweb password = icingaweb
dbname = icingaweb dbname = icingaweb

View File

@ -13,7 +13,9 @@ use Icinga\Data\Selectable;
use Icinga\Data\SimpleQuery; use Icinga\Data\SimpleQuery;
use Icinga\File\Ini\IniWriter; use Icinga\File\Ini\IniWriter;
use Icinga\File\Ini\IniParser; use Icinga\File\Ini\IniParser;
use Icinga\Exception\IcingaException;
use Icinga\Exception\NotReadableError; use Icinga\Exception\NotReadableError;
use Icinga\Web\Navigation\Navigation;
/** /**
* Container for INI like configuration and global registry of application and module related configuration. * 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(); protected static $modules = array();
/**
* Navigation config instances per type
*
* @var array
*/
protected static $navigation = array();
/** /**
* The internal ConfigObject * The internal ConfigObject
* *
@ -416,6 +425,60 @@ class Config implements Countable, Iterator, Selectable
return $moduleConfigs[$configname]; 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 * Return this config rendered as a INI structured string
* *

View File

@ -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 $type
* @param string $label * @param string $label
* @param string $config
* *
* @return $this * @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; return $this;
} }

View File

@ -8,33 +8,41 @@ namespace Icinga\Application;
*/ */
class Version class Version
{ {
const VERSION = '2.0.0-rc1';
/** /**
* Get the version of this instance of Icinga Web 2 * Get the version of this instance of Icinga Web 2
* *
* @return array|false array on success, false otherwise * @return array
*/ */
public static function get() public static function get()
{ {
if (false === ($appVersion = @file_get_contents( $version = array('appVersion' => self::VERSION);
Icinga::app()->getApplicationDir() . DIRECTORY_SEPARATOR . 'VERSION' if (false !== ($appVersion = @file_get_contents(Icinga::app()->getApplicationDir('VERSION')))) {
))) { $matches = array();
return false; if (@preg_match('/^(?P<gitCommitID>\w+) (?P<gitCommitDate>\S+)/', $appVersion, $matches)) {
} return array_merge($version, $matches);
$matches = array();
if (false === ($res = preg_match(
'/(?<!.)\s*(?P<gitCommitID>\w+)(?:\s*\(.*?(?:(?<=[\(,])\s*tag\s*:\s*v(?P<appVersion>.+?)\s*(?=[\),]).*?)?\))?\s*(?P<gitCommitDate>\S+)/ms',
$appVersion,
$matches
)) || $res === 0) {
return false;
}
foreach ($matches as $key => $value) {
if (is_int($key) || $value === '') {
unset($matches[$key]);
} }
} }
return $matches;
$gitDir = Icinga::app()->getBaseDir('.git');
$gitHead = @file_get_contents($gitDir . DIRECTORY_SEPARATOR . 'HEAD');
if (false !== $gitHead) {
$matches = array();
if (@preg_match('/(?<!.)ref:\s+(.+?)$/ms', $gitHead, $matches)) {
$gitCommitID = @file_get_contents($gitDir . DIRECTORY_SEPARATOR . $matches[1]);
} else {
$gitCommitID = $gitHead;
}
if (false !== $gitCommitID) {
$matches = array();
if (@preg_match('/(?<!.)(?P<gitCommitID>[0-9a-f]+)$/ms', $gitCommitID, $matches)) {
return array_merge($version, $matches);
}
}
}
return $version;
} }
} }

View File

@ -179,12 +179,11 @@ class Web extends EmbeddedWeb
*/ */
public function getSharedNavigation($type) public function getSharedNavigation($type)
{ {
$config = Config::app('navigation')->getConfigObject(); $config = Config::navigation($type === 'dashboard-pane' ? 'dashlet' : $type);
$config->setKeyColumn('name');
if ($type === 'dashboard-pane') { if ($type === 'dashboard-pane') {
$panes = array(); $panes = array();
foreach ($config->select()->where('type', 'dashlet') as $dashletName => $dashletConfig) { foreach ($config as $dashletName => $dashletConfig) {
if ($this->hasAccessToSharedNavigationItem($dashletConfig)) { if ($this->hasAccessToSharedNavigationItem($dashletConfig)) {
// TODO: Throw ConfigurationError if pane or url is missing // TODO: Throw ConfigurationError if pane or url is missing
$panes[$dashletConfig->pane][$dashletName] = $dashletConfig->url; $panes[$dashletConfig->pane][$dashletName] = $dashletConfig->url;
@ -203,7 +202,7 @@ class Web extends EmbeddedWeb
} }
} else { } else {
$items = array(); $items = array();
foreach ($config->select()->where('type', $type) as $name => $typeConfig) { foreach ($config as $name => $typeConfig) {
if ($this->hasAccessToSharedNavigationItem($typeConfig)) { if ($this->hasAccessToSharedNavigationItem($typeConfig)) {
$items[$name] = $typeConfig; $items[$name] = $typeConfig;
} }

View File

@ -12,10 +12,16 @@ use Icinga\Protocol\Ldap\Expression;
use Icinga\Repository\LdapRepository; use Icinga\Repository\LdapRepository;
use Icinga\Repository\RepositoryQuery; use Icinga\Repository\RepositoryQuery;
use Icinga\User; 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 * 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 * @param LdapUserBackend $backend
*/
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
* *
* @return $this * @return $this
*/ */
public function setName($name) public function setUserBackend(LdapUserBackend $backend)
{ {
$this->name = $name; $this->userBackend = $backend;
return $this; 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 LdapUserBackend
*
* @return string
*/ */
public function getName() public function getUserBackend()
{ {
return $this->name; return $this->userBackend;
} }
/** /**
@ -453,7 +401,6 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken
$lastModifiedAttribute = 'modifyTimestamp'; $lastModifiedAttribute = 'modifyTimestamp';
} }
// TODO(jom): Fetching memberships does not work currently, we'll need some aggregate functionality!
$columns = array( $columns = array(
'group' => $this->groupNameAttribute, 'group' => $this->groupNameAttribute,
'group_name' => $this->groupNameAttribute, 'group_name' => $this->groupNameAttribute,
@ -492,13 +439,37 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken
if ($this->groupClass === null) { if ($this->groupClass === null) {
throw new ProgrammingError('It is required to set the objectClass where to look for groups first'); 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( $this->groupClass => array(
'created_at' => 'generalized_time', 'created_at' => 'generalized_time',
'last_modified' => '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; 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 * 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) public function getMemberships(User $user)
{ {
if ($this->groupClass === 'posixGroup') { if ($this->isAmbiguous($this->groupClass, $this->groupMemberAttribute)) {
// Posix group only uses simple user name $queryValue = $user->getUsername();
$userDn = $user->getUsername(); } elseif (($queryValue = $user->getAdditional('ldap_dn')) === null) {
} else { $userQuery = $this->ds
// LDAP groups use the complete DN ->select()
if (($userDn = $user->getAdditional('ldap_dn')) === null) { ->from($this->userClass)
$userQuery = $this->ds ->where($this->userNameAttribute, $user->getUsername())
->select() ->setBase($this->userBaseDn)
->from($this->userClass) ->setUsePagedResults(false);
->where($this->userNameAttribute, $user->getUsername()) if ($this->userFilter) {
->setBase($this->userBaseDn) $userQuery->where(new Expression($this->userFilter));
->setUsePagedResults(false); }
if ($this->userFilter) {
$userQuery->where(new Expression($this->userFilter));
}
if (($userDn = $userQuery->fetchDn()) === null) { if (($queryValue = $userQuery->fetchDn()) === null) {
return array(); return array();
}
} }
} }
$groupQuery = $this->ds $groupQuery = $this->ds
->select() ->select()
->from($this->groupClass, array($this->groupNameAttribute)) ->from($this->groupClass, array($this->groupNameAttribute))
->where($this->groupMemberAttribute, $userDn) ->where($this->groupMemberAttribute, $queryValue)
->setBase($this->groupBaseDn); ->setBase($this->groupBaseDn);
if ($this->groupFilter) { if ($this->groupFilter) {
$groupQuery->where(new Expression($this->groupFilter)); $groupQuery->where(new Expression($this->groupFilter));
} }
Logger::debug('Fetching groups for user %s using filter %s.', $user->getUsername(), $groupQuery->__toString());
$groups = array(); $groups = array();
foreach ($groupQuery as $row) { foreach ($groupQuery as $row) {
$groups[] = $row->{$this->groupNameAttribute}; $groups[] = $row->{$this->groupNameAttribute};
} }
Logger::debug('Fetched %d groups: %s.', count($groups), join(', ', $groups));
return $groups; return $groups;
} }
@ -610,6 +596,7 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken
); );
} }
$this->setUserBackend($userBackend);
$defaults->merge(array( $defaults->merge(array(
'user_base_dn' => $userBackend->getBaseDn(), 'user_base_dn' => $userBackend->getBaseDn(),
'user_class' => $userBackend->getUserClass(), 'user_class' => $userBackend->getUserClass(),
@ -661,4 +648,4 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken
'group_member_attribute' => 'member' 'group_member_attribute' => 'member'
)); ));
} }
} }

View File

@ -80,7 +80,7 @@ class ArrayDatasource implements Selectable
*/ */
public function select() public function select()
{ {
return new SimpleQuery($this); return new SimpleQuery(clone $this);
} }
/** /**

View File

@ -358,9 +358,25 @@ class LdapConnection implements Selectable, Inspectable
*/ */
public function count(LdapQuery $query) public function count(LdapQuery $query)
{ {
$ds = $this->getConnection();
$this->bind(); $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( $results = @ldap_search(
$ds, $ds,
$query->getBase() ?: $this->getDn(), $query->getBase() ?: $this->getDn(),
@ -658,7 +674,7 @@ class LdapConnection implements Selectable, Inspectable
protected function runQuery(LdapQuery $query, array $fields = null) protected function runQuery(LdapQuery $query, array $fields = null)
{ {
$limit = $query->getLimit(); $limit = $query->getLimit();
$offset = $query->hasOffset() ? $query->getOffset() - 1 : 0; $offset = $query->hasOffset() ? $query->getOffset() : 0;
if ($fields === null) { if ($fields === null) {
$fields = $query->getColumns(); $fields = $query->getColumns();
@ -711,13 +727,41 @@ class LdapConnection implements Selectable, Inspectable
$count = 0; $count = 0;
$entries = array(); $entries = array();
$entry = ldap_first_entry($ds, $results); $entry = ldap_first_entry($ds, $results);
$unfoldAttribute = $query->getUnfoldAttribute();
do { do {
$count += 1; if ($unfoldAttribute) {
if (! $serverSorting || $offset === 0 || $offset < $count) { $rows = $this->cleanupAttributes(
$entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(
ldap_get_attributes($ds, $entry), 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)) } while ((! $serverSorting || $limit === 0 || $limit !== count($entries))
&& ($entry = ldap_next_entry($ds, $entry)) && ($entry = ldap_next_entry($ds, $entry))
@ -754,7 +798,7 @@ class LdapConnection implements Selectable, Inspectable
} }
$limit = $query->getLimit(); $limit = $query->getLimit();
$offset = $query->hasOffset() ? $query->getOffset() - 1 : 0; $offset = $query->hasOffset() ? $query->getOffset() : 0;
$queryString = (string) $query; $queryString = (string) $query;
$base = $query->getBase() ?: $this->rootDn; $base = $query->getBase() ?: $this->rootDn;
@ -776,6 +820,7 @@ class LdapConnection implements Selectable, Inspectable
$count = 0; $count = 0;
$cookie = ''; $cookie = '';
$entries = array(); $entries = array();
$unfoldAttribute = $query->getUnfoldAttribute();
do { do {
// Do not request the pagination control as a critical extension, as we want the // 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 // 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); $entry = ldap_first_entry($ds, $results);
do { do {
$count += 1; if ($unfoldAttribute) {
if (! $serverSorting || $offset === 0 || $offset < $count) { $rows = $this->cleanupAttributes(
$entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(
ldap_get_attributes($ds, $entry), 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 ( } while (
(! $serverSorting || $limit === 0 || $limit !== count($entries)) (! $serverSorting || $limit === 0 || $limit !== count($entries))
@ -861,9 +933,6 @@ class LdapConnection implements Selectable, Inspectable
// the server: https://www.ietf.org/rfc/rfc2696.txt // the server: https://www.ietf.org/rfc/rfc2696.txt
ldap_control_paged_result($ds, 0, false, $cookie); ldap_control_paged_result($ds, 0, false, $cookie);
ldap_search($ds, $base, $queryString); // Returns no entries, due to the page size 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()) { if (! $serverSorting && $query->hasOrder()) {
@ -879,14 +948,16 @@ class LdapConnection implements Selectable, Inspectable
/** /**
* Clean up the given attributes and return them as simple object * 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 $attributes
* @param array $requestedFields * @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 // 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. // 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; return (object) $cleanedAttributes;
} }

View File

@ -35,6 +35,13 @@ class LdapQuery extends SimpleQuery
*/ */
protected $usePagedResults; protected $usePagedResults;
/**
* The name of the attribute used to unfold the result
*
* @var string
*/
protected $unfoldAttribute;
/** /**
* Initialize this query * Initialize this query
*/ */
@ -90,6 +97,29 @@ class LdapQuery extends SimpleQuery
return $this->usePagedResults; 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 * Choose an objectClass and the columns you are interested in
* *

View File

@ -28,13 +28,27 @@ abstract class LdapRepository extends Repository
* @var array * @var array
*/ */
protected $normedAttributes = array( protected $normedAttributes = array(
'uid' => 'uid', 'uid' => 'uid',
'gid' => 'gid', 'gid' => 'gid',
'user' => 'user', 'user' => 'user',
'group' => 'group', 'group' => 'group',
'member' => 'member', 'member' => 'member',
'inetorgperson' => 'inetOrgPerson', 'memberuid' => 'memberUid',
'samaccountname' => 'sAMAccountName' '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; return $name;
} }
}
/**
* 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]);
}
}

View File

@ -479,22 +479,6 @@ class User
return false; 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 * Load and return this user's configured navigation of the given type
* *
@ -504,12 +488,11 @@ class User
*/ */
public function getNavigation($type) public function getNavigation($type)
{ {
$config = $this->loadNavigationConfig(); $config = Config::navigation($type === 'dashboard-pane' ? 'dashlet' : $type, $this->getUsername());
$config->getConfigObject()->setKeyColumn('name');
if ($type === 'dashboard-pane') { if ($type === 'dashboard-pane') {
$panes = array(); $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 // TODO: Throw ConfigurationError if pane or url is missing
$panes[$dashletConfig->pane][$dashletName] = $dashletConfig->url; $panes[$dashletConfig->pane][$dashletName] = $dashletConfig->url;
} }
@ -525,7 +508,7 @@ class User
); );
} }
} else { } else {
$navigation = Navigation::fromConfig($config->select()->where('type', $type)); $navigation = Navigation::fromConfig($config);
} }
return $navigation; return $navigation;

View File

@ -1,5 +1,6 @@
<?php <?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */ /* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Web; namespace Icinga\Web;
class FileCache class FileCache
@ -191,7 +192,7 @@ class FileCache
* Whether the given ETag matchesspecific file(s) on disk * Whether the given ETag matchesspecific file(s) on disk
* *
* If no ETag is given we'll try to fetch the one from the current * If no ETag is given we'll try to fetch the one from the current
* HTTP request. * HTTP request. Respects HTTP Cache-Control: no-cache, if set.
* *
* @param string|array $files file(s) to check * @param string|array $files file(s) to check
* @param string $match ETag to match against * @param string $match ETag to match against
@ -208,6 +209,9 @@ class FileCache
if (! $match) { if (! $match) {
return false; return false;
} }
if (isset($_SERVER['HTTP_CACHE_CONTROL']) && $_SERVER['HTTP_CACHE_CONTROL'] === 'no-cache') {
return false;
}
$etag = self::etagForFiles($files); $etag = self::etagForFiles($files);
return $match === $etag ? $etag : false; return $match === $etag ? $etag : false;

View File

@ -27,7 +27,8 @@ class JavaScript
'js/icinga/behavior/tristate.js', 'js/icinga/behavior/tristate.js',
'js/icinga/behavior/navigation.js', 'js/icinga/behavior/navigation.js',
'js/icinga/behavior/form.js', 'js/icinga/behavior/form.js',
'js/icinga/behavior/actiontable.js' 'js/icinga/behavior/actiontable.js',
'js/icinga/behavior/selectable.js'
); );
protected static $vendorFiles = array( protected static $vendorFiles = array(

View File

@ -429,12 +429,14 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate
*/ */
public function load($type) public function load($type)
{ {
// Shareables
$this->merge(Icinga::app()->getSharedNavigation($type));
// User Preferences
$user = Auth::getInstance()->getUser(); $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 // Modules
$moduleManager = Icinga::app()->getModuleManager(); $moduleManager = Icinga::app()->getModuleManager();
@ -451,6 +453,39 @@ class Navigation implements ArrayAccess, Countable, IteratorAggregate
return $this; 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 * Create and return a new set of navigation items for the given configuration
* *

View File

@ -36,6 +36,13 @@ class NavigationItemRenderer
*/ */
protected $internalLinkTargets; protected $internalLinkTargets;
/**
* Whether to escape the label
*
* @var bool
*/
protected $escapeLabel;
/** /**
* Create a new NavigationItemRenderer * Create a new NavigationItemRenderer
* *
@ -126,6 +133,29 @@ class NavigationItemRenderer
return $this->item; 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 * 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) { if (($icon = $item->getIcon()) !== null) {
$label = $this->view()->icon($icon) . $label; $label = $this->view()->icon($icon) . $label;
} }

View File

@ -37,7 +37,9 @@ class StyleSheet
'css/icinga/selection-toolbar.less', 'css/icinga/selection-toolbar.less',
'css/icinga/login.less', 'css/icinga/login.less',
'css/icinga/controls.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() public static function compileForPdf()

View File

@ -75,7 +75,7 @@ class Url
} }
$url = new Url(); $url = new Url();
$url->setPath($request->getPathInfo()); $url->setPath(ltrim($request->getPathInfo(), '/'));
// $urlParams = UrlParams::fromQueryString($request->getQuery()); // $urlParams = UrlParams::fromQueryString($request->getQuery());
if (isset($_SERVER['QUERY_STRING'])) { if (isset($_SERVER['QUERY_STRING'])) {
@ -159,16 +159,17 @@ class Url
if (isset($urlParts['path'])) { if (isset($urlParts['path'])) {
$urlPath = $urlParts['path']; $urlPath = $urlParts['path'];
if ($urlPath && $urlPath[0] === '/') { 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) { } elseif (! $baseUrl) {
$baseUrl = $request->getBaseUrl(); $baseUrl = $request->getBaseUrl();
} }
if ($baseUrl && !$urlObject->isExternal() && strpos($urlPath, $baseUrl) === 0) { $urlObject->setPath($urlPath);
$urlObject->setPath(substr($urlPath, strlen($baseUrl)));
} else {
$urlObject->setPath($urlPath);
}
} elseif (! $baseUrl) { } elseif (! $baseUrl) {
$baseUrl = $request->getBaseUrl(); $baseUrl = $request->getBaseUrl();
} }
@ -255,7 +256,7 @@ class Url
*/ */
public function setPath($path) public function setPath($path)
{ {
$this->path = ltrim($path, '/'); $this->path = $path;
return $this; return $this;
} }

View File

@ -43,6 +43,11 @@ $this->addHelperFunction('qlink', function ($title, $url, $params = null, $prope
$icon = $view->icon($properties['icon']); $icon = $view->icon($properties['icon']);
unset($properties['icon']); unset($properties['icon']);
} }
if (array_key_exists('img', $properties)) {
$icon = $view->img($properties['img']);
unset($properties['img']);
}
} }
return sprintf( return sprintf(

View File

@ -85,6 +85,7 @@ class Dashboard extends AbstractWidget
} }
$this->mergePanes($panes); $this->mergePanes($panes);
$this->loadUserDashboards();
return $this; return $this;
} }

View File

@ -0,0 +1,35 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Web\Widget\Tabextension;
use Icinga\Web\Url;
use Icinga\Web\Widget\Tabs;
/**
* Tabextension that allows to add the current URL as menu entry
*
* Displayed as a dropdown field in the tabs
*/
class MenuAction implements Tabextension
{
/**
* Applies the menu actions to the provided tabset
*
* @param Tabs $tabs The tabs object to extend with
*/
public function apply(Tabs $tabs)
{
$tabs->addAsDropdown(
'menu-entry',
array(
'icon' => 'menu',
'label' => t('Add To Menu'),
'url' => Url::fromPath('navigation/add'),
'urlParams' => array(
'url' => rawurlencode(Url::fromRequest()->getRelativeUrl())
)
)
);
}
}

View File

@ -16,6 +16,7 @@ use Icinga\Module\Monitoring\Controller;
use Icinga\Module\Monitoring\Web\Widget\SelectBox; use Icinga\Module\Monitoring\Web\Widget\SelectBox;
use Icinga\Web\Url; use Icinga\Web\Url;
use Icinga\Web\Widget\Tabextension\DashboardAction; use Icinga\Web\Widget\Tabextension\DashboardAction;
use Icinga\Web\Widget\Tabextension\MenuAction;
class AlertsummaryController extends Controller class AlertsummaryController extends Controller
{ {
@ -53,7 +54,7 @@ class AlertsummaryController extends Controller
'label' => $this->translate('Alert Summary'), 'label' => $this->translate('Alert Summary'),
'url' => Url::fromRequest() '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->title = $this->translate('Alert Summary');
$this->view->intervalBox = $this->createIntervalBox(); $this->view->intervalBox = $this->createIntervalBox();

View File

@ -7,6 +7,7 @@ use Icinga\Module\Monitoring\Controller;
use Icinga\Module\Monitoring\Forms\Command\Object\DeleteCommentCommandForm; use Icinga\Module\Monitoring\Forms\Command\Object\DeleteCommentCommandForm;
use Icinga\Web\Url; use Icinga\Web\Url;
use Icinga\Web\Widget\Tabextension\DashboardAction; use Icinga\Web\Widget\Tabextension\DashboardAction;
use Icinga\Web\Widget\Tabextension\MenuAction;
/** /**
* Display detailed information about a comment * Display detailed information about a comment
@ -55,7 +56,7 @@ class CommentController extends Controller
'title' => $this->translate('Display detailed information about a comment.'), 'title' => $this->translate('Display detailed information about a comment.'),
'url' =>'monitoring/comments/show' 'url' =>'monitoring/comments/show'
) )
)->activate('comment')->extend(new DashboardAction()); )->activate('comment')->extend(new DashboardAction())->extend(new MenuAction());
} }
/** /**

View File

@ -9,6 +9,7 @@ use Icinga\Module\Monitoring\Object\Host;
use Icinga\Module\Monitoring\Object\Service; use Icinga\Module\Monitoring\Object\Service;
use Icinga\Web\Url; use Icinga\Web\Url;
use Icinga\Web\Widget\Tabextension\DashboardAction; use Icinga\Web\Widget\Tabextension\DashboardAction;
use Icinga\Web\Widget\Tabextension\MenuAction;
/** /**
* Display detailed information about a downtime * Display detailed information about a downtime
@ -65,7 +66,7 @@ class DowntimeController extends Controller
'title' => $this->translate('Display detailed information about a downtime.'), 'title' => $this->translate('Display detailed information about a downtime.'),
'url' =>'monitoring/downtimes/show' 'url' =>'monitoring/downtimes/show'
) )
)->activate('downtime')->extend(new DashboardAction()); )->activate('downtime')->extend(new DashboardAction())->extend(new MenuAction());
} }
/** /**

View File

@ -7,6 +7,7 @@ use Icinga\Module\Monitoring\Controller;
use Icinga\Module\Monitoring\Forms\Command\Instance\DisableNotificationsExpireCommandForm; use Icinga\Module\Monitoring\Forms\Command\Instance\DisableNotificationsExpireCommandForm;
use Icinga\Module\Monitoring\Forms\Command\Instance\ToggleInstanceFeaturesCommandForm; use Icinga\Module\Monitoring\Forms\Command\Instance\ToggleInstanceFeaturesCommandForm;
use Icinga\Web\Widget\Tabextension\DashboardAction; 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 * 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' 'url' =>'monitoring/health/stats'
) )
) )
->extend(new DashboardAction()); ->extend(new DashboardAction())->extend(new MenuAction());
} }
/** /**

View File

@ -77,6 +77,7 @@ class HostController extends MonitoredObjectController
'host_state_type', 'host_state_type',
'host_last_state_change', 'host_last_state_change',
'host_address', 'host_address',
'host_address6',
'host_handled', 'host_handled',
'service_description', 'service_description',
'service_display_name', 'service_display_name',

View File

@ -18,6 +18,7 @@ use Icinga\Module\Monitoring\Forms\Command\Object\SendCustomNotificationCommandF
use Icinga\Module\Monitoring\Object\HostList; use Icinga\Module\Monitoring\Object\HostList;
use Icinga\Web\Url; use Icinga\Web\Url;
use Icinga\Web\Widget\Tabextension\DashboardAction; use Icinga\Web\Widget\Tabextension\DashboardAction;
use Icinga\Web\Widget\Tabextension\MenuAction;
class HostsController extends Controller class HostsController extends Controller
{ {
@ -44,7 +45,7 @@ class HostsController extends Controller
'url' => Url::fromRequest(), 'url' => Url::fromRequest(),
'icon' => 'host' 'icon' => 'host'
) )
)->extend(new DashboardAction())->activate('show'); )->extend(new DashboardAction())->extend(new MenuAction())->activate('show');
$this->view->listAllLink = Url::fromRequest()->setPath('monitoring/list/hosts'); $this->view->listAllLink = Url::fromRequest()->setPath('monitoring/list/hosts');
} }
@ -55,6 +56,7 @@ class HostsController extends Controller
'host_icon_image_alt', 'host_icon_image_alt',
'host_name', 'host_name',
'host_address', 'host_address',
'host_address6',
'host_state', 'host_state',
'host_problem', 'host_problem',
'host_handled', 'host_handled',
@ -92,6 +94,7 @@ class HostsController extends Controller
'host_icon_image_alt', 'host_icon_image_alt',
'host_name', 'host_name',
'host_address', 'host_address',
'host_address6',
'host_state', 'host_state',
'host_problem', 'host_problem',
'host_handled', 'host_handled',

View File

@ -13,6 +13,7 @@ use Icinga\Module\Monitoring\Forms\Command\Object\DeleteDowntimeCommandForm;
use Icinga\Module\Monitoring\Forms\StatehistoryForm; use Icinga\Module\Monitoring\Forms\StatehistoryForm;
use Icinga\Web\Url; use Icinga\Web\Url;
use Icinga\Web\Widget\Tabextension\DashboardAction; use Icinga\Web\Widget\Tabextension\DashboardAction;
use Icinga\Web\Widget\Tabextension\MenuAction;
use Icinga\Web\Widget\Tabextension\OutputFormat; use Icinga\Web\Widget\Tabextension\OutputFormat;
use Icinga\Web\Widget\Tabs; use Icinga\Web\Widget\Tabs;
@ -58,7 +59,6 @@ class ListController extends Controller
'host_name', 'host_name',
'host_display_name', 'host_display_name',
'host_state' => $stateColumn, 'host_state' => $stateColumn,
'host_address',
'host_acknowledged', 'host_acknowledged',
'host_output', 'host_output',
'host_attempt', 'host_attempt',
@ -66,15 +66,10 @@ class ListController extends Controller
'host_is_flapping', 'host_is_flapping',
'host_state_type', 'host_state_type',
'host_handled', 'host_handled',
'host_last_check',
'host_last_state_change' => $stateChangeColumn, 'host_last_state_change' => $stateChangeColumn,
'host_notifications_enabled', 'host_notifications_enabled',
'host_action_url',
'host_notes_url',
'host_active_checks_enabled', 'host_active_checks_enabled',
'host_passive_checks_enabled', 'host_passive_checks_enabled'
'host_current_check_attempt',
'host_max_check_attempts'
), $this->addColumns())); ), $this->addColumns()));
$this->applyRestriction('monitoring/filter/objects', $query); $this->applyRestriction('monitoring/filter/objects', $query);
$this->filterQuery($query); $this->filterQuery($query);
@ -132,10 +127,6 @@ class ListController extends Controller
'host_name', 'host_name',
'host_display_name', 'host_display_name',
'host_state', 'host_state',
'host_state_type',
'host_last_state_change',
'host_address',
'host_handled',
'service_description', 'service_description',
'service_display_name', 'service_display_name',
'service_state' => $stateColumn, 'service_state' => $stateColumn,
@ -152,14 +143,9 @@ class ListController extends Controller
'service_state_type', 'service_state_type',
'service_handled', 'service_handled',
'service_severity', 'service_severity',
'service_last_check',
'service_notifications_enabled', 'service_notifications_enabled',
'service_action_url',
'service_notes_url',
'service_active_checks_enabled', 'service_active_checks_enabled',
'service_passive_checks_enabled', 'service_passive_checks_enabled'
'current_check_attempt' => 'service_current_check_attempt',
'max_check_attempts' => 'service_max_check_attempts'
), $this->addColumns()); ), $this->addColumns());
$query = $this->backend->select()->from('servicestatus', $columns); $query = $this->backend->select()->from('servicestatus', $columns);
$this->applyRestriction('monitoring/filter/objects', $query); $this->applyRestriction('monitoring/filter/objects', $query);
@ -628,6 +614,6 @@ class ListController extends Controller
*/ */
private function createTabs() private function createTabs()
{ {
$this->getTabs()->extend(new OutputFormat())->extend(new DashboardAction()); $this->getTabs()->extend(new OutputFormat())->extend(new DashboardAction())->extend(new MenuAction());
} }
} }

View File

@ -17,6 +17,7 @@ use Icinga\Module\Monitoring\Forms\Command\Object\SendCustomNotificationCommandF
use Icinga\Module\Monitoring\Object\ServiceList; use Icinga\Module\Monitoring\Object\ServiceList;
use Icinga\Web\Url; use Icinga\Web\Url;
use Icinga\Web\Widget\Tabextension\DashboardAction; use Icinga\Web\Widget\Tabextension\DashboardAction;
use Icinga\Web\Widget\Tabextension\MenuAction;
class ServicesController extends Controller class ServicesController extends Controller
{ {
@ -46,7 +47,7 @@ class ServicesController extends Controller
'url' => Url::fromRequest(), 'url' => Url::fromRequest(),
'icon' => 'services' 'icon' => 'services'
) )
)->extend(new DashboardAction())->activate('show'); )->extend(new DashboardAction())->extend(new MenuAction())->activate('show');
} }
protected function handleCommandForm(ObjectsCommandForm $form) protected function handleCommandForm(ObjectsCommandForm $form)
@ -56,6 +57,7 @@ class ServicesController extends Controller
'host_icon_image_alt', 'host_icon_image_alt',
'host_name', 'host_name',
'host_address', 'host_address',
'host_address6',
'host_output', 'host_output',
'host_state', 'host_state',
'host_problem', 'host_problem',
@ -101,6 +103,7 @@ class ServicesController extends Controller
'host_icon_image_alt', 'host_icon_image_alt',
'host_name', 'host_name',
'host_address', 'host_address',
'host_address6',
'host_output', 'host_output',
'host_state', 'host_state',
'host_problem', 'host_problem',

View File

@ -6,6 +6,7 @@ namespace Icinga\Module\Monitoring\Controllers;
use Icinga\Module\Monitoring\Controller; use Icinga\Module\Monitoring\Controller;
use Icinga\Web\Url; use Icinga\Web\Url;
use Icinga\Web\Widget\Tabextension\DashboardAction; use Icinga\Web\Widget\Tabextension\DashboardAction;
use Icinga\Web\Widget\Tabextension\MenuAction;
class TacticalController extends Controller class TacticalController extends Controller
{ {
@ -22,7 +23,7 @@ class TacticalController extends Controller
'label' => $this->translate('Tactical Overview'), 'label' => $this->translate('Tactical Overview'),
'url' => Url::fromRequest() 'url' => Url::fromRequest()
) )
)->extend(new DashboardAction())->activate('tactical_overview'); )->extend(new DashboardAction())->extend(new MenuAction())->activate('tactical_overview');
$stats = $this->backend->select()->from( $stats = $this->backend->select()->from(
'statussummary', 'statussummary',
array( array(

View File

@ -12,6 +12,7 @@ use Icinga\Module\Monitoring\Web\Widget\SelectBox;
use Icinga\Util\Format; use Icinga\Util\Format;
use Icinga\Web\Url; use Icinga\Web\Url;
use Icinga\Web\Widget\Tabextension\DashboardAction; use Icinga\Web\Widget\Tabextension\DashboardAction;
use Icinga\Web\Widget\Tabextension\MenuAction;
class TimelineController extends Controller class TimelineController extends Controller
{ {
@ -24,7 +25,7 @@ class TimelineController extends Controller
'label' => $this->translate('Timeline'), 'label' => $this->translate('Timeline'),
'url' => Url::fromRequest() 'url' => Url::fromRequest()
) )
)->extend(new DashboardAction())->activate('timeline'); )->extend(new DashboardAction())->extend(new MenuAction())->activate('timeline');
$this->view->title = $this->translate('Timeline'); $this->view->title = $this->translate('Timeline');
// TODO: filter for hard_states (precedence adjustments necessary!) // TODO: filter for hard_states (precedence adjustments necessary!)

View File

@ -16,7 +16,11 @@ foreach ($object->getActionUrls() as $i => $link) {
'Action ' . ($i + 1) . $newTabInfo, 'Action ' . ($i + 1) . $newTabInfo,
array( array(
'url' => $link, 'url' => $link,
'target' => '_blank' 'target' => '_blank',
'renderer' => array(
'NavigationItemRenderer',
'escape_label' => false
)
) )
); );
} }

View File

@ -1,6 +1,6 @@
<?php foreach ($object->customvars as $name => $value): ?> <?php foreach ($object->customvars as $name => $value): ?>
<tr> <tr>
<th><?= $this->escape($name) ?></th> <th><?= $this->escape(ucwords(str_replace('_', ' ', strtolower($name)))) ?></th>
<td><?= $this->customvar($value) ?></td> <td><?= $this->customvar($value) ?></td>
</tr> </tr>
<?php endforeach ?> <?php endforeach ?>

View File

@ -3,7 +3,7 @@
use Icinga\Web\Navigation\Navigation; use Icinga\Web\Navigation\Navigation;
$navigation = new Navigation(); $navigation = new Navigation();
$navigation->load($object->getType() . '-note'); //$navigation->load($object->getType() . '-note');
foreach ($navigation as $item) { foreach ($navigation as $item) {
$item->setObject($object); $item->setObject($object);
} }
@ -26,7 +26,11 @@ if (! empty($links)) {
$this->escape($link) . $newTabInfo, $this->escape($link) . $newTabInfo,
array( array(
'url' => $link, 'url' => $link,
'target' => '_blank' 'target' => '_blank',
'renderer' => array(
'NavigationItemRenderer',
'escape_label' => false
)
) )
); );
} }

View File

@ -28,6 +28,7 @@ class HoststatusQuery extends IdoQuery
'host' => 'ho.name1 COLLATE latin1_general_ci', 'host' => 'ho.name1 COLLATE latin1_general_ci',
'host_action_url' => 'h.action_url', 'host_action_url' => 'h.action_url',
'host_address' => 'h.address', 'host_address' => 'h.address',
'host_address6' => 'h.address6',
'host_alias' => 'h.alias', 'host_alias' => 'h.alias',
'host_display_name' => 'h.display_name COLLATE latin1_general_ci', 'host_display_name' => 'h.display_name COLLATE latin1_general_ci',
'host_icon_image' => 'h.icon_image', 'host_icon_image' => 'h.icon_image',

View File

@ -832,7 +832,7 @@ abstract class IdoQuery extends DbQuery
list($type, $name) = $this->customvarNameToTypeName($customvar); list($type, $name) = $this->customvarNameToTypeName($customvar);
$alias = ($type === 'host' ? 'hcv_' : 'scv_') . $name; $alias = ($type === 'host' ? 'hcv_' : 'scv_') . $name;
$this->customVars[$customvar] = $alias; $this->customVars[strtolower($customvar)] = $alias;
if ($this->hasJoinedVirtualTable('services')) { if ($this->hasJoinedVirtualTable('services')) {
$leftcol = 's.' . $type . '_object_id'; $leftcol = 's.' . $type . '_object_id';

View File

@ -27,6 +27,7 @@ class ServicestatusQuery extends IdoQuery
'hosts' => array( 'hosts' => array(
'host_action_url' => 'h.action_url', 'host_action_url' => 'h.action_url',
'host_address' => 'h.address', 'host_address' => 'h.address',
'host_address6' => 'h.address6',
'host_alias' => 'h.alias COLLATE latin1_general_ci', 'host_alias' => 'h.alias COLLATE latin1_general_ci',
'host_display_name' => 'h.display_name COLLATE latin1_general_ci', 'host_display_name' => 'h.display_name COLLATE latin1_general_ci',
'host_icon_image' => 'h.icon_image', 'host_icon_image' => 'h.icon_image',

View File

@ -82,7 +82,7 @@ class Controller extends IcingaWebController
'service_description', 'service_description',
'servicegroup_name', 'servicegroup_name',
function ($c) { function ($c) {
return preg_match('/^_(?:host|service)_/', $c); return preg_match('/^_(?:host|service)_/i', $c);
} }
)); ));
foreach ($this->getRestrictions($name) as $filter) { foreach ($this->getRestrictions($name) as $filter) {

View File

@ -185,7 +185,11 @@ abstract class DataView implements QueryInterface, SortRules, FilterColumns, Ite
*/ */
public function isValidFilterTarget($column) 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());
} }
/** /**

View File

@ -16,6 +16,7 @@ class HostStatus extends DataView
'host_display_name', 'host_display_name',
'host_alias', 'host_alias',
'host_address', 'host_address',
'host_address6',
'host_state', 'host_state',
'host_state_type', 'host_state_type',
'host_handled', 'host_handled',

View File

@ -18,6 +18,7 @@ class ServiceStatus extends DataView
'host_state_type', 'host_state_type',
'host_last_state_change', 'host_last_state_change',
'host_address', 'host_address',
'host_address6',
'host_problem', 'host_problem',
'host_handled', 'host_handled',
'service_description', 'service_description',

View File

@ -95,6 +95,7 @@ class Host extends MonitoredObject
'host_active_checks_enabled', 'host_active_checks_enabled',
'host_active_checks_enabled_changed', 'host_active_checks_enabled_changed',
'host_address', 'host_address',
'host_address6',
'host_alias', 'host_alias',
'host_attempt', 'host_attempt',
'host_check_command', 'host_check_command',

View File

@ -14,11 +14,13 @@ class Macro
* @var array * @var array
*/ */
private static $icingaMacros = array( private static $icingaMacros = array(
'HOSTNAME' => 'host_name', 'HOSTNAME' => 'host_name',
'HOSTADDRESS' => 'host_address', 'HOSTADDRESS' => 'host_address',
'SERVICEDESC' => 'service_description', 'HOSTADDRESS6' => 'host_address6',
'host.name' => 'host_name', 'SERVICEDESC' => 'service_description',
'host.address' => 'host_address', 'host.name' => 'host_name',
'host.address' => 'host_address',
'host.address6' => 'host_address6',
'service.description' => 'service_description' 'service.description' => 'service_description'
); );
@ -58,8 +60,9 @@ class Macro
if (isset(self::$icingaMacros[$macro]) && isset($object->{self::$icingaMacros[$macro]})) { if (isset(self::$icingaMacros[$macro]) && isset($object->{self::$icingaMacros[$macro]})) {
return $object->{self::$icingaMacros[$macro]}; return $object->{self::$icingaMacros[$macro]};
} }
if (isset($object->customvars[$macro])) { $customVar = strtolower($macro);
return $object->customvars[$macro]; if (isset($object->customvars[$customVar])) {
return $object->customvars[$customVar];
} }
return $macro; return $macro;

View File

@ -239,7 +239,7 @@ abstract class MonitoredObject implements Filterable
foreach ($this->customvars as $name => $value) { foreach ($this->customvars as $name => $value) {
if (! is_object($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(); $this->customvars = array();
$customvars = $query->getQuery()->fetchAll(); $customvars = $query->getQuery()->fetchAll();
foreach ($customvars as $name => $cv) { foreach ($customvars as $cv) {
$name = ucwords(str_replace('_', ' ', strtolower($cv->varname))); $name = strtolower($cv->varname);
if ($blacklistPattern && preg_match($blacklistPattern, $cv->varname)) { if ($blacklistPattern && preg_match($blacklistPattern, $cv->varname)) {
$this->customvars[$name] = '***'; $this->customvars[$name] = '***';
} elseif ($cv->is_json) { } elseif ($cv->is_json) {

View File

@ -112,6 +112,7 @@ class Service extends MonitoredObject
'host_acknowledged', 'host_acknowledged',
'host_active_checks_enabled', 'host_active_checks_enabled',
'host_address', 'host_address',
'host_address6',
'host_alias', 'host_alias',
'host_display_name', 'host_display_name',
'host_handled', 'host_handled',

View File

@ -13,6 +13,7 @@ use Icinga\Module\Monitoring\Forms\Command\Object\ToggleObjectFeaturesCommandFor
use Icinga\Web\Hook; use Icinga\Web\Hook;
use Icinga\Web\Url; use Icinga\Web\Url;
use Icinga\Web\Widget\Tabextension\DashboardAction; use Icinga\Web\Widget\Tabextension\DashboardAction;
use Icinga\Web\Widget\Tabextension\MenuAction;
/** /**
* Base class for the host and service controller * 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());
} }
} }

View File

@ -16,11 +16,14 @@ class MacroTest extends BaseTestCase
$hostMock = Mockery::mock('host'); $hostMock = Mockery::mock('host');
$hostMock->host_name = 'test'; $hostMock->host_name = 'test';
$hostMock->host_address = '1.1.1.1'; $hostMock->host_address = '1.1.1.1';
$hostMock->host_address6 = '::1';
$this->assertEquals(Macro::resolveMacros('$HOSTNAME$', $hostMock), $hostMock->host_name); $this->assertEquals(Macro::resolveMacros('$HOSTNAME$', $hostMock), $hostMock->host_name);
$this->assertEquals(Macro::resolveMacros('$HOSTADDRESS$', $hostMock), $hostMock->host_address); $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.name$', $hostMock), $hostMock->host_name);
$this->assertEquals(Macro::resolveMacros('$host.address$', $hostMock), $hostMock->host_address); $this->assertEquals(Macro::resolveMacros('$host.address$', $hostMock), $hostMock->host_address);
$this->assertEquals(Macro::resolveMacros('$host.address6$', $hostMock), $hostMock->host_address6);
} }
public function testServiceMacros() public function testServiceMacros()
@ -28,13 +31,16 @@ class MacroTest extends BaseTestCase
$svcMock = Mockery::mock('service'); $svcMock = Mockery::mock('service');
$svcMock->host_name = 'test'; $svcMock->host_name = 'test';
$svcMock->host_address = '1.1.1.1'; $svcMock->host_address = '1.1.1.1';
$svcMock->host_address6 = '::1';
$svcMock->service_description = 'a service'; $svcMock->service_description = 'a service';
$this->assertEquals(Macro::resolveMacros('$HOSTNAME$', $svcMock), $svcMock->host_name); $this->assertEquals(Macro::resolveMacros('$HOSTNAME$', $svcMock), $svcMock->host_name);
$this->assertEquals(Macro::resolveMacros('$HOSTADDRESS$', $svcMock), $svcMock->host_address); $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('$SERVICEDESC$', $svcMock), $svcMock->service_description);
$this->assertEquals(Macro::resolveMacros('$host.name$', $svcMock), $svcMock->host_name); $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.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); $this->assertEquals(Macro::resolveMacros('$service.description$', $svcMock), $svcMock->service_description);
} }

View File

@ -8,7 +8,7 @@
<?php if ($success): ?> <?php if ($success): ?>
<?= $this->qlink( <?= $this->qlink(
$this->translate('Login to Icinga Web 2'), $this->translate('Login to Icinga Web 2'),
'authentication/login', 'authentication/login?renderLayout',
null, null,
array( array(
'class' => 'button-like login', 'class' => 'button-like login',
@ -30,4 +30,4 @@
<pre class="log-output"><?= join("\n\n", array_map(function($a) { <pre class="log-output"><?= join("\n\n", array_map(function($a) {
return join("\n", $a); return join("\n", $a);
}, $report)); ?></pre> }, $report)); ?></pre>
</div> </div>

View File

@ -0,0 +1,9 @@
div.about {
width: 600px;
margin: 0 auto;
text-align: center;
.about-modules {
text-align: initial;
}
}

View File

@ -271,14 +271,7 @@ html {
} }
#login { #login {
.logo .image img { .below-logo label {
width: 70%;
}
.form {
width: 100%;
margin: auto;
}
.form label {
width: 100%; width: 100%;
margin: 0; margin: 0;
text-align: center; text-align: center;

View File

@ -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;
}
}

BIN
public/img/bugreport.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
public/img/docs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
public/img/support.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
public/img/wiki.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -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);