Merge branch 'master' into feature/show-icinga-web-2-s-version-in-the-frontend-9247

This commit is contained in:
Johannes Meyer 2015-09-30 15:54:22 +02:00
commit 142851ede7
124 changed files with 6194 additions and 621 deletions

View File

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

View File

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

View File

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

View File

@ -3,11 +3,8 @@
namespace Icinga\Controllers;
use Icinga\Application\Icinga;
use Icinga\Web\Controller\ActionController;
use Icinga\Web\Hook;
use Icinga\Web\Menu;
use Icinga\Web\MenuRenderer;
use Icinga\Web\Url;
/**
* Create complex layout parts
@ -21,9 +18,6 @@ class LayoutController extends ActionController
{
$this->setAutorefreshInterval(15);
$this->_helper->layout()->disableLayout();
$url = Url::fromRequest();
$menu = new MenuRenderer(Menu::load(), $url->getRelativeUrl());
$this->view->menuRenderer = $menu->useCustomRenderer();
$this->view->menuRenderer = Icinga::app()->getMenu()->getRenderer();
}
}

View File

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

View File

@ -0,0 +1,410 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Controllers;
use Exception;
use Icinga\Application\Config;
use Icinga\Exception\NotFoundError;
use Icinga\Data\DataArray\ArrayDatasource;
use Icinga\Forms\ConfirmRemovalForm;
use Icinga\Forms\Navigation\NavigationConfigForm;
use Icinga\Web\Controller;
use Icinga\Web\Form;
use Icinga\Web\Navigation\Navigation;
use Icinga\Web\Notification;
use Icinga\Web\Url;
/**
* Navigation configuration
*/
class NavigationController extends Controller
{
/**
* The global navigation item type configuration
*
* @var array
*/
protected $itemTypeConfig;
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
$this->itemTypeConfig = Navigation::getItemTypeConfiguration();
}
/**
* Return the label for the given navigation item type
*
* @param string $type
*
* @return string $type if no label can be found
*/
protected function getItemLabel($type)
{
return isset($this->itemTypeConfig[$type]['label']) ? $this->itemTypeConfig[$type]['label'] : $type;
}
/**
* Return a list of available navigation item types
*
* @return array
*/
protected function listItemTypes()
{
$types = array();
foreach ($this->itemTypeConfig as $type => $options) {
$types[$type] = isset($options['label']) ? $options['label'] : $type;
}
return $types;
}
/**
* Return all shared navigation item configurations
*
* @param string $owner A username if only items shared by a specific user are desired
*
* @return array
*/
protected function fetchSharedNavigationItemConfigs($owner = null)
{
$configs = array();
foreach ($this->itemTypeConfig as $type => $_) {
$config = Config::navigation($type);
$config->getConfigObject()->setKeyColumn('name');
$query = $config->select();
if ($owner !== null) {
$query->where('owner', $owner);
}
foreach ($query as $itemConfig) {
$configs[] = $itemConfig;
}
}
return $configs;
}
/**
* Return all user navigation item configurations
*
* @param string $username
*
* @return array
*/
protected function fetchUserNavigationItemConfigs($username)
{
$configs = array();
foreach ($this->itemTypeConfig as $type => $_) {
$config = Config::navigation($type, $username);
$config->getConfigObject()->setKeyColumn('name');
foreach ($config->select() as $itemConfig) {
$configs[] = $itemConfig;
}
}
return $configs;
}
/**
* Show the current user a list of his/her navigation items
*/
public function indexAction()
{
$user = $this->Auth()->getUser();
$ds = new ArrayDatasource(array_merge(
$this->fetchSharedNavigationItemConfigs($user->getUsername()),
$this->fetchUserNavigationItemConfigs($user->getUsername())
));
$query = $ds->select();
$this->view->types = $this->listItemTypes();
$this->view->items = $query;
$this->getTabs()->add(
'navigation',
array(
'title' => $this->translate('List and configure your own navigation items'),
'label' => $this->translate('Navigation'),
'url' => 'navigation'
)
)->activate('navigation');
$this->setupSortControl(
array(
'type' => $this->translate('Type'),
'owner' => $this->translate('Shared'),
'name' => $this->translate('Navigation')
),
$query
);
}
/**
* List all shared navigation items
*/
public function sharedAction()
{
$this->assertPermission('config/application/navigation');
$ds = new ArrayDatasource($this->fetchSharedNavigationItemConfigs());
$query = $ds->select();
$removeForm = new Form();
$removeForm->setUidDisabled();
$removeForm->addElement('hidden', 'name', array(
'decorators' => array('ViewHelper')
));
$removeForm->addElement('hidden', 'redirect', array(
'value' => Url::fromPath('navigation/shared'),
'decorators' => array('ViewHelper')
));
$removeForm->addElement('button', 'btn_submit', array(
'escape' => false,
'type' => 'submit',
'class' => 'link-like spinner',
'value' => 'btn_submit',
'decorators' => array('ViewHelper'),
'label' => $this->view->icon('trash'),
'title' => $this->translate('Unshare this navigation item')
));
$this->view->removeForm = $removeForm;
$this->view->types = $this->listItemTypes();
$this->view->items = $query;
$this->getTabs()->add(
'navigation/shared',
array(
'title' => $this->translate('List and configure shared navigation items'),
'label' => $this->translate('Shared Navigation'),
'url' => 'navigation/shared'
)
)->activate('navigation/shared');
$this->setupSortControl(
array(
'type' => $this->translate('Type'),
'owner' => $this->translate('Owner'),
'name' => $this->translate('Shared Navigation')
),
$query
);
}
/**
* Add a navigation item
*/
public function addAction()
{
$form = new NavigationConfigForm();
$form->setRedirectUrl('navigation');
$form->setUser($this->Auth()->getUser());
$form->setItemTypes($this->listItemTypes());
$form->setTitle($this->translate('Create New Navigation Item'));
$form->addDescription($this->translate('Create a new navigation item, such as a menu entry or dashlet.'));
// TODO: Fetch all "safe" parameters from the url and populate them
$form->populate(array('url' => rawurldecode($this->params->get('url', ''))));
$form->setOnSuccess(function (NavigationConfigForm $form) {
$data = array_filter($form->getValues());
try {
$form->add($data);
} catch (Exception $e) {
$form->error($e->getMessage());
return false;
}
if ($form->save()) {
if ($data['type'] === 'menu-item') {
$form->getResponse()->setRerenderLayout();
}
Notification::success(t('Navigation item successfully created'));
return true;
}
return false;
});
$form->handleRequest();
$this->view->form = $form;
$this->render('form');
}
/**
* Edit a navigation item
*/
public function editAction()
{
$itemName = $this->params->getRequired('name');
$itemType = $this->params->getRequired('type');
$referrer = $this->params->get('referrer', 'index');
$user = $this->Auth()->getUser();
if ($user->can('config/application/navigation')) {
$itemOwner = $this->params->get('owner', $user->getUsername());
} else {
$itemOwner = $user->getUsername();
}
$form = new NavigationConfigForm();
$form->setUser($user);
$form->setShareConfig(Config::navigation($itemType));
$form->setUserConfig(Config::navigation($itemType, $itemOwner));
$form->setRedirectUrl($referrer === 'shared' ? 'navigation/shared' : 'navigation');
$form->setTitle(sprintf($this->translate('Edit %s %s'), $this->getItemLabel($itemType), $itemName));
$form->setOnSuccess(function (NavigationConfigForm $form) use ($itemName) {
$data = array_map(
function ($v) {
return $v !== '' ? $v : null;
},
$form->getValues()
);
try {
$form->edit($itemName, $data);
} catch (NotFoundError $e) {
throw $e;
} catch (Exception $e) {
$form->error($e->getMessage());
return false;
}
if ($form->save()) {
if (isset($data['type']) && $data['type'] === 'menu-item') {
$form->getResponse()->setRerenderLayout();
}
Notification::success(sprintf(t('Navigation item "%s" successfully updated'), $itemName));
return true;
}
return false;
});
try {
$form->load($itemName);
$form->handleRequest();
} catch (NotFoundError $_) {
$this->httpNotFound(sprintf($this->translate('Navigation item "%s" not found'), $itemName));
}
$this->view->form = $form;
$this->render('form');
}
/**
* Remove a navigation item
*/
public function removeAction()
{
$itemName = $this->params->getRequired('name');
$itemType = $this->params->getRequired('type');
$user = $this->Auth()->getUser();
$navigationConfigForm = new NavigationConfigForm();
$navigationConfigForm->setUser($user);
$navigationConfigForm->setShareConfig(Config::navigation($itemType));
$navigationConfigForm->setUserConfig(Config::navigation($itemType, $user->getUsername()));
$form = new ConfirmRemovalForm();
$form->setRedirectUrl('navigation');
$form->setTitle(sprintf($this->translate('Remove %s %s'), $this->getItemLabel($itemType), $itemName));
$form->setOnSuccess(function (ConfirmRemovalForm $form) use ($itemName, $navigationConfigForm) {
try {
$itemConfig = $navigationConfigForm->delete($itemName);
} catch (NotFoundError $e) {
Notification::success(sprintf(t('Navigation Item "%s" not found. No action required'), $itemName));
return true;
} catch (Exception $e) {
$form->error($e->getMessage());
return false;
}
if ($navigationConfigForm->save()) {
if ($itemConfig->type === 'menu-item') {
$form->getResponse()->setRerenderLayout();
}
Notification::success(sprintf(t('Navigation Item "%s" successfully removed'), $itemName));
return true;
}
return false;
});
$form->handleRequest();
$this->view->form = $form;
$this->render('form');
}
/**
* Unshare a navigation item
*/
public function unshareAction()
{
$this->assertPermission('config/application/navigation');
$this->assertHttpMethod('POST');
// TODO: I'd like these being form fields
$itemType = $this->params->getRequired('type');
$itemOwner = $this->params->getRequired('owner');
$navigationConfigForm = new NavigationConfigForm();
$navigationConfigForm->setUser($this->Auth()->getUser());
$navigationConfigForm->setShareConfig(Config::navigation($itemType));
$navigationConfigForm->setUserConfig(Config::navigation($itemType, $itemOwner));
$form = new Form(array(
'onSuccess' => function ($form) use ($navigationConfigForm) {
$itemName = $form->getValue('name');
try {
$newConfig = $navigationConfigForm->unshare($itemName);
if ($navigationConfigForm->save()) {
if ($newConfig->getSection($itemName)->type === 'menu-item') {
$form->getResponse()->setRerenderLayout();
}
Notification::success(sprintf(
t('Navigation item "%s" has been unshared'),
$form->getValue('name')
));
} else {
// TODO: It failed obviously to write one of the configs, so we're leaving the user in
// a inconsistent state. Luckily, it's nothing lost but possibly duplicated...
Notification::error(sprintf(
t('Failed to unshare navigation item "%s"'),
$form->getValue('name')
));
}
} catch (NotFoundError $e) {
throw $e;
} catch (Exception $e) {
Notification::error($e->getMessage());
}
$redirect = $form->getValue('redirect');
if (! empty($redirect)) {
$form->setRedirectUrl(htmlspecialchars_decode($redirect));
}
return true;
}
));
$form->setUidDisabled();
$form->setSubmitLabel('btn_submit'); // Required to ensure that isSubmitted() is called
$form->addElement('hidden', 'name', array('required' => true));
$form->addElement('hidden', 'redirect');
try {
$form->handleRequest();
} catch (NotFoundError $_) {
$this->httpNotFound(sprintf($this->translate('Navigation item "%s" not found'), $form->getValue('name')));
}
}
}

View File

@ -32,7 +32,7 @@ class PreferenceController extends BasePreferenceController
array(
'title' => t('Adjust the preferences of Icinga Web 2 according to your needs'),
'label' => t('Preferences'),
'url' => Url::fromPath('/preference')
'url' => Url::fromPath('preference')
)
)
);

View File

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

View File

@ -43,7 +43,7 @@ class ConfigForm extends Form
public function save()
{
try {
$this->config->saveIni();
$this->writeConfig($this->config);
} catch (Exception $e) {
$this->addDecorator('ViewScript', array(
'viewModule' => 'default',
@ -58,4 +58,14 @@ class ConfigForm extends Form
return true;
}
/**
* Write the configuration to disk
*
* @param Config $config
*/
protected function writeConfig(Config $config)
{
$config->saveIni();
}
}

View File

@ -0,0 +1,35 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Forms\Navigation;
class DashletForm extends NavigationItemForm
{
/**
* {@inheritdoc}
*/
public function createElements(array $formData)
{
$this->addElement(
'text',
'pane',
array(
'required' => true,
'label' => $this->translate('Pane'),
'description' => $this->translate('The name of the dashboard pane in which to display this dashlet')
)
);
$this->addElement(
'text',
'url',
array(
'required' => true,
'label' => $this->translate('Url'),
'description' => $this->translate(
'The url to load in the dashlet. For external urls, make sure to prepend'
. ' an appropriate protocol identifier (e.g. http://example.tld)'
)
)
);
}
}

View File

@ -0,0 +1,31 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Forms\Navigation;
class MenuItemForm extends NavigationItemForm
{
/**
* {@inheritdoc}
*/
protected $requiresParentSelection = true;
/**
* {@inheritdoc}
*/
public function createElements(array $formData)
{
parent::createElements($formData);
// Remove _self and _next as for menu entries only _main is valid
$this->getElement('target')->removeMultiOption('_self');
$this->getElement('target')->removeMultiOption('_next');
$parentElement = $this->getParent()->getElement('parent');
if ($parentElement !== null) {
$parentElement->setDescription($this->translate(
'The parent menu to assign this menu entry to. Select "None" to make this a main menu entry'
));
}
}
}

View File

@ -0,0 +1,856 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Forms\Navigation;
use InvalidArgumentException;
use Icinga\Application\Config;
use Icinga\Application\Logger;
use Icinga\Application\Icinga;
use Icinga\Authentication\Auth;
use Icinga\Data\ConfigObject;
use Icinga\Exception\IcingaException;
use Icinga\Exception\NotFoundError;
use Icinga\Exception\ProgrammingError;
use Icinga\Forms\ConfigForm;
use Icinga\User;
use Icinga\Util\String;
use Icinga\Web\Form;
/**
* Form for managing navigation items
*/
class NavigationConfigForm extends ConfigForm
{
/**
* The class namespace where to locate navigation type forms
*
* @var string
*/
const FORM_NS = 'Forms\\Navigation';
/**
* The secondary configuration to write
*
* This is always the reduced configuration and is only written to
* disk once the main configuration has been successfully written.
*
* @var Config
*/
protected $secondaryConfig;
/**
* The navigation item to load when displaying the form for the first time
*
* @var string
*/
protected $itemToLoad;
/**
* The user for whom to manage navigation items
*
* @var User
*/
protected $user;
/**
* The user's navigation configuration
*
* @var Config
*/
protected $userConfig;
/**
* The shared navigation configuration
*
* @var Config
*/
protected $shareConfig;
/**
* The available navigation item types
*
* @var array
*/
protected $itemTypes;
/**
* Initialize this form
*/
public function init()
{
$this->setName('form_config_navigation');
$this->setSubmitLabel($this->translate('Save Changes'));
}
/**
* Set the user for whom to manage navigation items
*
* @param User $user
*
* @return $this
*/
public function setUser(User $user)
{
$this->user = $user;
return $this;
}
/**
* Return the user for whom to manage navigation items
*
* @return User
*/
public function getUser()
{
return $this->user;
}
/**
* Set the user's navigation configuration
*
* @param Config $config
*
* @return $this
*/
public function setUserConfig(Config $config)
{
$config->getConfigObject()->setKeyColumn('name');
$this->userConfig = $config;
return $this;
}
/**
* Return the user's navigation configuration
*
* @param string $type
*
* @return Config
*/
public function getUserConfig($type = null)
{
if ($this->userConfig === null) {
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;
}
/**
* Set the shared navigation configuration
*
* @param Config $config
*
* @return $this
*/
public function setShareConfig(Config $config)
{
$config->getConfigObject()->setKeyColumn('name');
$this->shareConfig = $config;
return $this;
}
/**
* Return the shared navigation configuration
*
* @param string $type
*
* @return Config
*/
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;
}
/**
* Set the available navigation item types
*
* @param array $itemTypes
*
* @return $this
*/
public function setItemTypes(array $itemTypes)
{
$this->itemTypes = $itemTypes;
return $this;
}
/**
* Return the available navigation item types
*
* @return array
*/
public function getItemTypes()
{
return $this->itemTypes ?: array();
}
/**
* Return a list of available parent items for the given type of navigation item
*
* @param string $type
* @param string $owner
*
* @return array
*/
public function listAvailableParents($type, $owner = null)
{
$children = $this->itemToLoad ? $this->getFlattenedChildren($this->itemToLoad) : array();
$names = array();
foreach ($this->getShareConfig($type) as $sectionName => $sectionConfig) {
if (
$sectionName !== $this->itemToLoad
&& $sectionConfig->owner === ($owner ?: $this->getUser()->getUsername())
&& !in_array($sectionName, $children, true)
) {
$names[] = $sectionName;
}
}
foreach ($this->getUserConfig($type) as $sectionName => $sectionConfig) {
if (
$sectionName !== $this->itemToLoad
&& !in_array($sectionName, $children, true)
) {
$names[] = $sectionName;
}
}
return $names;
}
/**
* Recursively return all children of the given navigation item
*
* @param string $name
*
* @return array
*/
protected function getFlattenedChildren($name)
{
$config = $this->getConfigForItem($name);
if ($config === null) {
return array();
}
$children = array();
foreach ($config->toArray() as $sectionName => $sectionConfig) {
if (isset($sectionConfig['parent']) && $sectionConfig['parent'] === $name) {
$children[] = $sectionName;
$children = array_merge($children, $this->getFlattenedChildren($sectionName));
}
}
return $children;
}
/**
* Populate the form with the given navigation item's config
*
* @param string $name
*
* @return $this
*
* @throws NotFoundError In case no navigation item with the given name is found
*/
public function load($name)
{
if ($this->getConfigForItem($name) === null) {
throw new NotFoundError('No navigation item called "%s" found', $name);
}
$this->itemToLoad = $name;
return $this;
}
/**
* Add a new navigation item
*
* The navigation item to add is identified by the array-key `name'.
*
* @param array $data
*
* @return $this
*
* @throws InvalidArgumentException In case $data does not contain a navigation item name or type
* @throws IcingaException In case a navigation item with the same name already exists
*/
public function add(array $data)
{
if (! isset($data['name'])) {
throw new InvalidArgumentException('Key \'name\' missing');
} elseif (! isset($data['type'])) {
throw new InvalidArgumentException('Key \'type\' missing');
}
$shared = false;
$config = $this->getUserConfig($data['type']);
if ((isset($data['users']) && $data['users']) || (isset($data['groups']) && $data['groups'])) {
if ($this->getUser()->can('application/share/navigation')) {
$data['owner'] = $this->getUser()->getUsername();
$config = $this->getShareConfig($data['type']);
$shared = true;
} else {
unset($data['users']);
unset($data['groups']);
}
} elseif (isset($data['parent']) && $data['parent'] && $this->hasBeenShared($data['parent'], $data['type'])) {
$data['owner'] = $this->getUser()->getUsername();
$config = $this->getShareConfig($data['type']);
$shared = true;
}
$itemName = $data['name'];
$exists = $config->hasSection($itemName);
if (! $exists) {
if ($shared) {
$exists = $this->getUserConfig($data['type'])->hasSection($itemName);
} else {
$exists = (bool) $this->getShareConfig($data['type'])
->select()
->where('name', $itemName)
->where('owner', $this->getUser()->getUsername())
->count();
}
}
if ($exists) {
throw new IcingaException(
$this->translate('A navigation item with the name "%s" does already exist'),
$itemName
);
}
unset($data['name']);
$config->setSection($itemName, $data);
$this->setIniConfig($config);
return $this;
}
/**
* Edit a navigation item
*
* @param string $name
* @param array $data
*
* @return $this
*
* @throws NotFoundError In case no navigation item with the given name is found
* @throws IcingaException In case a navigation item with the same name already exists
*/
public function edit($name, array $data)
{
$config = $this->getConfigForItem($name);
if ($config === null) {
throw new NotFoundError('No navigation item called "%s" found', $name);
} else {
$itemConfig = $config->getSection($name);
}
$shared = false;
if ($this->hasBeenShared($name)) {
if (isset($data['parent']) && $data['parent']
? !$this->hasBeenShared($data['parent'])
: ((! isset($data['users']) || !$data['users']) && (! isset($data['groups']) || !$data['groups']))
) {
// It is shared but shouldn't anymore
$config = $this->unshare($name, isset($data['parent']) ? $data['parent'] : null);
}
} elseif ((isset($data['users']) && $data['users']) || (isset($data['groups']) && $data['groups'])) {
if ($this->getUser()->can('application/share/navigation')) {
// It is not shared yet but should be
$this->secondaryConfig = $config;
$config = $this->getShareConfig();
$data['owner'] = $this->getUser()->getUsername();
$shared = true;
} else {
unset($data['users']);
unset($data['groups']);
}
} elseif (isset($data['parent']) && $data['parent'] && $this->hasBeenShared($data['parent'])) {
// Its parent is shared so should it itself
$this->secondaryConfig = $config;
$config = $this->getShareConfig();
$data['owner'] = $this->getUser()->getUsername();
$shared = true;
}
$oldName = null;
if (isset($data['name'])) {
if ($data['name'] !== $name) {
$oldName = $name;
$name = $data['name'];
$exists = $config->hasSection($name);
if (! $exists) {
$ownerName = $itemConfig->owner ?: $this->getUser()->getUsername();
if ($shared || $this->hasBeenShared($oldName)) {
if ($ownerName === $this->getUser()->getUsername()) {
$exists = $this->getUserConfig()->hasSection($name);
} else {
$exists = Config::navigation($itemConfig->type, $ownerName)->hasSection($name);
}
} else {
$exists = (bool) $this->getShareConfig()
->select()
->where('name', $name)
->where('owner', $ownerName)
->count();
}
}
if ($exists) {
throw new IcingaException(
$this->translate('A navigation item with the name "%s" does already exist'),
$name
);
}
}
unset($data['name']);
}
$itemConfig->merge($data);
foreach ($itemConfig->toArray() as $k => $v) {
if ($v === null) {
unset($itemConfig->$k);
}
}
if ($shared) {
// Share all descendant children
foreach ($this->getFlattenedChildren($oldName ?: $name) as $child) {
$childConfig = $this->secondaryConfig->getSection($child);
$this->secondaryConfig->removeSection($child);
$childConfig->owner = $this->getUser()->getUsername();
$config->setSection($child, $childConfig);
}
}
if ($oldName) {
// Update the parent name on all direct children
foreach ($config as $sectionConfig) {
if ($sectionConfig->parent === $oldName) {
$sectionConfig->parent = $name;
}
}
$config->removeSection($oldName);
}
if ($this->secondaryConfig !== null) {
$this->secondaryConfig->removeSection($oldName ?: $name);
}
$config->setSection($name, $itemConfig);
$this->setIniConfig($config);
return $this;
}
/**
* Remove a navigation item
*
* @param string $name
*
* @return ConfigObject The navigation item's config
*
* @throws NotFoundError In case no navigation item with the given name is found
* @throws IcingaException In case the navigation item has still children
*/
public function delete($name)
{
$config = $this->getConfigForItem($name);
if ($config === null) {
throw new NotFoundError('No navigation item called "%s" found', $name);
}
$children = $this->getFlattenedChildren($name);
if (! empty($children)) {
throw new IcingaException(
$this->translate(
'Unable to delete navigation item "%s". There'
. ' are other items dependent from it: %s'
),
$name,
join(', ', $children)
);
}
$section = $config->getSection($name);
$config->removeSection($name);
$this->setIniConfig($config);
return $section;
}
/**
* Unshare the given navigation item
*
* @param string $name
* @param string $parent
*
* @return Config The new config of the given navigation item
*
* @throws NotFoundError In case no navigation item with the given name is found
* @throws IcingaException In case the navigation item has a parent assigned to it
*/
public function unshare($name, $parent = null)
{
$config = $this->getShareConfig();
if (! $config->hasSection($name)) {
throw new NotFoundError('No navigation item called "%s" found', $name);
}
$itemConfig = $config->getSection($name);
if ($parent === null) {
$parent = $itemConfig->parent;
}
if ($parent && $this->hasBeenShared($parent)) {
throw new IcingaException(
$this->translate(
'Unable to unshare navigation item "%s". It is dependent from item "%s".'
. ' Dependent items can only be unshared by unsharing their parent'
),
$name,
$parent
);
}
$children = $this->getFlattenedChildren($name);
$config->removeSection($name);
$this->secondaryConfig = $config;
if (! $itemConfig->owner || $itemConfig->owner === $this->getUser()->getUsername()) {
$config = $this->getUserConfig();
} else {
$config = Config::navigation($itemConfig->type, $itemConfig->owner);
}
foreach ($children as $child) {
$childConfig = $this->secondaryConfig->getSection($child);
unset($childConfig->owner);
$this->secondaryConfig->removeSection($child);
$config->setSection($child, $childConfig);
}
unset($itemConfig->owner);
unset($itemConfig->users);
unset($itemConfig->groups);
$config->setSection($name, $itemConfig);
$this->setIniConfig($config);
return $config;
}
/**
* {@inheritdoc}
*/
public function createElements(array $formData)
{
$shared = false;
$itemTypes = $this->getItemTypes();
$itemType = isset($formData['type']) ? $formData['type'] : key($itemTypes);
if ($itemType === null) {
throw new ProgrammingError(
'This should actually not happen. Create a bug report at dev.icinga.org'
. ' or remove this assertion if you know what you\'re doing'
);
}
$itemForm = $this->getItemForm($itemType);
$this->addElement(
'text',
'name',
array(
'required' => true,
'label' => $this->translate('Name'),
'description' => $this->translate(
'The name of this navigation item that is used to differentiate it from others'
)
)
);
if (
(! $itemForm->requiresParentSelection() || !isset($formData['parent']) || !$formData['parent'])
&& $this->getUser()->can('application/share/navigation')
) {
$checked = isset($formData['shared']) ? null : (isset($formData['users']) || isset($formData['groups']));
$this->addElement(
'checkbox',
'shared',
array(
'autosubmit' => true,
'ignore' => true,
'value' => $checked,
'label' => $this->translate('Shared'),
'description' => $this->translate('Tick this box to share this item with others')
)
);
if ($checked || (isset($formData['shared']) && $formData['shared'])) {
$shared = true;
$this->addElement(
'text',
'users',
array(
'label' => $this->translate('Users'),
'description' => $this->translate(
'Comma separated list of usernames to share this item with'
)
)
);
$this->addElement(
'text',
'groups',
array(
'label' => $this->translate('Groups'),
'description' => $this->translate(
'Comma separated list of group names to share this item with'
)
)
);
}
}
if (empty($itemTypes) || count($itemTypes) === 1) {
$this->addElement(
'hidden',
'type',
array(
'value' => $itemType
)
);
} else {
$this->addElement(
'select',
'type',
array(
'required' => true,
'autosubmit' => true,
'label' => $this->translate('Type'),
'description' => $this->translate('The type of this navigation item'),
'multiOptions' => $itemTypes
)
);
}
if (! $shared && $itemForm->requiresParentSelection()) {
if ($this->itemToLoad && $this->hasBeenShared($this->itemToLoad)) {
$itemConfig = $this->getShareConfig()->getSection($this->itemToLoad);
$availableParents = $this->listAvailableParents($itemType, $itemConfig->owner);
} else {
$availableParents = $this->listAvailableParents($itemType);
}
$this->addElement(
'select',
'parent',
array(
'allowEmpty' => true,
'autosubmit' => true,
'label' => $this->translate('Parent'),
'description' => $this->translate(
'The parent item to assign this navigation item to. '
. 'Select "None" to make this a main navigation item'
),
'multiOptions' => array_merge(
array('' => $this->translate('None', 'No parent for a navigation item')),
empty($availableParents) ? array() : array_combine($availableParents, $availableParents)
)
)
);
}
$this->addSubForm($itemForm, 'item_form');
$itemForm->create($formData); // May require a parent which gets set by addSubForm()
}
/**
* Populate the configuration of the navigation item to load
*/
public function onRequest()
{
if ($this->itemToLoad) {
$data = $this->getConfigForItem($this->itemToLoad)->getSection($this->itemToLoad)->toArray();
$data['name'] = $this->itemToLoad;
$this->populate($data);
}
}
/**
* {@inheritdoc}
*/
public function isValid($formData)
{
if (! parent::isValid($formData)) {
return false;
}
$valid = true;
if (isset($formData['users']) && $formData['users']) {
$parsedUserRestrictions = array();
foreach (Auth::getInstance()->getRestrictions('application/share/users') as $userRestriction) {
$parsedUserRestrictions[] = array_map('trim', explode(',', $userRestriction));
}
if (! empty($parsedUserRestrictions)) {
$desiredUsers = array_map('trim', explode(',', $formData['users']));
array_unshift($parsedUserRestrictions, $desiredUsers);
$forbiddenUsers = call_user_func_array('array_diff', $parsedUserRestrictions);
if (! empty($forbiddenUsers)) {
$valid = false;
$this->getElement('users')->addError(
$this->translate(sprintf(
'You are not permitted to share this navigation item with the following users: %s',
implode(', ', $forbiddenUsers)
))
);
}
}
}
if (isset($formData['groups']) && $formData['groups']) {
$parsedGroupRestrictions = array();
foreach (Auth::getInstance()->getRestrictions('application/share/groups') as $groupRestriction) {
$parsedGroupRestrictions[] = array_map('trim', explode(',', $groupRestriction));
}
if (! empty($parsedGroupRestrictions)) {
$desiredGroups = array_map('trim', explode(',', $formData['groups']));
array_unshift($parsedGroupRestrictions, $desiredGroups);
$forbiddenGroups = call_user_func_array('array_diff', $parsedGroupRestrictions);
if (! empty($forbiddenGroups)) {
$valid = false;
$this->getElement('groups')->addError(
$this->translate(sprintf(
'You are not permitted to share this navigation item with the following groups: %s',
implode(', ', $forbiddenGroups)
))
);
}
}
}
return $valid;
}
/**
* {@inheritdoc}
*/
public function getValues($suppressArrayNotation = false)
{
$values = parent::getValues();
$values = array_merge($values, $values['item_form']);
unset($values['item_form']);
return $values;
}
/**
* {@inheritdoc}
*/
protected function writeConfig(Config $config)
{
parent::writeConfig($config);
if ($this->secondaryConfig !== null) {
$this->config = $this->secondaryConfig; // Causes the config being displayed to the user in case of an error
parent::writeConfig($this->secondaryConfig);
}
}
/**
* Return the navigation configuration the given item is a part of
*
* @param string $name
*
* @return Config|null In case the item is not part of any configuration
*/
protected function getConfigForItem($name)
{
if ($this->getUserConfig()->hasSection($name)) {
return $this->getUserConfig();
} elseif ($this->getShareConfig()->hasSection($name)) {
if (
$this->getShareConfig()->get($name, 'owner') === $this->getUser()->getUsername()
|| $this->getUser()->can('config/application/navigation')
) {
return $this->getShareConfig();
}
}
}
/**
* Return whether the given navigation item has been shared
*
* @param string $name
* @param string $type
*
* @return bool
*/
protected function hasBeenShared($name, $type = null)
{
return $this->getShareConfig($type) === $this->getConfigForItem($name);
}
/**
* Return the form for the given type of navigation item
*
* @param string $type
*
* @return Form
*/
protected function getItemForm($type)
{
$className = String::cname($type, '-') . 'Form';
$form = null;
foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) {
$classPath = 'Icinga\\Module\\'
. ucfirst($module->getName())
. '\\'
. static::FORM_NS
. '\\'
. $className;
if (class_exists($classPath)) {
$form = new $classPath();
break;
}
}
if ($form === null) {
$classPath = 'Icinga\\' . static::FORM_NS . '\\' . $className;
if (class_exists($classPath)) {
$form = new $classPath();
}
}
if ($form === null) {
Logger::debug(
'Failed to find custom navigation item form %s for item %s. Using form NavigationItemForm now',
$className,
$type
);
$form = new NavigationItemForm();
} elseif (! $form instanceof NavigationItemForm) {
throw new ProgrammingError('Class %s must inherit from NavigationItemForm', $classPath);
}
return $form;
}
}

View File

@ -0,0 +1,91 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Forms\Navigation;
use Icinga\Web\Form;
use Icinga\Web\Url;
class NavigationItemForm extends Form
{
/**
* Whether to create a select input to choose a parent for a navigation item of a particular type
*
* @var bool
*/
protected $requiresParentSelection = false;
/**
* Return whether to create a select input to choose a parent for a navigation item of a particular type
*
* @return bool
*/
public function requiresParentSelection()
{
return $this->requiresParentSelection;
}
/**
* {@inheritdoc}
*/
public function createElements(array $formData)
{
$this->addElement(
'select',
'target',
array(
'allowEmpty' => true,
'label' => $this->translate('Target'),
'description' => $this->translate('The target where to open this navigation item\'s url'),
'multiOptions' => array(
'_blank' => $this->translate('New Window'),
'_next' => $this->translate('New Column'),
'_main' => $this->translate('Single Column'),
'_self' => $this->translate('Current Column')
)
)
);
$this->addElement(
'text',
'url',
array(
'allowEmpty' => true,
'label' => $this->translate('Url'),
'description' => $this->translate(
'The url of this navigation item. Leave blank if you only want the'
. ' name being displayed. For external urls, make sure to prepend'
. ' an appropriate protocol identifier (e.g. http://example.tld)'
)
)
);
$this->addElement(
'text',
'icon',
array(
'allowEmpty' => true,
'label' => $this->translate('Icon'),
'description' => $this->translate(
'The icon of this navigation item. Leave blank if you do not want a icon being displayed'
)
)
);
}
/**
* {@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

@ -28,7 +28,7 @@ class RoleForm extends ConfigForm
*
* @var array
*/
protected $providedRestrictions = array();
protected $providedRestrictions;
/**
* {@inheritdoc}
@ -37,6 +37,8 @@ class RoleForm extends ConfigForm
{
$this->providedPermissions = array(
'*' => $this->translate('Allow everything') . ' (*)',
'application/share/navigation' => $this->translate('Allow to share navigation items')
. ' (application/share/navigation)',
'application/stacktraces' => $this->translate(
'Allow to adjust in the preferences whether to show stacktraces'
) . ' (application/stacktraces)',
@ -48,6 +50,7 @@ class RoleForm extends ConfigForm
'config/application/resources' => 'config/application/resources',
'config/application/userbackend' => 'config/application/userbackend',
'config/application/usergroupbackend' => 'config/application/usergroupbackend',
'config/application/navigation' => 'config/application/navigation',
'config/authentication/*' => 'config/authentication/*',
'config/authentication/users/*' => 'config/authentication/users/*',
'config/authentication/users/show' => 'config/authentication/users/show',
@ -67,9 +70,23 @@ class RoleForm extends ConfigForm
'config/modules' => 'config/modules'
*/
);
$helper = new Zend_Form_Element('bogus');
$this->providedRestrictions = array(
$helper->filterName('application/share/users') => array(
'name' => 'application/share/users',
'description' => $this->translate(
'Restrict which users this role can share items and information with'
)
),
$helper->filterName('application/share/groups') => array(
'name' => 'application/share/groups',
'description' => $this->translate(
'Restrict which groups this role can share items and information with'
)
)
);
$mm = Icinga::app()->getModuleManager();
foreach ($mm->listInstalledModules() as $moduleName) {
$modulePermission = $mm::MODULE_PERMISSION_NS . $moduleName;

View File

@ -22,7 +22,7 @@ if ($this->layout()->autorefreshInterval) {
<?php if (Auth::getInstance()->isAuthenticated()): ?>
<?= $this->qlink(
'',
'/dashboard',
'dashboard',
null,
array(
'icon' => '../logo_icinga-inv.png',

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

@ -63,7 +63,7 @@ $innerLayoutScript = $this->layout()->innerLayout . '.phtml';
<!--<![endif]-->
<script type="text/javascript">
var icinga = new Icinga({
baseUrl: '<?= $this->href('/') ?>'
baseUrl: '<?= $this->baseUrl(); ?>'
});
</script>
</body>

View File

@ -1,8 +1,6 @@
<?php
use Icinga\Web\Url;
use Icinga\Web\Menu;
use Icinga\Web\MenuRenderer;
use Icinga\Application\Icinga;
// Don't render a menu for unauthenticated users unless menu is auth aware
if (! $this->auth()->isAuthenticated()) {
@ -27,10 +25,7 @@ if (! $this->auth()->isAuthenticated()) {
'layout/menu.phtml',
'default',
array(
'menuRenderer' => new MenuRenderer(
Menu::load(),
Url::fromRequest()->without('renderLayout')->getRelativeUrl()
)
'menuRenderer' => Icinga::app()->getMenu()->getRenderer()
)
) ?>
</div>

View File

@ -4,7 +4,7 @@
<img aria-hidden="true" class="fade-in one" src="<?= $this->baseUrl('img/logo_icinga_big.png'); ?>" alt="<?= $this->translate('The Icinga logo'); ?>" >
</div>
</div>
<div class="form" data-base-target="layout">
<div class="below-logo" data-base-target="layout">
<h1><?= $this->translate('Welcome to Icinga Web 2'); ?></h1>
<?php if ($requiresSetup): ?>
<p class="config-note"><?= sprintf(

View File

@ -2,7 +2,7 @@
<?= $tabs; ?>
</div>
<div class="content" data-base-target="_next">
<a href="<?= $this->href('/config/createresource'); ?>">
<a href="<?= $this->href('config/createresource'); ?>">
<?= $this->icon('plus'); ?> <?= $this->translate('Create A New Resource'); ?>
</a>
<table class="action alternating" id="resource-edit-table">

View File

@ -2,7 +2,7 @@
<?= $tabs; ?>
</div>
<div class="content" data-base-target="_next">
<a href="<?= $this->href('/config/createuserbackend'); ?>">
<a href="<?= $this->href('config/createuserbackend'); ?>">
<?= $this->icon('plus'); ?><?= $this->translate('Create A New User Backend'); ?>
</a>
<div id="authentication-reorder-form">

View File

@ -1,10 +1,12 @@
<?php if (! $hideControls): ?>
<div class="controls">
<?= $this->tabs->showOnlyCloseButton() ?>
<?= $tabs->showOnlyCloseButton(); ?>
</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>
<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\Updatable;
use Icinga\Data\Selectable;
$extensible = $this->hasPermission('config/authentication/groups/add') && $backend instanceof Extensible;
@ -67,7 +68,22 @@ foreach ($members as $member): ?>
<tbody>
<?php endif ?>
<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)): ?>
<td class="member-remove" data-base-target="_self">
<?php $removeForm->getElement('user_name')->setValue($member->user_name); echo $removeForm; ?>

View File

@ -12,8 +12,5 @@ if ($searchDashboard->search('dummy')->getPane('search')->hasDashlets()): ?>
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
/>
</form>
<? endif; ?>
<nav>
<h1 id="navigation" class="sr-only"><?= t('Navigation'); ?></h1>
<?= $menuRenderer; ?>
</nav>
<?php endif; ?>
<?= $menuRenderer->setHeading(t('Navigation'))->setElementTag('nav'); ?>

View File

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

View File

@ -0,0 +1,59 @@
<?php if (! $this->compact): ?>
<div class="controls">
<?= $this->tabs; ?>
<?= $this->sortBox; ?>
<?= $this->limiter; ?>
<?= $this->paginator; ?>
<?= $this->filterEditor; ?>
</div>
<?php endif ?>
<div class="content" data-base-target="_next">
<a href="<?= $this->href('navigation/add'); ?>">
<?= $this->icon('plus'); ?> <?= $this->translate('Create A New Navigation Item'); ?>
</a>
<?php if (count($items) === 0): ?>
<p><?= $this->translate('You did not create any navigation item yet'); ?></p>
<?php else: ?>
<table class="action alternating">
<thead>
<th><?= $this->translate('Navigation'); ?></th>
<th style="width: 10em"><?= $this->translate('Type'); ?></th>
<th style="width: 5em"><?= $this->translate('Shared'); ?></th>
<th style="width: 5em"><?= $this->translate('Remove'); ?></th>
</thead>
<tbody>
<?php foreach ($items as $item): ?>
<tr>
<td><?= $this->qlink(
$item->name,
'navigation/edit',
array(
'name' => $item->name,
'type' => $item->type
),
array(
'title' => sprintf($this->translate('Edit navigation item %s'), $item->name)
)
); ?></td>
<td><?= $item->type && isset($types[$item->type])
? $this->escape($types[$item->type])
: $this->escape($this->translate('Unknown')); ?></td>
<td><?= $item->owner ? $this->translate('Yes') : $this->translate('No'); ?></td>
<td><?= $this->qlink(
'',
'navigation/remove',
array(
'name' => $item->name,
'type' => $item->type
),
array(
'icon' => 'trash',
'title' => sprintf($this->translate('Remove navigation item %s'), $item->name)
)
); ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php endif ?>
</div>

View File

@ -0,0 +1,69 @@
<?php
use Icinga\Web\Url;
if (! $this->compact): ?>
<div class="controls">
<?= $this->tabs; ?>
<?= $this->sortBox; ?>
<?= $this->limiter; ?>
<?= $this->paginator; ?>
<?= $this->filterEditor; ?>
</div>
<?php endif ?>
<div class="content" data-base-target="_next">
<?php if (count($items) === 0): ?>
<p><?= $this->translate('There are currently no navigation items being shared'); ?></p>
<?php else: ?>
<table class="action alternating">
<thead>
<th><?= $this->translate('Shared Navigation'); ?></th>
<th style="width: 10em"><?= $this->translate('Type'); ?></th>
<th style="width: 10em"><?= $this->translate('Owner'); ?></th>
<th style="width: 5em"><?= $this->translate('Unshare'); ?></th>
</thead>
<tbody>
<?php foreach ($items as $item): ?>
<tr>
<td><?= $this->qlink(
$item->name,
'navigation/edit',
array(
'name' => $item->name,
'type' => $item->type,
'owner' => $item->owner,
'referrer' => 'shared'
),
array(
'title' => sprintf($this->translate('Edit shared navigation item %s'), $item->name)
)
); ?></td>
<td><?= $item->type && isset($types[$item->type])
? $this->escape($types[$item->type])
: $this->escape($this->translate('Unknown')); ?></td>
<td><?= $this->escape($item->owner); ?></td>
<?php if ($item->parent): ?>
<td><?= $this->icon(
'block',
sprintf(
$this->translate(
'This is a child of the navigation item %1$s. You can'
. ' only unshare this item by unsharing %1$s'
),
$item->parent
)
); ?></td>
<?php else: ?>
<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 ?>
</tr>
<?php endforeach ?>
</tbody>
</table>
<?php endif ?>
</div>

View File

@ -301,3 +301,8 @@ The first release candidate of Icinga Web 2 introduces the following non-backwar
* The **instances.ini** configuration file provided by the monitoring module
has been renamed to **commandtransports.ini**. The content and location of
the file remains unchanged.
* The location of a user's preferences has been changed from
**<config-dir>/preferences/<username>.ini** to
**<config-dir>/preferences/<username>/config.ini**.
The content of the file remains unchanged.

View File

@ -19,21 +19,38 @@ to handle authentication and authorization, monitoring data or user preferences.
Directive | Description
----------------|------------
**type** | `db`
**db** | Database management system. Either `mysql` or `pgsql`.
**host** | Connect to the database server on the given host.
**port** | Port number to use for the connection.
**db** | Database management system. In most cases `mysql` or `pgsql`.
**host** | Connect to the database server on the given host. For using unix domain sockets, specify `localhost` for MySQL and the path to the unix domain socket directory for PostgreSQL.
**port** | Port number to use. Mandatory for connections to a PostgreSQL database.
**username** | The username to use when connecting to the server.
**password** | The password to use when connecting to the server.
**dbname** | The database to use.
**Example:**
```
[icingaweb]
````
[icingaweb-mysql-tcp]
type = db
db = mysql
host = 127.0.0.1
port = 3306
username = icingaweb
password = icingaweb
dbname = icingaweb
[icingaweb-mysql-socket]
type = db
db = mysql
host = localhost
port = 3306
username = icingaweb
password = icingaweb
dbname = icingaweb
[icingaweb-pgsql-socket]
type = db
db = pgsql
host = /var/run/postgresql
port = 5432
username = icingaweb
password = icingaweb
dbname = icingaweb

View File

@ -12,7 +12,10 @@ use Icinga\Data\ConfigObject;
use Icinga\Data\Selectable;
use Icinga\Data\SimpleQuery;
use Icinga\File\Ini\IniWriter;
use Icinga\File\Ini\IniParser;
use Icinga\Exception\IcingaException;
use Icinga\Exception\NotReadableError;
use Icinga\Web\Navigation\Navigation;
/**
* Container for INI like configuration and global registry of application and module related configuration.
@ -40,6 +43,13 @@ class Config implements Countable, Iterator, Selectable
*/
protected static $modules = array();
/**
* Navigation config instances per type
*
* @var array
*/
protected static $navigation = array();
/**
* The internal ConfigObject
*
@ -313,9 +323,7 @@ class Config implements Countable, Iterator, Selectable
if ($filepath === false) {
$emptyConfig->setConfigFile($file);
} elseif (is_readable($filepath)) {
$config = new static(new ConfigObject(parse_ini_file($filepath, true)));
$config->setConfigFile($filepath);
return $config;
return IniParser::parseIniFile($filepath);
} elseif (@file_exists($filepath)) {
throw new NotReadableError(t('Cannot read config file "%s". Permission denied'), $filepath);
}
@ -417,6 +425,60 @@ class Config implements Countable, Iterator, Selectable
return $moduleConfigs[$configname];
}
/**
* Retrieve a navigation config
*
* @param string $type The type identifier of the navigation item for which to return its config
* @param string $username A user's name or null if the shared config is desired
* @param bool $fromDisk If true, the configuration will be read from disk
*
* @return Config The requested configuration
*/
public static function navigation($type, $username = null, $fromDisk = false)
{
if (! isset(self::$navigation[$type])) {
self::$navigation[$type] = array();
}
$branch = $username ?: 'shared';
$typeConfigs = self::$navigation[$type];
if (! isset($typeConfigs[$branch]) || $fromDisk) {
$typeConfigs[$branch] = static::fromIni(static::getNavigationConfigPath($type, $username));
}
return $typeConfigs[$branch];
}
/**
* Return the path to the configuration file for the given navigation item type and user
*
* @param string $type
* @param string $username
*
* @return string
*
* @throws IcingaException In case the given type is unknown
*/
protected static function getNavigationConfigPath($type, $username = null)
{
$itemTypeConfig = Navigation::getItemTypeConfiguration();
if (! isset($itemTypeConfig[$type])) {
throw new IcingaException('Invalid navigation item type %s provided', $type);
}
if (isset($itemTypeConfig[$type]['config'])) {
$filename = $itemTypeConfig[$type]['config'] . '.ini';
} else {
$filename = $type . 's.ini';
}
return static::resolvePath(
($username ? 'preferences' . DIRECTORY_SEPARATOR . $username : 'navigation')
. DIRECTORY_SEPARATOR
. $filename
);
}
/**
* Return this config rendered as a INI structured string
*

View File

@ -0,0 +1,54 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Application\Modules;
/**
* Container for module dashboards
*/
class DashboardContainer extends NavigationItemContainer
{
/**
* This dashboard's dashlets
*
* @var array
*/
protected $dashlets;
/**
* Set this dashboard's dashlets
*
* @param array $dashlets
*
* @return $this
*/
public function setDashlets(array $dashlets)
{
$this->dashlets = $dashlets;
return $this;
}
/**
* Return this dashboard's dashlets
*
* @return array
*/
public function getDashlets()
{
return $this->dashlets ?: array();
}
/**
* Add a new dashlet
*
* @param string $name
* @param string $url
*
* @return $this
*/
public function add($name, $url)
{
$this->dashlets[$name] = $url;
return $this;
}
}

View File

@ -0,0 +1,55 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Application\Modules;
/**
* Container for module menu items
*/
class MenuItemContainer extends NavigationItemContainer
{
/**
* This menu item's children
*
* @var MenuItemContainer[]
*/
protected $children;
/**
* Set this menu item's children
*
* @param MenuItemContainer[] $children
*
* @return $this
*/
public function setChildren(array $children)
{
$this->children = $children;
return $this;
}
/**
* Return this menu item's children
*
* @return array
*/
public function getChildren()
{
return $this->children ?: array();
}
/**
* Add a new sub menu
*
* @param string $name
* @param array $properties
*
* @return MenuItemContainer The newly added sub menu
*/
public function add($name, array $properties = array())
{
$child = new MenuItemContainer($name, $properties);
$this->children[] = $child;
return $child;
}
}

View File

@ -11,7 +11,8 @@ use Icinga\Application\ApplicationBootstrap;
use Icinga\Application\Config;
use Icinga\Application\Icinga;
use Icinga\Application\Logger;
use Icinga\Data\ConfigObject;
use Icinga\Application\Modules\DashboardContainer;
use Icinga\Application\Modules\MenuItemContainer;
use Icinga\Exception\IcingaException;
use Icinga\Exception\ProgrammingError;
use Icinga\Module\Setup\SetupWizard;
@ -19,9 +20,8 @@ use Icinga\Util\File;
use Icinga\Util\Translator;
use Icinga\Web\Controller\Dispatcher;
use Icinga\Web\Hook;
use Icinga\Web\Menu;
use Icinga\Web\Navigation\Navigation;
use Icinga\Web\Widget;
use Icinga\Web\Widget\Dashboard\Pane;
/**
* Module handling
@ -189,7 +189,7 @@ class Module
/**
* A set of menu elements
*
* @var Menu[]
* @var MenuItemContainer[]
*/
protected $menuItems = array();
@ -221,6 +221,13 @@ class Module
*/
protected $userGroupBackends = array();
/**
* This module's configurable navigation items
*
* @var array
*/
protected $navigationItems = array();
/**
* Create a new module object
*
@ -277,38 +284,98 @@ class Module
}
/**
* Get all pane items
* Return this module's dashboard
*
* @return array
* @return Navigation
*/
public function getPaneItems()
public function getDashboard()
{
$this->launchConfigScript();
return $this->paneItems;
return $this->createDashboard($this->paneItems);
}
/**
* Add a pane to dashboard
* Create and return a new navigation for the given dashboard panes
*
* @param string $name
* @param DashboardContainer[] $panes
*
* @return Pane
* @return Navigation
*/
protected function dashboard($name)
public function createDashboard(array $panes)
{
$this->paneItems[$name] = new Pane($name);
$navigation = new Navigation();
foreach ($panes as $pane) {
/** @var DashboardContainer $pane */
$dashlets = array();
foreach ($pane->getDashlets() as $dashletName => $dashletUrl) {
$dashlets[$this->translate($dashletName)] = $dashletUrl;
}
$navigation->addItem(
$pane->getName(),
array_merge(
$pane->getProperties(),
array(
'label' => $this->translate($pane->getName()),
'type' => 'dashboard-pane',
'dashlets' => $dashlets
)
)
);
}
return $navigation;
}
/**
* Add or get a dashboard pane
*
* @param string $name
* @param array $properties
*
* @return DashboardContainer
*/
protected function dashboard($name, array $properties = array())
{
if (array_key_exists($name, $this->paneItems)) {
$this->paneItems[$name]->setProperties($properties);
} else {
$this->paneItems[$name] = new DashboardContainer($name, $properties);
}
return $this->paneItems[$name];
}
/**
* Get all menu items
* Return this module's menu
*
* @return array
* @return Navigation
*/
public function getMenuItems()
public function getMenu()
{
$this->launchConfigScript();
return $this->menuItems;
return $this->createMenu($this->menuItems);
}
/**
* Create and return a new navigation for the given menu items
*
* @param MenuItemContainer[] $items
*
* @return Navigation
*/
private function createMenu(array $items)
{
$navigation = new Navigation();
foreach ($items as $item) {
/** @var MenuItemContainer $item */
$navigationItem = $navigation->createItem($item->getName(), $item->getProperties());
$navigationItem->setChildren($this->createMenu($item->getChildren()));
$navigationItem->setLabel($this->translate($item->getName()));
$navigation->addItem($navigationItem);
}
return $navigation;
}
/**
@ -317,14 +384,14 @@ class Module
* @param string $name
* @param array $properties
*
* @return Menu
* @return MenuItemContainer
*/
protected function menuSection($name, array $properties = array())
{
if (array_key_exists($name, $this->menuItems)) {
$this->menuItems[$name]->setProperties($properties);
} else {
$this->menuItems[$name] = new Menu($name, new ConfigObject($properties));
$this->menuItems[$name] = new MenuItemContainer($name, $properties);
}
return $this->menuItems[$name];
@ -831,6 +898,17 @@ class Module
return $this->userGroupBackends;
}
/**
* Return this module's configurable navigation items
*
* @return array
*/
public function getNavigationItems()
{
$this->launchConfigScript();
return $this->navigationItems;
}
/**
* Provide a named permission
*
@ -935,6 +1013,25 @@ class Module
return $this;
}
/**
* Provide a new type of configurable navigation item with a optional label and config filename
*
* @param string $type
* @param string $label
* @param string $config
*
* @return $this
*/
protected function provideNavigationItem($type, $label = null, $config = null)
{
$this->navigationItems[$type] = array(
'label' => $label,
'config' => $config
);
return $this;
}
/**
* Register module namespaces on our class loader
*

View File

@ -0,0 +1,117 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Application\Modules;
use Icinga\Exception\ProgrammingError;
/**
* Container for module navigation items
*/
abstract class NavigationItemContainer
{
/**
* This navigation item's name
*
* @var string
*/
protected $name;
/**
* This navigation item's properties
*
* @var array
*/
protected $properties;
/**
* Create a new NavigationItemContainer
*
* @param string $name
* @param array $properties
*/
public function __construct($name, array $properties = array())
{
$this->name = $name;
$this->properties = $properties;
}
/**
* Set this menu item's name
*
* @param string $name
*
* @return $this
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Return this menu item's name
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Set this menu item's properties
*
* @param array $properties
*
* @return $this
*/
public function setProperties(array $properties)
{
$this->properties = $properties;
return $this;
}
/**
* Return this menu item's properties
*
* @return array
*/
public function getProperties()
{
return $this->properties ?: array();
}
/**
* Allow dynamic setters and getters for properties
*
* @param string $name
* @param array $arguments
*
* @return mixed
*
* @throws ProgrammingError In case the called method is not supported
*/
public function __call($name, $arguments)
{
if (method_exists($this, $name)) {
return call_user_method_array($name, $this, $arguments);
}
$type = substr($name, 0, 3);
if ($type !== 'set' && $type !== 'get') {
throw new ProgrammingError(
'Dynamic method %s is not supported. Only getters (get*) and setters (set*) are.',
$name
);
}
$propertyName = strtolower(join('_', preg_split('~(?=[A-Z])~', lcfirst(substr($name, 3)))));
if ($type === 'set') {
$this->properties[$propertyName] = $arguments[0];
return $this;
} else { // $type === 'get'
return array_key_exists($propertyName, $this->properties) ? $this->properties[$propertyName] : null;
}
}
}

View File

@ -16,6 +16,7 @@ use Icinga\User;
use Icinga\Util\TimezoneDetect;
use Icinga\Util\Translator;
use Icinga\Web\Controller\Dispatcher;
use Icinga\Web\Navigation\Navigation;
use Icinga\Web\Notification;
use Icinga\Web\Session;
use Icinga\Web\Session\Session as BaseSession;
@ -139,6 +140,203 @@ class Web extends EmbeddedWeb
return $this->viewRenderer;
}
private function hasAccessToSharedNavigationItem(& $config)
{
// TODO: Provide a more sophisticated solution
if (isset($config['owner']) && $config['owner'] === $this->user->getUsername()) {
unset($config['owner']);
return true;
}
if (isset($config['users'])) {
$users = array_map('trim', explode(',', strtolower($config['users'])));
if (in_array($this->user->getUsername(), $users, true)) {
unset($config['users']);
return true;
}
}
if (isset($config['groups'])) {
$groups = array_map('trim', explode(',', strtolower($config['groups'])));
$userGroups = array_map('strtolower', $this->user->getGroups());
$matches = array_intersect($userGroups, $groups);
if (! empty($matches)) {
unset($config['groups']);
return true;
}
}
return false;
}
/**
* Load and return the shared navigation of the given type
*
* @param string $type
*
* @return Navigation
*/
public function getSharedNavigation($type)
{
$config = Config::navigation($type === 'dashboard-pane' ? 'dashlet' : $type);
if ($type === 'dashboard-pane') {
$panes = array();
foreach ($config as $dashletName => $dashletConfig) {
if ($this->hasAccessToSharedNavigationItem($dashletConfig)) {
// TODO: Throw ConfigurationError if pane or url is missing
$panes[$dashletConfig->pane][$dashletName] = $dashletConfig->url;
}
}
$navigation = new Navigation();
foreach ($panes as $paneName => $dashlets) {
$navigation->addItem(
$paneName,
array(
'type' => 'dashboard-pane',
'dashlets' => $dashlets
)
);
}
} else {
$items = array();
foreach ($config as $name => $typeConfig) {
if ($this->hasAccessToSharedNavigationItem($typeConfig)) {
$items[$name] = $typeConfig;
}
}
$navigation = Navigation::fromConfig($items);
}
return $navigation;
}
/**
* Return the app's menu
*
* @return Navigation
*/
public function getMenu()
{
if ($this->user !== null) {
$menu = array(
'dashboard' => array(
'label' => t('Dashboard'),
'url' => 'dashboard',
'icon' => 'dashboard',
'priority' => 10
),
'system' => array(
'label' => t('System'),
'icon' => 'services',
'priority' => 700,
'renderer' => array(
'SummaryNavigationItemRenderer',
'state' => 'critical'
),
'children' => array(
'about' => array(
'label' => t('About'),
'url' => 'about',
'priority' => 701
)
)
),
'configuration' => array(
'label' => t('Configuration'),
'icon' => 'wrench',
'permission' => 'config/*',
'priority' => 800,
'children' => array(
'application' => array(
'label' => t('Application'),
'url' => 'config/general',
'permission' => 'config/application/*',
'priority' => 810
),
'navigation' => array(
'label' => t('Shared Navigation'),
'url' => 'navigation/shared',
'permission' => 'config/application/navigation',
'priority' => 820,
),
'authentication' => array(
'label' => t('Authentication'),
'url' => 'config/userbackend',
'permission' => 'config/authentication/*',
'priority' => 830
),
'roles' => array(
'label' => t('Roles'),
'url' => 'role/list',
'permission' => 'config/authentication/roles/show',
'priority' => 840
),
'users' => array(
'label' => t('Users'),
'url' => 'user/list',
'permission' => 'config/authentication/users/show',
'priority' => 850
),
'groups' => array(
'label' => t('Usergroups'),
'url' => 'group/list',
'permission' => 'config/authentication/groups/show',
'priority' => 860
),
'modules' => array(
'label' => t('Modules'),
'url' => 'config/modules',
'permission' => 'config/modules',
'priority' => 890
)
)
),
'user' => array(
'label' => $this->user->getUsername(),
'icon' => 'user',
'priority' => 900,
'children' => array(
'preferences' => array(
'label' => t('Preferences'),
'url' => 'preference',
'priority' => 910
),
'navigation' => array(
'label' => t('Navigation'),
'url' => 'navigation',
'priority' => 920
),
'logout' => array(
'label' => t('Logout'),
'url' => 'authentication/logout',
'priority' => 990,
'renderer' => array(
'NavigationItemRenderer',
'target' => '_self'
)
)
)
)
);
if (Logger::writesToFile()) {
$menu['system']['children']['application_log'] = array(
'label' => t('Application Log'),
'url' => 'list/applicationlog',
'priority' => 710
);
}
} else {
$menu = array();
}
return Navigation::fromArray($menu)->load('menu-item');
}
/**
* Dispatch public interface
*/

View File

@ -3,6 +3,23 @@
use Icinga\Util\Translator;
/**
* No-op translate
*
* Supposed to be used for marking a string as available for translation without actually translating it immediately.
* The returned string is the one given in the input. This does only work with the standard gettext macros t() and mt().
*
* @param string $messageId
*
* @return string
*/
function N_($messageId)
{
return $messageId;
}
if (extension_loaded('gettext')) {
/**

View File

@ -12,10 +12,16 @@ use Icinga\Protocol\Ldap\Expression;
use Icinga\Repository\LdapRepository;
use Icinga\Repository\RepositoryQuery;
use Icinga\User;
use Icinga\Application\Logger;
class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBackendInterface
class LdapUserGroupBackend extends LdapRepository implements UserGroupBackendInterface
{
/**
* The user backend being associated with this user group backend
*
* @var LdapUserBackend
*/
protected $userBackend;
/**
* The base DN to use for a user query
*
@ -105,84 +111,26 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken
);
/**
* Normed attribute names based on known LDAP environments
* Set the user backend to be associated with this user group backend
*
* @var array
*/
protected $normedAttributes = array(
'uid' => 'uid',
'gid' => 'gid',
'user' => 'user',
'group' => 'group',
'member' => 'member',
'inetorgperson' => 'inetOrgPerson',
'samaccountname' => 'sAMAccountName'
);
/**
* The name of this repository
*
* @var string
*/
protected $name;
/**
* The datasource being used
*
* @var Connection
*/
protected $ds;
/**
* Create a new LDAP repository object
*
* @param Connection $ds The data source to use
*/
public function __construct($ds)
{
$this->ds = $ds;
}
/**
* Return the given attribute name normed to known LDAP enviroments, if possible
*
* @param string $name
*
* @return string
*/
protected function getNormedAttribute($name)
{
$loweredName = strtolower($name);
if (array_key_exists($loweredName, $this->normedAttributes)) {
return $this->normedAttributes[$loweredName];
}
return $name;
}
/**
* Set this repository's name
*
* @param string $name
* @param LdapUserBackend $backend
*
* @return $this
*/
public function setName($name)
public function setUserBackend(LdapUserBackend $backend)
{
$this->name = $name;
$this->userBackend = $backend;
return $this;
}
/**
* Return this repository's name
* Return the user backend being associated with this user group backend
*
* In case no name has been explicitly set yet, the class name is returned.
*
* @return string
* @return LdapUserBackend
*/
public function getName()
public function getUserBackend()
{
return $this->name;
return $this->userBackend;
}
/**
@ -453,7 +401,6 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken
$lastModifiedAttribute = 'modifyTimestamp';
}
// TODO(jom): Fetching memberships does not work currently, we'll need some aggregate functionality!
$columns = array(
'group' => $this->groupNameAttribute,
'group_name' => $this->groupNameAttribute,
@ -492,13 +439,37 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken
if ($this->groupClass === null) {
throw new ProgrammingError('It is required to set the objectClass where to look for groups first');
}
if ($this->groupMemberAttribute === null) {
throw new ProgrammingError('It is required to set a attribute name where to find a group\'s members first');
}
return array(
$rules = array(
$this->groupClass => array(
'created_at' => 'generalized_time',
'last_modified' => 'generalized_time'
)
);
if (! $this->isAmbiguous($this->groupClass, $this->groupMemberAttribute)) {
$rules[$this->groupClass][] = 'user_name';
}
return $rules;
}
/**
* Return the uid for the given distinguished name
*
* @param string $username
*
* @param string
*/
protected function retrieveUserName($dn)
{
return $this->ds
->select()
->from('*', array($this->userNameAttribute))
->setBase($dn)
->fetchOne();
}
/**
@ -524,6 +495,27 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken
return $table;
}
/**
* Validate that the given column is a valid query target and return it or the actual name if it's an alias
*
* @param string $table The table where to look for the column or alias
* @param string $name The name or alias of the column to validate
* @param RepositoryQuery $query An optional query to pass as context
*
* @return string The given column's name
*
* @throws QueryException In case the given column is not a valid query column
*/
public function requireQueryColumn($table, $name, RepositoryQuery $query = null)
{
$column = parent::requireQueryColumn($table, $name, $query);
if ($name === 'user_name' && $query !== null) {
$query->getQuery()->setUnfoldAttribute('user_name');
}
return $column;
}
/**
* Return the groups the given user is a member of
*
@ -533,43 +525,37 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken
*/
public function getMemberships(User $user)
{
if ($this->groupClass === 'posixGroup') {
// Posix group only uses simple user name
$userDn = $user->getUsername();
} else {
// LDAP groups use the complete DN
if (($userDn = $user->getAdditional('ldap_dn')) === null) {
$userQuery = $this->ds
->select()
->from($this->userClass)
->where($this->userNameAttribute, $user->getUsername())
->setBase($this->userBaseDn)
->setUsePagedResults(false);
if ($this->userFilter) {
$userQuery->where(new Expression($this->userFilter));
}
if ($this->isAmbiguous($this->groupClass, $this->groupMemberAttribute)) {
$queryValue = $user->getUsername();
} elseif (($queryValue = $user->getAdditional('ldap_dn')) === null) {
$userQuery = $this->ds
->select()
->from($this->userClass)
->where($this->userNameAttribute, $user->getUsername())
->setBase($this->userBaseDn)
->setUsePagedResults(false);
if ($this->userFilter) {
$userQuery->where(new Expression($this->userFilter));
}
if (($userDn = $userQuery->fetchDn()) === null) {
return array();
}
if (($queryValue = $userQuery->fetchDn()) === null) {
return array();
}
}
$groupQuery = $this->ds
->select()
->from($this->groupClass, array($this->groupNameAttribute))
->where($this->groupMemberAttribute, $userDn)
->where($this->groupMemberAttribute, $queryValue)
->setBase($this->groupBaseDn);
if ($this->groupFilter) {
$groupQuery->where(new Expression($this->groupFilter));
}
Logger::debug('Fetching groups for user %s using filter %s.', $user->getUsername(), $groupQuery->__toString());
$groups = array();
foreach ($groupQuery as $row) {
$groups[] = $row->{$this->groupNameAttribute};
}
Logger::debug('Fetched %d groups: %s.', count($groups), join(', ', $groups));
return $groups;
}
@ -610,6 +596,7 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken
);
}
$this->setUserBackend($userBackend);
$defaults->merge(array(
'user_base_dn' => $userBackend->getBaseDn(),
'user_class' => $userBackend->getUserClass(),
@ -661,4 +648,4 @@ class LdapUserGroupBackend /*extends LdapRepository*/ implements UserGroupBacken
'group_member_attribute' => 'member'
));
}
}
}

View File

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

View File

@ -97,18 +97,41 @@ class FilterExpression extends Filter
public function matches($row)
{
if (! isset($row->{$this->column})) {
// TODO: REALLY? Exception?
return false;
}
if (is_array($this->expression)) {
return in_array($row->{$this->column}, $this->expression);
} elseif (strpos($this->expression, '*') === false) {
return (string) $row->{$this->column} === (string) $this->expression;
} else {
$parts = preg_split('~\*~', $this->expression);
foreach ($parts as & $part) {
$part = preg_quote($part);
}
$pattern = '/^' . implode('.*', $parts) . '$/';
return (bool) preg_match($pattern, $row->{$this->column});
}
$expression = (string) $this->expression;
if (strpos($expression, '*') === false) {
if (is_array($row->{$this->column})) {
return in_array($expression, $row->{$this->column});
}
return (string) $row->{$this->column} === $expression;
}
$parts = array();
foreach (preg_split('~\*~', $expression) as $part) {
$parts[] = preg_quote($part);
}
$pattern = '/^' . implode('.*', $parts) . '$/';
if (is_array($row->{$this->column})) {
foreach ($row->{$this->column} as $candidate) {
if (preg_match($pattern, $candidate)) {
return true;
}
}
return false;
}
return (bool) preg_match($pattern, $row->{$this->column});
}
public function andFilter(Filter $filter)

View File

@ -5,21 +5,4 @@ namespace Icinga\Data\Filter;
class FilterMatch extends FilterExpression
{
public function matches($row)
{
if (! isset($row->{$this->column})) {
// TODO: REALLY? Exception?
return false;
}
$expression = (string) $this->expression;
if (strpos($expression, '*') === false) {
return (string) $row->{$this->column} === $expression;
} else {
$parts = array();
foreach (preg_split('/\*/', $expression) as $part) {
$parts[] = preg_quote($part);
}
return preg_match('/^' . implode('.*', $parts) . '$/', $row->{$this->column});
}
}
}

View File

@ -7,15 +7,6 @@ class FilterMatchNot extends FilterExpression
{
public function matches($row)
{
$expression = (string) $this->expression;
if (strpos($expression, '*') === false) {
return (string) $row->{$this->column} !== $expression;
} else {
$parts = array();
foreach (preg_split('/\*/', $expression) as $part) {
$parts[] = preg_quote($part);
}
return ! preg_match('/^' . implode('.*', $parts) . '$/', $row->{$this->column});
}
return !parent::matches($row);
}
}

View File

@ -115,4 +115,18 @@ class Document
}
return $str;
}
/**
* Convert $this to an array
*
* @return array
*/
public function toArray()
{
$a = array();
foreach ($this->sections as $section) {
$a[$section->getName()] = $section->toArray();
}
return $a;
}
}

View File

@ -169,4 +169,18 @@ class Section
$str = str_replace(';', '\\;', $str);
return str_replace(PHP_EOL, ' ', $str);
}
/**
* Convert $this to an array
*
* @return array
*/
public function toArray()
{
$a = array();
foreach ($this->directives as $directive) {
$a[$directive->getKey()] = $directive->getValue();
}
return $a;
}
}

View File

@ -9,6 +9,8 @@ use Icinga\File\Ini\Dom\Document;
use Icinga\File\Ini\Dom\Directive;
use Icinga\Application\Logger;
use Icinga\Exception\ConfigurationError;
use Icinga\Exception\NotReadableError;
use Icinga\Application\Config;
class IniParser
{
@ -239,4 +241,25 @@ class IniParser
}
return $doc;
}
/**
* Read the ini file and parse it with ::parseIni()
*
* @param string $file The ini file to read
*
* @return Config
* @throws NotReadableError When the file cannot be read
*/
public static function parseIniFile($file)
{
if (($path = realpath($file)) === false) {
throw new NotReadableError('Couldn\'t compute the absolute path of `%s\'', $file);
}
if (($content = file_get_contents($path)) === false) {
throw new NotReadableError('Couldn\'t read the file `%s\'', $path);
}
return Config::fromArray(self::parseIni($content)->toArray())->setConfigFile($file);
}
}

View File

@ -358,9 +358,25 @@ class LdapConnection implements Selectable, Inspectable
*/
public function count(LdapQuery $query)
{
$ds = $this->getConnection();
$this->bind();
if (($unfoldAttribute = $query->getUnfoldAttribute()) !== null) {
$desiredColumns = $query->getColumns();
if (isset($desiredColumns[$unfoldAttribute])) {
$fields = array($unfoldAttribute => $desiredColumns[$unfoldAttribute]);
} elseif (in_array($unfoldAttribute, $desiredColumns, true)) {
$fields = array($unfoldAttribute);
} else {
throw new ProgrammingError(
'The attribute used to unfold a query\'s result must be selected'
);
}
$res = $this->runQuery($query, $fields);
return count($res);
}
$ds = $this->getConnection();
$results = @ldap_search(
$ds,
$query->getBase() ?: $this->getDn(),
@ -658,7 +674,7 @@ class LdapConnection implements Selectable, Inspectable
protected function runQuery(LdapQuery $query, array $fields = null)
{
$limit = $query->getLimit();
$offset = $query->hasOffset() ? $query->getOffset() - 1 : 0;
$offset = $query->hasOffset() ? $query->getOffset() : 0;
if ($fields === null) {
$fields = $query->getColumns();
@ -711,13 +727,41 @@ class LdapConnection implements Selectable, Inspectable
$count = 0;
$entries = array();
$entry = ldap_first_entry($ds, $results);
$unfoldAttribute = $query->getUnfoldAttribute();
do {
$count += 1;
if (! $serverSorting || $offset === 0 || $offset < $count) {
$entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(
if ($unfoldAttribute) {
$rows = $this->cleanupAttributes(
ldap_get_attributes($ds, $entry),
array_flip($fields)
array_flip($fields),
$unfoldAttribute
);
if (is_array($rows)) {
// TODO: Register the DN the same way as a section name in the ArrayDatasource!
foreach ($rows as $row) {
$count += 1;
if (! $serverSorting || $offset === 0 || $offset < $count) {
$entries[] = $row;
}
if ($serverSorting && $limit > 0 && $limit === count($entries)) {
break;
}
}
} else {
$count += 1;
if (! $serverSorting || $offset === 0 || $offset < $count) {
$entries[ldap_get_dn($ds, $entry)] = $rows;
}
}
} else {
$count += 1;
if (! $serverSorting || $offset === 0 || $offset < $count) {
$entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(
ldap_get_attributes($ds, $entry),
array_flip($fields)
);
}
}
} while ((! $serverSorting || $limit === 0 || $limit !== count($entries))
&& ($entry = ldap_next_entry($ds, $entry))
@ -754,7 +798,7 @@ class LdapConnection implements Selectable, Inspectable
}
$limit = $query->getLimit();
$offset = $query->hasOffset() ? $query->getOffset() - 1 : 0;
$offset = $query->hasOffset() ? $query->getOffset() : 0;
$queryString = (string) $query;
$base = $query->getBase() ?: $this->rootDn;
@ -776,6 +820,7 @@ class LdapConnection implements Selectable, Inspectable
$count = 0;
$cookie = '';
$entries = array();
$unfoldAttribute = $query->getUnfoldAttribute();
do {
// Do not request the pagination control as a critical extension, as we want the
// server to return results even if the paged search request cannot be satisfied
@ -826,12 +871,39 @@ class LdapConnection implements Selectable, Inspectable
$entry = ldap_first_entry($ds, $results);
do {
$count += 1;
if (! $serverSorting || $offset === 0 || $offset < $count) {
$entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(
if ($unfoldAttribute) {
$rows = $this->cleanupAttributes(
ldap_get_attributes($ds, $entry),
array_flip($fields)
array_flip($fields),
$unfoldAttribute
);
if (is_array($rows)) {
// TODO: Register the DN the same way as a section name in the ArrayDatasource!
foreach ($rows as $row) {
$count += 1;
if (! $serverSorting || $offset === 0 || $offset < $count) {
$entries[] = $row;
}
if ($serverSorting && $limit > 0 && $limit === count($entries)) {
break;
}
}
} else {
$count += 1;
if (! $serverSorting || $offset === 0 || $offset < $count) {
$entries[ldap_get_dn($ds, $entry)] = $rows;
}
}
} else {
$count += 1;
if (! $serverSorting || $offset === 0 || $offset < $count) {
$entries[ldap_get_dn($ds, $entry)] = $this->cleanupAttributes(
ldap_get_attributes($ds, $entry),
array_flip($fields)
);
}
}
} while (
(! $serverSorting || $limit === 0 || $limit !== count($entries))
@ -861,9 +933,6 @@ class LdapConnection implements Selectable, Inspectable
// the server: https://www.ietf.org/rfc/rfc2696.txt
ldap_control_paged_result($ds, 0, false, $cookie);
ldap_search($ds, $base, $queryString); // Returns no entries, due to the page size
} else {
// Reset the paged search request so that subsequent requests succeed
ldap_control_paged_result($ds, 0);
}
if (! $serverSorting && $query->hasOrder()) {
@ -879,14 +948,16 @@ class LdapConnection implements Selectable, Inspectable
/**
* Clean up the given attributes and return them as simple object
*
* Applies column aliases, aggregates multi-value attributes as array and sets null for each missing attribute.
* Applies column aliases, aggregates/unfolds multi-value attributes
* as array and sets null for each missing attribute.
*
* @param array $attributes
* @param array $requestedFields
* @param string $unfoldAttribute
*
* @return object
* @return object|array An array in case the object has been unfolded
*/
public function cleanupAttributes($attributes, array $requestedFields)
public function cleanupAttributes($attributes, array $requestedFields, $unfoldAttribute = null)
{
// In case the result contains attributes with a differing case than the requested fields, it is
// necessary to create another array to map attributes case insensitively to their requested counterparts.
@ -927,6 +998,24 @@ class LdapConnection implements Selectable, Inspectable
}
}
if (
$unfoldAttribute !== null
&& isset($cleanedAttributes[$unfoldAttribute])
&& is_array($cleanedAttributes[$unfoldAttribute])
) {
$values = $cleanedAttributes[$unfoldAttribute];
unset($cleanedAttributes[$unfoldAttribute]);
$baseRow = (object) $cleanedAttributes;
$rows = array();
foreach ($values as $value) {
$row = clone $baseRow;
$row->{$unfoldAttribute} = $value;
$rows[] = $row;
}
return $rows;
}
return (object) $cleanedAttributes;
}

View File

@ -35,6 +35,13 @@ class LdapQuery extends SimpleQuery
*/
protected $usePagedResults;
/**
* The name of the attribute used to unfold the result
*
* @var string
*/
protected $unfoldAttribute;
/**
* Initialize this query
*/
@ -90,6 +97,29 @@ class LdapQuery extends SimpleQuery
return $this->usePagedResults;
}
/**
* Set the attribute to be used to unfold the result
*
* @param string $attributeName
*
* @return $this
*/
public function setUnfoldAttribute($attributeName)
{
$this->unfoldAttribute = $attributeName;
return $this;
}
/**
* Return the attribute to use to unfold the result
*
* @return string
*/
public function getUnfoldAttribute()
{
return $this->unfoldAttribute;
}
/**
* Choose an objectClass and the columns you are interested in
*

View File

@ -28,13 +28,27 @@ abstract class LdapRepository extends Repository
* @var array
*/
protected $normedAttributes = array(
'uid' => 'uid',
'gid' => 'gid',
'user' => 'user',
'group' => 'group',
'member' => 'member',
'inetorgperson' => 'inetOrgPerson',
'samaccountname' => 'sAMAccountName'
'uid' => 'uid',
'gid' => 'gid',
'user' => 'user',
'group' => 'group',
'member' => 'member',
'memberuid' => 'memberUid',
'posixgroup' => 'posixGroup',
'uniquemember' => 'uniqueMember',
'groupofnames' => 'groupOfNames',
'inetorgperson' => 'inetOrgPerson',
'samaccountname' => 'sAMAccountName',
'groupofuniquenames' => 'groupOfUniqueNames'
);
/**
* Object attributes whose value is not distinguished name
*
* @var array
*/
protected $ambiguousAttributes = array(
'posixGroup' => 'memberUid'
);
/**
@ -63,4 +77,17 @@ abstract class LdapRepository extends Repository
return $name;
}
}
/**
* 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

@ -5,7 +5,9 @@ namespace Icinga;
use DateTimeZone;
use InvalidArgumentException;
use Icinga\Application\Config;
use Icinga\User\Preferences;
use Icinga\Web\Navigation\Navigation;
/**
* This class represents an authorized user
@ -476,4 +478,39 @@ class User
return false;
}
/**
* Load and return this user's configured navigation of the given type
*
* @param string $type
*
* @return Navigation
*/
public function getNavigation($type)
{
$config = Config::navigation($type === 'dashboard-pane' ? 'dashlet' : $type, $this->getUsername());
if ($type === 'dashboard-pane') {
$panes = array();
foreach ($config as $dashletName => $dashletConfig) {
// TODO: Throw ConfigurationError if pane or url is missing
$panes[$dashletConfig->pane][$dashletName] = $dashletConfig->url;
}
$navigation = new Navigation();
foreach ($panes as $paneName => $dashlets) {
$navigation->addItem(
$paneName,
array(
'type' => 'dashboard-pane',
'dashlets' => $dashlets
)
);
}
} else {
$navigation = Navigation::fromConfig($config);
}
return $navigation;
}
}

View File

@ -8,6 +8,7 @@ use Icinga\Exception\NotReadableError;
use Icinga\Exception\NotWritableError;
use Icinga\User\Preferences;
use Icinga\User\Preferences\PreferencesStore;
use Icinga\File\Ini\IniParser;
/**
* Load and save user preferences from and to INI files
@ -34,7 +35,7 @@ class IniStore extends PreferencesStore
protected function init()
{
$this->preferencesFile = sprintf(
'%s/%s.ini',
'%s/%s/config.ini',
$this->getStoreConfig()->location,
strtolower($this->getUser()->getUsername())
);
@ -57,7 +58,7 @@ class IniStore extends PreferencesStore
$this->getUser()->getUsername()
);
} else {
$this->preferences = parse_ini_file($this->preferencesFile, true);
$this->preferences = IniParser::parseIniFile($this->preferencesFile)->toArray();
}
}

View File

@ -24,7 +24,7 @@ class String
/**
* Uppercase the first character of each word in a string
*
* Converts 'first_name' to 'firstName' for example.
* Converts 'first_name' to 'FirstName' for example.
*
* @param string $name
* @param string $separator Word separator

View File

@ -1,5 +1,6 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Web;
class FileCache
@ -191,7 +192,7 @@ class FileCache
* Whether the given ETag matchesspecific file(s) on disk
*
* 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 $match ETag to match against
@ -208,6 +209,9 @@ class FileCache
if (! $match) {
return false;
}
if (isset($_SERVER['HTTP_CACHE_CONTROL']) && $_SERVER['HTTP_CACHE_CONTROL'] === 'no-cache') {
return false;
}
$etag = self::etagForFiles($files);
return $match === $etag ? $etag : false;

View File

@ -65,6 +65,15 @@ class Form extends Zend_Form
*/
protected $created = false;
/**
* This form's parent
*
* Gets automatically set upon calling addSubForm().
*
* @var Form
*/
protected $_parent;
/**
* Whether the form is an API target
*
@ -243,6 +252,29 @@ class Form extends Zend_Form
parent::__construct($options);
}
/**
* Set this form's parent
*
* @param Form $form
*
* @return $this
*/
public function setParent(Form $form)
{
$this->_parent = $form;
return $this;
}
/**
* Return this form's parent
*
* @return Form
*/
public function getParent()
{
return $this->_parent;
}
/**
* Set a callback that is called instead of this form's onSuccess method
*
@ -844,6 +876,7 @@ class Form extends Zend_Form
$form->setSubmitLabel('');
$form->setTokenDisabled();
$form->setUidDisabled();
$form->setParent($this);
}
if ($name === null) {

View File

@ -27,7 +27,8 @@ class JavaScript
'js/icinga/behavior/tristate.js',
'js/icinga/behavior/navigation.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(

View File

@ -0,0 +1,73 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Web\Navigation;
use Icinga\Web\Url;
/**
* A dashboard pane
*/
class DashboardPane extends NavigationItem
{
/**
* This pane's dashlets
*
* @var array
*/
protected $dashlets;
/**
* Set this pane's dashlets
*
* @param array $dashlets
*
* @return $this
*/
public function setDashlets(array $dashlets)
{
$this->dashlets = $dashlets;
return $this;
}
/**
* Return this pane's dashlets
*
* @param bool $ordered Whether to order the dashlets first
*
* @return array
*/
public function getDashlets($ordered = true)
{
if ($this->dashlets === null) {
return array();
}
if ($ordered) {
ksort($this->dashlets);
}
return $this->dashlets;
}
/**
* {@inheritdoc}
*/
public function init()
{
$this->setUrl(Url::fromPath('dashboard', array('pane' => $this->getName())));
}
/**
* {@inheritdoc}
*/
public function merge(NavigationItem $item)
{
parent::merge($item);
$this->setDashlets(array_merge(
$this->getDashlets(false),
$item->getDashlets(false)
));
}
}

View File

@ -0,0 +1,21 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Web\Navigation;
/**
* Dropdown navigation item
*
* @see \Icinga\Web\Navigation\Navigation For a usage example.
*/
class DropdownItem extends NavigationItem
{
/**
* {@inheritdoc}
*/
public function init()
{
$this->children->setLayout(Navigation::LAYOUT_DROPDOWN);
}
}

View File

@ -0,0 +1,587 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Web\Navigation;
use ArrayAccess;
use ArrayIterator;
use Exception;
use Countable;
use InvalidArgumentException;
use IteratorAggregate;
use Traversable;
use Icinga\Application\Icinga;
use Icinga\Application\Logger;
use Icinga\Authentication\Auth;
use Icinga\Data\ConfigObject;
use Icinga\Exception\ConfigurationError;
use Icinga\Exception\IcingaException;
use Icinga\Exception\ProgrammingError;
use Icinga\Util\String;
use Icinga\Web\Navigation\Renderer\RecursiveNavigationRenderer;
/**
* Container for navigation items
*/
class Navigation implements ArrayAccess, Countable, IteratorAggregate
{
/**
* The class namespace where to locate navigation type classes
*
* @var string
*/
const NAVIGATION_NS = 'Web\\Navigation';
/**
* Flag for dropdown layout
*
* @var int
*/
const LAYOUT_DROPDOWN = 1;
/**
* Flag for tabs layout
*
* @var int
*/
const LAYOUT_TABS = 2;
/**
* Known navigation types
*
* @var array
*/
protected static $types;
/**
* This navigation's items
*
* @var NavigationItem[]
*/
protected $items = array();
/**
* This navigation's layout
*
* @var int
*/
protected $layout;
/**
* {@inheritdoc}
*/
public function offsetExists($offset)
{
return isset($this->items[$offset]);
}
/**
* {@inheritdoc}
*/
public function offsetGet($offset)
{
return isset($this->items[$offset]) ? $this->items[$offset] : null;
}
/**
* {@inheritdoc}
*/
public function offsetSet($offset, $value)
{
$this->items[$offset] = $value;
}
/**
* {@inheritdoc}
*/
public function offsetUnset($offset)
{
unset($this->items[$offset]);
}
/**
* {@inheritdoc}
*/
public function count()
{
return count($this->items);
}
/**
* {@inheritdoc}
*/
public function getIterator()
{
$this->order();
return new ArrayIterator($this->items);
}
/**
* Create and return a new navigation item for the given configuration
*
* @param string $name
* @param array|ConfigObject $properties
*
* @return NavigationItem
*
* @throws InvalidArgumentException If the $properties argument is neither an array nor a ConfigObject
*/
public function createItem($name, $properties)
{
if ($properties instanceof ConfigObject) {
$properties = $properties->toArray();
} elseif (! is_array($properties)) {
throw new InvalidArgumentException('Argument $properties must be of type array or ConfigObject');
}
$itemType = isset($properties['type']) ? String::cname($properties['type'], '-') : 'NavigationItem';
if (! empty(static::$types) && isset(static::$types[$itemType])) {
return new static::$types[$itemType]($name, $properties);
}
$item = null;
foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) {
$classPath = 'Icinga\\Module\\'
. ucfirst($module->getName())
. '\\'
. static::NAVIGATION_NS
. '\\'
. $itemType;
if (class_exists($classPath)) {
$item = new $classPath($name, $properties);
break;
}
}
if ($item === null) {
$classPath = 'Icinga\\' . static::NAVIGATION_NS . '\\' . $itemType;
if (class_exists($classPath)) {
$item = new $classPath($name, $properties);
}
}
if ($item === null) {
Logger::debug(
'Failed to find custom navigation item class %s for item %s. Using base class NavigationItem now',
$itemType,
$name
);
$item = new NavigationItem($name, $properties);
static::$types[$itemType] = 'Icinga\\Web\\Navigation\\NavigationItem';
} elseif (! $item instanceof NavigationItem) {
throw new ProgrammingError('Class %s must inherit from NavigationItem', $classPath);
} else {
static::$types[$itemType] = $classPath;
}
return $item;
}
/**
* Add a navigation item
*
* If you do not pass an instance of NavigationItem, this will only add the item
* if it does not require a permission or the current user has the permission.
*
* @param string|NavigationItem $name The name of the item or an instance of NavigationItem
* @param array $properties The properties of the item to add (Ignored if $name is not a string)
*
* @return bool Whether the item was added or not
*
* @throws InvalidArgumentException In case $name is neither a string nor an instance of NavigationItem
*/
public function addItem($name, array $properties = array())
{
if (is_string($name)) {
if (isset($properties['permission'])) {
if (! Auth::getInstance()->hasPermission($properties['permission'])) {
return false;
}
unset($properties['permission']);
}
$item = $this->createItem($name, $properties);
} elseif (! $name instanceof NavigationItem) {
throw new InvalidArgumentException('Argument $name must be of type string or NavigationItem');
} else {
$item = $name;
}
$this->items[$item->getName()] = $item;
return true;
}
/**
* Return the item with the given name
*
* @param string $name
* @param mixed $default
*
* @return NavigationItem|mixed
*/
public function getItem($name, $default = null)
{
return isset($this->items[$name]) ? $this->items[$name] : $default;
}
/**
* Return the currently active item or the first one if none is active
*
* @return NavigationItem
*/
public function getActiveItem()
{
foreach ($this->items as $item) {
if ($item->getActive()) {
return $item;
}
}
$firstItem = reset($this->items);
return $firstItem ? $firstItem->setActive() : null;
}
/**
* Return this navigation's items
*
* @return array
*/
public function getItems()
{
return $this->items;
}
/**
* Return whether this navigation is empty
*
* @return bool
*/
public function isEmpty()
{
return empty($this->items);
}
/**
* Return whether this navigation has any renderable items
*
* @return bool
*/
public function hasRenderableItems()
{
foreach ($this->getItems() as $item) {
if ($item->shouldRender()) {
return true;
}
}
return false;
}
/**
* Return this navigation's layout
*
* @return int
*/
public function getLayout()
{
return $this->layout;
}
/**
* Set this navigation's layout
*
* @param int $layout
*
* @return $this
*/
public function setLayout($layout)
{
$this->layout = (int) $layout;
return $this;
}
/**
* Create and return the renderer for this navigation
*
* @return RecursiveNavigationRenderer
*/
public function getRenderer()
{
return new RecursiveNavigationRenderer($this);
}
/**
* Return this navigation rendered to HTML
*
* @return string
*/
public function render()
{
return $this->getRenderer()->render();
}
/**
* Order this navigation's items
*
* @return $this
*/
public function order()
{
uasort($this->items, array($this, 'compareItems'));
foreach ($this->items as $item) {
if ($item->hasChildren()) {
$item->getChildren()->order();
}
}
return $this;
}
/**
* Return whether the first item is less than, more than or equal to the second one
*
* @param NavigationItem $a
* @param NavigationItem $b
*
* @return int
*/
protected function compareItems(NavigationItem $a, NavigationItem $b)
{
if ($a->getPriority() === $b->getPriority()) {
return strcasecmp($a->getLabel(), $b->getLabel());
}
return $a->getPriority() > $b->getPriority() ? 1 : -1;
}
/**
* Try to find and return a item with the given or a similar name
*
* @param string $name
*
* @return NavigationItem
*/
protected function findItem($name)
{
$item = $this->getItem($name);
if ($item !== null) {
return $item;
}
$loweredName = strtolower($name);
foreach ($this->getItems() as $item) {
if (strtolower($item->getName()) === $loweredName) {
return $item;
}
}
}
/**
* Merge this navigation with the given one
*
* Any duplicate items of this navigation will be overwritten by the given navigation's items.
*
* @param Navigation $navigation
*
* @return $this
*/
public function merge(Navigation $navigation)
{
foreach ($navigation as $item) {
/** @var $item NavigationItem */
if (($existingItem = $this->findItem($item->getName())) !== null) {
if ($existingItem->conflictsWith($item)) {
$name = $item->getName();
do {
if (preg_match('~_(\d+)$~', $name, $matches)) {
$name = preg_replace('~_\d+$~', $matches[1] + 1, $name);
} else {
$name .= '_2';
}
} while ($this->getItem($name) !== null);
$this->addItem($item->setName($name));
} else {
$existingItem->merge($item);
}
} else {
$this->addItem($item);
}
}
return $this;
}
/**
* Extend this navigation set with all additional items of the given type
*
* This will fetch navigation items from the following sources:
* * User Shareables
* * User Preferences
* * Modules
* Any existing entry will be overwritten by one that is coming later in order.
*
* @param string $type
*
* @return $this
*/
public function load($type)
{
$user = Auth::getInstance()->getUser();
if ($type !== 'dashboard-pane') {
// Shareables
$this->merge(Icinga::app()->getSharedNavigation($type));
// User Preferences
$this->merge($user->getNavigation($type));
}
// Modules
$moduleManager = Icinga::app()->getModuleManager();
foreach ($moduleManager->getLoadedModules() as $module) {
if ($user->can($moduleManager::MODULE_PERMISSION_NS . $module->getName())) {
if ($type === 'menu-item') {
$this->merge($module->getMenu());
} elseif ($type === 'dashboard-pane') {
$this->merge($module->getDashboard());
}
}
}
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
*
* Note that this is supposed to be utilized for one dimensional structures
* only. Multi dimensional structures can be processed by fromArray().
*
* @param Traversable|array $config
*
* @return Navigation
*
* @throws InvalidArgumentException In case the given configuration is invalid
* @throws ConfigurationError In case a referenced parent does not exist
*/
public static function fromConfig($config)
{
if (! is_array($config) && !$config instanceof Traversable) {
throw new InvalidArgumentException('Argument $config must be an array or a instance of Traversable');
}
$flattened = $orphans = $topLevel = array();
foreach ($config as $sectionName => $sectionConfig) {
$parentName = $sectionConfig->parent;
unset($sectionConfig->parent);
if (! $parentName) {
$topLevel[$sectionName] = $sectionConfig->toArray();
$flattened[$sectionName] = & $topLevel[$sectionName];
} elseif (isset($flattened[$parentName])) {
$flattened[$parentName]['children'][$sectionName] = $sectionConfig->toArray();
$flattened[$sectionName] = & $flattened[$parentName]['children'][$sectionName];
} else {
$orphans[$parentName][$sectionName] = $sectionConfig->toArray();
$flattened[$sectionName] = & $orphans[$parentName][$sectionName];
}
}
do {
$match = false;
foreach ($orphans as $parentName => $children) {
if (isset($flattened[$parentName])) {
if (isset($flattened[$parentName]['children'])) {
$flattened[$parentName]['children'] = array_merge(
$flattened[$parentName]['children'],
$children
);
} else {
$flattened[$parentName]['children'] = $children;
}
unset($orphans[$parentName]);
$match = true;
}
}
} while ($match && !empty($orphans));
if (! empty($orphans)) {
throw new ConfigurationError(
t(
'Failed to fully parse navigation configuration. Ensure that'
. ' all referenced parents are existing navigation items: %s'
),
join(', ', array_keys($orphans))
);
}
return static::fromArray($topLevel);
}
/**
* Create and return a new set of navigation items for the given array
*
* @param array $array
*
* @return Navigation
*/
public static function fromArray(array $array)
{
$navigation = new static();
foreach ($array as $name => $properties) {
$navigation->addItem($name, $properties);
}
return $navigation;
}
/**
* Return this navigation rendered to HTML
*
* @return string
*/
public function __toString()
{
try {
return $this->render();
} catch (Exception $e) {
return IcingaException::describe($e);
}
}
}

View File

@ -0,0 +1,791 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Web\Navigation;
use Exception;
use InvalidArgumentException;
use IteratorAggregate;
use Icinga\Application\Icinga;
use Icinga\Exception\IcingaException;
use Icinga\Exception\ProgrammingError;
use Icinga\Web\Navigation\Renderer\NavigationItemRenderer;
use Icinga\Web\Url;
/**
* A navigation item
*/
class NavigationItem implements IteratorAggregate
{
/**
* Alternative markup element for items without a url
*
* @var string
*/
const LINK_ALTERNATIVE = 'span';
/**
* The class namespace where to locate navigation type renderer classes
*/
const RENDERER_NS = 'Web\\Navigation\\Renderer';
/**
* Whether this item is active
*
* @var bool
*/
protected $active;
/**
* This item's priority
*
* The priority defines when the item is rendered in relation to its parent's childs.
*
* @var int
*/
protected $priority;
/**
* The attributes of this item's element
*
* @var array
*/
protected $attributes;
/**
* This item's children
*
* @var Navigation
*/
protected $children;
/**
* This item's icon
*
* @var string
*/
protected $icon;
/**
* This item's name
*
* @var string
*/
protected $name;
/**
* This item's label
*
* @var string
*/
protected $label;
/**
* This item's parent
*
* @var NavigationItem
*/
protected $parent;
/**
* This item's url
*
* @var Url
*/
protected $url;
/**
* This item's url target
*
* @var string
*/
protected $target;
/**
* Additional parameters for this item's url
*
* @var array
*/
protected $urlParameters;
/**
* This item's renderer
*
* @var NavigationItemRenderer
*/
protected $renderer;
/**
* Whether to render this item
*
* @var bool
*/
protected $render;
/**
* Create a new NavigationItem
*
* @param string $name
* @param array $properties
*/
public function __construct($name, array $properties = null)
{
$this->setName($name);
$this->priority = 100;
$this->children = new Navigation();
if (! empty($properties)) {
$this->setProperties($properties);
}
$this->init();
}
/**
* Initialize this NavigationItem
*/
public function init()
{
}
/**
* {@inheritdoc}
*/
public function getIterator()
{
return $this->getChildren();
}
/**
* Return whether this item is active
*
* @return bool
*/
public function getActive()
{
if ($this->active === null) {
$this->active = false;
if ($this->getUrl() !== null && Icinga::app()->getRequest()->getUrl()->matches($this->getUrl())) {
$this->setActive();
} elseif ($this->hasChildren()) {
foreach ($this->getChildren() as $item) {
/** @var NavigationItem $item */
if ($item->getActive()) {
// Do nothing, a true active state is automatically passed to all parents
}
}
}
}
return $this->active;
}
/**
* Set whether this item is active
*
* If it's active and has a parent, the parent gets activated as well.
*
* @param bool $active
*
* @return $this
*/
public function setActive($active = true)
{
$this->active = (bool) $active;
if ($this->active && $this->getParent() !== null) {
$this->getParent()->setActive();
}
return $this;
}
/**
* Return this item's priority
*
* @return int
*/
public function getPriority()
{
return $this->priority;
}
/**
* Set this item's priority
*
* @param int $priority
*
* @return $this
*/
public function setPriority($priority)
{
$this->priority = (int) $priority;
return $this;
}
/**
* Return the value of the given element attribute
*
* @param string $name
* @param mixed $default
*
* @return mixed
*/
public function getAttribute($name, $default = null)
{
$attributes = $this->getAttributes();
return array_key_exists($name, $attributes) ? $attributes[$name] : $default;
}
/**
* Set the value of the given element attribute
*
* @param string $name
* @param mixed $value
*
* @return $this
*/
public function setAttribute($name, $value)
{
$this->attributes[$name] = $value;
return $this;
}
/**
* Return the attributes of this item's element
*
* @return array
*/
public function getAttributes()
{
return $this->attributes ?: array();
}
/**
* Set the attributes of this item's element
*
* @param array $attributes
*
* @return $this
*/
public function setAttributes(array $attributes)
{
$this->attributes = $attributes;
return $this;
}
/**
* Add a child to this item
*
* If the child is active this item gets activated as well.
*
* @param NavigationItem $child
*
* @return $this
*/
public function addChild(NavigationItem $child)
{
$this->getChildren()->addItem($child->setParent($this));
if ($child->getActive()) {
$this->setActive();
}
return $this;
}
/**
* Return this item's children
*
* @return Navigation
*/
public function getChildren()
{
return $this->children;
}
/**
* Return whether this item has any children
*
* @return bool
*/
public function hasChildren()
{
return !$this->getChildren()->isEmpty();
}
/**
* Set this item's children
*
* @param array|Navigation $children
*
* @return $this
*/
public function setChildren($children)
{
if (is_array($children)) {
$children = Navigation::fromArray($children);
} elseif (! $children instanceof Navigation) {
throw new InvalidArgumentException('Argument $children must be of type array or Navigation');
}
foreach ($children as $item) {
$item->setParent($this);
}
$this->children = $children;
return $this;
}
/**
* Return this item's icon
*
* @return string
*/
public function getIcon()
{
return $this->icon;
}
/**
* Set this item's icon
*
* @param string $icon
*
* @return $this
*/
public function setIcon($icon)
{
$this->icon = $icon;
return $this;
}
/**
* Return this item's name escaped with only ASCII chars and/or digits
*
* @return string
*/
protected function getEscapedName()
{
return preg_replace('~[^a-zA-Z0-9]~', '_', $this->getName());
}
/**
* Return a unique version of this item's name
*
* @return string
*/
public function getUniqueName()
{
if ($this->getParent() === null) {
return 'navigation-' . $this->getEscapedName();
}
return $this->getParent()->getUniqueName() . '-' . $this->getEscapedName();
}
/**
* Return this item's name
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Set this item's name
*
* @param string $name
*
* @return $this
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Set this item's parent
*
* @param NavigationItem $parent
*
* @return $this
*/
public function setParent(NavigationItem $parent)
{
$this->parent = $parent;
return $this;
}
/**
* Return this item's parent
*
* @return NavigationItem
*/
public function getParent()
{
return $this->parent;
}
/**
* Return this item's label
*
* @return string
*/
public function getLabel()
{
return $this->label ?: $this->getName();
}
/**
* Set this item's label
*
* @param string $label
*
* @return $this
*/
public function setLabel($label)
{
$this->label = $label;
return $this;
}
/**
* Set this item's url target
*
* @param string $target
*
* @return $this
*/
public function setTarget($target)
{
$this->target = $target;
return $this;
}
/**
* Return this item's url target
*
* @return string
*/
public function getTarget()
{
return $this->target;
}
/**
* Return this item's url
*
* @return Url
*/
public function getUrl()
{
if ($this->url === null && $this->hasChildren()) {
$this->setUrl(Url::fromPath('#'));
}
return $this->url;
}
/**
* Set this item's url
*
* @param Url|string $url
*
* @return $this
*
* @throws InvalidArgumentException If the given url is neither of type
*/
public function setUrl($url)
{
if (is_string($url)) {
$url = Url::fromPath($url);
} elseif (! $url instanceof Url) {
throw new InvalidArgumentException('Argument $url must be of type string or Url');
}
$this->url = $url;
return $this;
}
/**
* Return the value of the given url parameter
*
* @param string $name
* @param mixed $default
*
* @return mixed
*/
public function getUrlParameter($name, $default = null)
{
$parameters = $this->getUrlParameters();
return isset($parameters[$name]) ? $parameters[$name] : $default;
}
/**
* Set the value of the given url parameter
*
* @param string $name
* @param mixed $value
*
* @return $this
*/
public function setUrlParameter($name, $value)
{
$this->urlParameters[$name] = $value;
return $this;
}
/**
* Return all additional parameters for this item's url
*
* @return array
*/
public function getUrlParameters()
{
return $this->urlParameters ?: array();
}
/**
* Set additional parameters for this item's url
*
* @param array $urlParameters
*
* @return $this
*/
public function setUrlParameters(array $urlParameters)
{
$this->urlParameters = $urlParameters;
return $this;
}
/**
* Set this item's properties
*
* Unknown properties (no matching setter) are considered as element attributes.
*
* @param array $properties
*
* @return $this
*/
public function setProperties(array $properties)
{
foreach ($properties as $name => $value) {
$setter = 'set' . ucfirst($name);
if (method_exists($this, $setter)) {
$this->$setter($value);
} else {
$this->setAttribute($name, $value);
}
}
return $this;
}
/**
* Merge this item with the given one
*
* @param NavigationItem $item
*
* @return $this
*/
public function merge(NavigationItem $item)
{
if ($this->conflictsWith($item)) {
throw new ProgrammingError('Cannot merge, conflict detected.');
}
if ($item->getActive()) {
$this->setActive();
}
if (! $this->getIcon()) {
$this->setIcon($item->getIcon());
}
if ($this->getLabel() === $this->getName()) {
$this->setLabel($item->getLabel());
}
foreach ($item->getAttributes() as $name => $value) {
$this->setAttribute($name, $value);
}
foreach ($item->getUrlParameters() as $name => $value) {
$this->setUrlParameter($name, $value);
}
if ($item->hasChildren()) {
$this->getChildren()->merge($item->getChildren());
}
return $this;
}
/**
* Return whether it's possible to merge this item with the given one
*
* @param NavigationItem $item
*
* @return bool
*/
public function conflictsWith(NavigationItem $item)
{
if (! $item instanceof $this) {
return true;
}
if ($this->getUrl() === null || $item->getUrl() === null) {
return false;
}
return !$this->getUrl()->matches($item->getUrl());
}
/**
* Create and return the given renderer
*
* @param string|array $name
*
* @return NavigationItemRenderer
*/
protected function createRenderer($name)
{
if (is_array($name)) {
$options = array_splice($name, 1);
$name = $name[0];
} else {
$options = array();
}
$renderer = null;
foreach (Icinga::app()->getModuleManager()->getLoadedModules() as $module) {
$classPath = 'Icinga\\Module\\' . ucfirst($module->getName()) . '\\' . static::RENDERER_NS . '\\' . $name;
if (class_exists($classPath)) {
$renderer = new $classPath($options);
break;
}
}
if ($renderer === null) {
$classPath = 'Icinga\\' . static::RENDERER_NS . '\\' . $name;
if (class_exists($classPath)) {
$renderer = new $classPath($options);
}
}
if ($renderer === null) {
throw new ProgrammingError(
'Cannot find renderer "%s" for navigation item "%s"',
$name,
$this->getName()
);
} elseif (! $renderer instanceof NavigationItemRenderer) {
throw new ProgrammingError('Class %s must inherit from NavigationItemRenderer', $classPath);
}
return $renderer;
}
/**
* Set this item's renderer
*
* @param string|array|NavigationItemRenderer $renderer
*
* @return $this
*
* @throws InvalidArgumentException If the $renderer argument is neither a string nor a NavigationItemRenderer
*/
public function setRenderer($renderer)
{
if (is_string($renderer) || is_array($renderer)) {
$renderer = $this->createRenderer($renderer);
} elseif (! $renderer instanceof NavigationItemRenderer) {
throw new InvalidArgumentException(
'Argument $renderer must be of type string, array or NavigationItemRenderer'
);
}
$this->renderer = $renderer;
return $this;
}
/**
* Return this item's renderer
*
* @return NavigationItemRenderer
*/
public function getRenderer()
{
if ($this->renderer === null) {
$this->setRenderer('NavigationItemRenderer');
}
return $this->renderer;
}
/**
* Set whether this item should be rendered
*
* @param bool $state
*
* @return $this
*/
public function setRender($state = true)
{
$this->render = (bool) $state;
return $this;
}
/**
* Return whether this item should be rendered
*
* @return bool
*/
public function getRender()
{
if ($this->render === null) {
return true;
}
return $this->render;
}
/**
* Return whether this item should be rendered
*
* Alias for NavigationItem::getRender().
*
* @return bool
*/
public function shouldRender()
{
return $this->getRender();
}
/**
* Return this item rendered to HTML
*
* @return string
*/
public function render()
{
return $this->getRenderer()->setItem($this)->render();
}
/**
* Return this item rendered to HTML
*
* @return string
*/
public function __toString()
{
try {
return $this->render();
} catch (Exception $e) {
return IcingaException::describe($e);
}
}
}

View File

@ -0,0 +1,118 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Web\Navigation\Renderer;
use Icinga\Web\Navigation\NavigationItem;
/**
* Abstract base class for a NavigationItem with a status badge
*/
abstract class BadgeNavigationItemRenderer extends NavigationItemRenderer
{
const STATE_OK = 'ok';
const STATE_CRITICAL = 'critical';
const STATE_WARNING = 'warning';
const STATE_PENDING = 'pending';
const STATE_UNKNOWN = 'unknown';
/**
* The tooltip text for the badge
*
* @var string
*/
protected $title;
/**
* The state identifier being used
*
* The state identifier defines the background color of the badge.
*
* @var string
*/
protected $state;
/**
* Set the tooltip text for the badge
*
* @param string $title
*
* @return $this
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Return the tooltip text for the badge
*
* @return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set the state identifier to use
*
* @param string $state
*
* @return $this
*/
public function setState($state)
{
$this->state = $state;
return $this;
}
/**
* Return the state identifier to use
*
* @return string
*/
public function getState()
{
return $this->state;
}
/**
* Return the amount of items represented by the badge
*
* @return int
*/
abstract public function getCount();
/**
* Render the given navigation item as HTML anchor with a badge
*
* @param NavigationItem $item
*
* @return string
*/
public function render(NavigationItem $item = null)
{
return $this->renderBadge() . parent::render($item);
}
/**
* Render the badge
*
* @return string
*/
protected function renderBadge()
{
if (($count = $this->getCount()) > 0) {
return sprintf(
'<div title="%s" class="badge-container"><span class="badge badge-%s">%s</span></div>',
$this->view()->escape($this->getTitle()),
$this->view()->escape($this->getState()),
$count
);
}
return '';
}
}

View File

@ -0,0 +1,229 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Web\Navigation\Renderer;
use Icinga\Application\Icinga;
use Icinga\Exception\ProgrammingError;
use Icinga\Util\String;
use Icinga\Web\Navigation\NavigationItem;
use Icinga\Web\Url;
use Icinga\Web\View;
/**
* NavigationItemRenderer
*/
class NavigationItemRenderer
{
/**
* View
*
* @var View
*/
protected $view;
/**
* The item being rendered
*
* @var NavigationItem
*/
protected $item;
/**
* Internal link targets provided by Icinga Web 2
*
* @var array
*/
protected $internalLinkTargets;
/**
* Whether to escape the label
*
* @var bool
*/
protected $escapeLabel;
/**
* Create a new NavigationItemRenderer
*
* @param array $options
*/
public function __construct(array $options = null)
{
if (! empty($options)) {
$this->setOptions($options);
}
$this->internalLinkTargets = array('_main', '_self', '_next');
$this->init();
}
/**
* Initialize this renderer
*/
public function init()
{
}
/**
* Set the given options
*
* @param array $options
*
* @return $this
*/
public function setOptions(array $options)
{
foreach ($options as $name => $value) {
$setter = 'set' . String::cname($name);
if (method_exists($this, $setter)) {
$this->$setter($value);
}
}
}
/**
* Set the view
*
* @param View $view
*
* @return $this
*/
public function setView(View $view)
{
$this->view = $view;
return $this;
}
/**
* Return the view
*
* @return View
*/
public function view()
{
if ($this->view === null) {
$this->setView(Icinga::app()->getViewRenderer()->view);
}
return $this->view;
}
/**
* Set the navigation item to render
*
* @param NavigationItem $item
*
* @return $this
*/
public function setItem(NavigationItem $item)
{
$this->item = $item;
return $this;
}
/**
* Return the navigation item being rendered
*
* @return NavigationItem
*/
public function getItem()
{
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
*
* @param NavigationItem $item
*
* @return string
*/
public function render(NavigationItem $item = null)
{
if ($item !== null) {
$this->setItem($item);
} elseif (($item = $this->getItem()) === null) {
throw new ProgrammingError(
'Cannot render nothing. Pass the item to render as part'
. ' of the call to render() or set it with setItem()'
);
}
$label = $this->getEscapeLabel()
? $this->view()->escape($item->getLabel())
: $item->getLabel();
if (($icon = $item->getIcon()) !== null) {
$label = $this->view()->icon($icon) . $label;
}
if (($url = $item->getUrl()) !== null) {
$url->overwriteParams($item->getUrlParameters());
$target = $item->getTarget();
if ($url->isExternal() && (!$target || in_array($target, $this->internalLinkTargets, true))) {
$url = Url::fromPath('iframe', array('url' => $url));
}
$content = sprintf(
'<a%s href="%s"%s>%s</a>',
$this->view()->propertiesToString($item->getAttributes()),
$url,
$this->renderTargetAttribute(),
$label
);
} else {
$content = sprintf(
'<%1$s%2$s>%3$s</%1$s>',
$item::LINK_ALTERNATIVE,
$this->view()->propertiesToString($item->getAttributes()),
$label
);
}
return $content;
}
/**
* Render and return the attribute to provide a non-default target for the url
*
* @return string
*/
protected function renderTargetAttribute()
{
$target = $this->getItem()->getTarget();
if ($target === null) {
return '';
}
if (! in_array($target, $this->internalLinkTargets, true)) {
return ' target="' . $this->view()->escape($target) . '"';
}
return ' data-base-target="' . $target . '"';
}
}

View File

@ -0,0 +1,367 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Web\Navigation\Renderer;
use ArrayIterator;
use Exception;
use RecursiveIterator;
use Icinga\Application\Icinga;
use Icinga\Exception\IcingaException;
use Icinga\Web\Navigation\Navigation;
use Icinga\Web\Navigation\NavigationItem;
use Icinga\Web\View;
/**
* Renderer for single level navigation
*/
class NavigationRenderer implements RecursiveIterator, NavigationRendererInterface
{
/**
* The tag used for the outer element
*
* @var string
*/
protected $elementTag;
/**
* The CSS class used for the outer element
*
* @var string
*/
protected $cssClass;
/**
* The navigation's heading text
*
* @var string
*/
protected $heading;
/**
* The content rendered so far
*
* @var array
*/
protected $content;
/**
* Whether to skip rendering the outer element
*
* @var bool
*/
protected $skipOuterElement;
/**
* The navigation's iterator
*
* @var ArrayIterator
*/
protected $iterator;
/**
* The navigation
*
* @var Navigation
*/
protected $navigation;
/**
* View
*
* @var View
*/
protected $view;
/**
* Create a new NavigationRenderer
*
* @param Navigation $navigation
* @param bool $skipOuterElement
*/
public function __construct(Navigation $navigation, $skipOuterElement = false)
{
$this->skipOuterElement = $skipOuterElement;
$this->iterator = $navigation->getIterator();
$this->navigation = $navigation;
$this->content = array();
}
/**
* {@inheritdoc}
*/
public function setElementTag($tag)
{
$this->elementTag = $tag;
return $this;
}
/**
* {@inheritdoc}
*/
public function getElementTag()
{
return $this->elementTag ?: static::OUTER_ELEMENT_TAG;
}
/**
* {@inheritdoc}
*/
public function setCssClass($class)
{
$this->cssClass = $class;
return $this;
}
/**
* {@inheritdoc}
*/
public function getCssClass()
{
return $this->cssClass;
}
/**
* {@inheritdoc}
*/
public function setHeading($heading)
{
$this->heading = $heading;
return $this;
}
/**
* {@inheritdoc}
*/
public function getHeading()
{
return $this->heading;
}
/**
* Return the view
*
* @return View
*/
public function view()
{
if ($this->view === null) {
$this->setView(Icinga::app()->getViewRenderer()->view);
}
return $this->view;
}
/**
* Set the view
*
* @param View $view
*
* @return $this
*/
public function setView(View $view)
{
$this->view = $view;
return $this;
}
/**
* {@inheritdoc}
*/
public function getChildren()
{
return new static($this->current()->getChildren(), $this->skipOuterElement);
}
/**
* {@inheritdoc}
*/
public function hasChildren()
{
return $this->current()->hasChildren();
}
/**
* {@inheritdoc}
*
* @return NavigationItem
*/
public function current()
{
return $this->iterator->current();
}
/**
* {@inheritdoc}
*/
public function key()
{
return $this->iterator->key();
}
/**
* {@inheritdoc}
*/
public function next()
{
$this->iterator->next();
}
/**
* {@inheritdoc}
*/
public function rewind()
{
$this->iterator->rewind();
if (! $this->skipOuterElement) {
$this->content[] = $this->beginMarkup();
}
}
/**
* {@inheritdoc}
*/
public function valid()
{
$valid = $this->iterator->valid();
if (! $this->skipOuterElement && !$valid) {
$this->content[] = $this->endMarkup();
}
return $valid;
}
/**
* Return the opening markup for the navigation
*
* @return string
*/
public function beginMarkup()
{
$content = array();
$content[] = sprintf(
'<%s%s role="navigation">',
$this->getElementTag(),
$this->getCssClass() !== null ? ' class="' . $this->getCssClass() . '"' : ''
);
if (($heading = $this->getHeading()) !== null) {
$content[] = sprintf(
'<h%1$d id="navigation" class="sr-only" tabindex="-1">%2$s</h%1$d>',
static::HEADING_RANK,
$this->view()->escape($heading)
);
}
$content[] = $this->beginChildrenMarkup();
return join("\n", $content);
}
/**
* Return the closing markup for the navigation
*
* @return string
*/
public function endMarkup()
{
$content = array();
$content[] = $this->endChildrenMarkup();
$content[] = '</' . $this->getElementTag() . '>';
return join("\n", $content);
}
/**
* Return the opening markup for multiple navigation items
*
* @return string
*/
public function beginChildrenMarkup()
{
$cssClass = array(static::CSS_CLASS_NAV);
if ($this->navigation->getLayout() === Navigation::LAYOUT_TABS) {
$cssClass[] = static::CSS_CLASS_NAV_TABS;
} elseif ($this->navigation->getLayout() === Navigation::LAYOUT_DROPDOWN) {
$cssClass[] = static::CSS_CLASS_NAV_DROPDOWN;
}
return '<ul class="' . join(' ', $cssClass) . '">';
}
/**
* Return the closing markup for multiple navigation items
*
* @return string
*/
public function endChildrenMarkup()
{
return '</ul>';
}
/**
* Return the opening markup for the given navigation item
*
* @param NavigationItem $item
*
* @return string
*/
public function beginItemMarkup(NavigationItem $item)
{
$cssClass = array(static::CSS_CLASS_ITEM);
if ($item->hasChildren() && $item->getChildren()->getLayout() === Navigation::LAYOUT_DROPDOWN) {
$cssClass[] = static::CSS_CLASS_DROPDOWN;
$item
->setAttribute('class', static::CSS_CLASS_DROPDOWN_TOGGLE)
->setIcon(static::DROPDOWN_TOGGLE_ICON)
->setUrl('#');
}
if ($item->getActive()) {
$cssClass[] = static::CSS_CLASS_ACTIVE;
}
$content = sprintf(
'<li id="%s" class="%s">',
$this->view()->escape($item->getUniqueName()),
join(' ', $cssClass)
);
return $content;
}
/**
* Return the closing markup for a navigation item
*
* @return string
*/
public function endItemMarkup()
{
return '</li>';
}
/**
* {@inheritdoc}
*/
public function render()
{
foreach ($this as $item) {
/** @var NavigationItem $item */
if ($item->shouldRender()) {
$this->content[] = $this->beginItemMarkup($item);
$this->content[] = $item->render();
$this->content[] = $this->endItemMarkup();
}
}
return join("\n", $this->content);
}
/**
* {@inheritdoc}
*/
public function __toString()
{
try {
return $this->render();
} catch (Exception $e) {
return IcingaException::describe($e);
}
}
}

View File

@ -0,0 +1,135 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Web\Navigation\Renderer;
/**
* Interface for navigation renderers
*/
interface NavigationRendererInterface
{
/**
* CSS class for items
*
* @var string
*/
const CSS_CLASS_ITEM = 'nav-item';
/**
* CSS class for active items
*
* @var string
*/
const CSS_CLASS_ACTIVE = 'active';
/**
* CSS class for dropdown items
*
* @var string
*/
const CSS_CLASS_DROPDOWN = 'dropdown-nav-item';
/**
* CSS class for a dropdown item's trigger
*
* @var string
*/
const CSS_CLASS_DROPDOWN_TOGGLE = 'dropdown-toggle';
/**
* CSS class for the ul element
*
* @var string
*/
const CSS_CLASS_NAV = 'nav';
/**
* CSS class for the ul element with dropdown layout
*
* @var string
*/
const CSS_CLASS_NAV_DROPDOWN = 'dropdown-nav';
/**
* CSS class for the ul element with tabs layout
*
* @var string
*/
const CSS_CLASS_NAV_TABS = 'tab-nav';
/**
* Icon for a dropdown item's trigger
*
* @var string
*/
const DROPDOWN_TOGGLE_ICON = 'menu';
/**
* Default tag for the outer element the navigation will be wrapped with
*
* @var string
*/
const OUTER_ELEMENT_TAG = 'div';
/**
* The heading's rank
*
* @var int
*/
const HEADING_RANK = 1;
/**
* Set the tag for the outer element the navigation is wrapped with
*
* @param string $tag
*
* @return $this
*/
public function setElementTag($tag);
/**
* Return the tag for the outer element the navigation is wrapped with
*
* @return string
*/
public function getElementTag();
/**
* Set the CSS class to use for the outer element
*
* @param string $class
*
* @return $this
*/
public function setCssClass($class);
/**
* Get the CSS class used for the outer element
*
* @return string
*/
public function getCssClass();
/**
* Set the navigation's heading text
*
* @param string $heading
*
* @return $this
*/
public function setHeading($heading);
/**
* Return the navigation's heading text
*
* @return string
*/
public function getHeading();
/**
* Return the navigation rendered to HTML
*
* @return string
*/
public function render();
}

View File

@ -0,0 +1,155 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Web\Navigation\Renderer;
use Exception;
use RecursiveIteratorIterator;
use Icinga\Exception\IcingaException;
use Icinga\Web\Navigation\Navigation;
use Icinga\Web\Navigation\NavigationItem;
/**
* Renderer for multi level navigation
*
* @method NavigationRenderer getInnerIterator() {
* {@inheritdoc}
* }
*/
class RecursiveNavigationRenderer extends RecursiveIteratorIterator implements NavigationRendererInterface
{
/**
* The content rendered so far
*
* @var array
*/
protected $content;
/**
* Create a new RecursiveNavigationRenderer
*
* @param Navigation $navigation
*/
public function __construct(Navigation $navigation)
{
$this->content = array();
parent::__construct(
new NavigationRenderer($navigation, true),
RecursiveIteratorIterator::SELF_FIRST
);
}
/**
* {@inheritdoc}
*/
public function setElementTag($tag)
{
$this->getInnerIterator()->setElementTag($tag);
return $this;
}
/**
* {@inheritdoc}
*/
public function getElementTag()
{
return $this->getInnerIterator()->getElementTag();
}
/**
* {@inheritdoc}
*/
public function setCssClass($class)
{
$this->getInnerIterator()->setCssClass($class);
return $this;
}
/**
* {@inheritdoc}
*/
public function getCssClass()
{
return $this->getInnerIterator()->getCssClass();
}
/**
* {@inheritdoc}
*/
public function setHeading($heading)
{
$this->getInnerIterator()->setHeading($heading);
return $this;
}
/**
* {@inheritdoc}
*/
public function getHeading()
{
return $this->getInnerIterator()->getHeading();
}
/**
* {@inheritdoc}
*/
public function beginIteration()
{
$this->content[] = $this->getInnerIterator()->beginMarkup();
}
/**
* {@inheritdoc}
*/
public function endIteration()
{
$this->content[] = $this->getInnerIterator()->endMarkup();
}
/**
* {@inheritdoc}
*/
public function beginChildren()
{
$this->content[] = $this->getInnerIterator()->beginChildrenMarkup();
}
/**
* {@inheritdoc}
*/
public function endChildren()
{
$this->content[] = $this->getInnerIterator()->endChildrenMarkup();
}
/**
* {@inheritdoc}
*/
public function render()
{
foreach ($this as $item) {
/** @var NavigationItem $item */
if ($item->shouldRender()) {
$this->content[] = $this->getInnerIterator()->beginItemMarkup($item);
$this->content[] = $item->render();
if (! $item->hasChildren()) {
$this->content[] = $this->getInnerIterator()->endItemMarkup();
}
}
}
return join("\n", $this->content);
}
/**
* {@inheritdoc}
*/
public function __toString()
{
try {
return $this->render();
} catch (Exception $e) {
return IcingaException::describe($e);
}
}
}

View File

@ -0,0 +1,46 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Web\Navigation\Renderer;
use Icinga\Web\Navigation\Renderer\BadgeNavigationItemRenderer;
/**
* Summary badge adding up all badges in the navigation's children that have the same state
*/
class SummaryNavigationItemRenderer extends BadgeNavigationItemRenderer
{
/**
* The title of each summarized child
*
* @var array
*/
protected $titles;
/**
* {@inheritdoc}
*/
public function getCount()
{
$count = 0;
foreach ($this->getItem()->getChildren() as $child) {
$renderer = $child->getRenderer();
if ($renderer instanceof BadgeNavigationItemRenderer) {
if ($renderer->getState() === $this->getState()) {
$this->titles[] = $renderer->getTitle();
$count += $renderer->getCount();
}
}
}
return $count;
}
/**
* {@inheritdoc}
*/
public function getTitle()
{
return !empty($this->titles) ? join(', ', $this->titles) : '';
}
}

View File

@ -28,6 +28,7 @@ class StyleSheet
'css/icinga/pagination.less',
'css/icinga/selection-toolbar.less',
'css/icinga/login.less',
'css/icinga/logo.less',
'css/icinga/controls.less'
);

View File

@ -13,12 +13,17 @@ use Icinga\Data\Filter\Filter;
* returns Urls reflecting all changes made to the url and to the parameters.
*
* Direct instantiation is prohibited and should be done either with @see Url::fromRequest() or
* @see Url::fromUrlString()
*
* Currently, protocol, host and port are ignored and will be implemented when required
* @see Url::fromPath()
*/
class Url
{
/**
* Whether this url points to an external resource
*
* @var bool
*/
protected $external;
/**
* An array of all parameters stored in this Url
*
@ -41,12 +46,11 @@ class Url
protected $path = '';
/**
* The baseUrl that will be appended to @see Url::$path in order to
* create an absolute Url
* The baseUrl that will be appended to @see Url::$path
*
* @var string
*/
protected $baseUrl = '/';
protected $baseUrl = '';
protected function __construct()
{
@ -71,7 +75,7 @@ class Url
}
$url = new Url();
$url->setPath($request->getPathInfo());
$url->setPath(ltrim($request->getPathInfo(), '/'));
// $urlParams = UrlParams::fromQueryString($request->getQuery());
if (isset($_SERVER['QUERY_STRING'])) {
@ -124,7 +128,7 @@ class Url
$request = self::getRequest();
}
if (!is_string($url)) {
if (! is_string($url)) {
throw new ProgrammingError(
'url "%s" is not a string',
$url
@ -132,17 +136,44 @@ class Url
}
$urlObject = new Url();
$baseUrl = $request->getBaseUrl();
$urlObject->setBaseUrl($baseUrl);
if ($url === '#') {
$urlObject->setPath($url);
return $urlObject;
}
$urlParts = parse_url($url);
if (isset($urlParts['path'])) {
if ($baseUrl !== '' && strpos($urlParts['path'], $baseUrl) === 0) {
$urlObject->setPath(substr($urlParts['path'], strlen($baseUrl)));
} else {
$urlObject->setPath($urlParts['path']);
}
if (isset($urlParts['scheme']) && (
$urlParts['scheme'] !== $request->getScheme()
|| (isset($urlParts['host']) && $urlParts['host'] !== $request->getServer('SERVER_NAME'))
|| (isset($urlParts['port']) && $urlParts['port'] != $request->getServer('SERVER_PORT')))
) {
$baseUrl = $urlParts['scheme'] . '://' . $urlParts['host'] . (isset($urlParts['port'])
? (':' . $urlParts['port'])
: '');
$urlObject->setIsExternal();
} else {
$baseUrl = '';
}
if (isset($urlParts['path'])) {
$urlPath = $urlParts['path'];
if ($urlPath && $urlPath[0] === '/') {
if ($baseUrl) {
$urlPath = substr($urlPath, 1);
} elseif (strpos($urlPath, $request->getBaseUrl()) === 0) {
$baseUrl = $request->getBaseUrl();
$urlPath = substr($urlPath, strlen($baseUrl) + 1);
}
} elseif (! $baseUrl) {
$baseUrl = $request->getBaseUrl();
}
$urlObject->setPath($urlPath);
} elseif (! $baseUrl) {
$baseUrl = $request->getBaseUrl();
}
// TODO: This has been used by former filter implementation, remove it:
if (isset($urlParts['query'])) {
$params = UrlParams::fromQueryString($urlParts['query'])->mergeValues($params);
@ -152,6 +183,7 @@ class Url
$urlObject->setAnchor($urlParts['fragment']);
}
$urlObject->setBaseUrl($baseUrl);
$urlObject->setParams($params);
return $urlObject;
}
@ -179,19 +211,13 @@ class Url
/**
* Overwrite the baseUrl
*
* If an empty Url is given '/' is used as the base
*
* @param string $baseUrl The url path to use as the Url Base
*
* @return $this
*/
public function setBaseUrl($baseUrl)
{
if (($baseUrl = rtrim($baseUrl, '/ ')) === '') {
$baseUrl = '/';
}
$this->baseUrl = $baseUrl;
$this->baseUrl = rtrim($baseUrl, '/ ');
return $this;
}
@ -214,7 +240,7 @@ class Url
*/
public function setPath($path)
{
$this->path = ltrim($path, '/');
$this->path = $path;
return $this;
}
@ -231,17 +257,61 @@ class Url
}
/**
* Return the relative url with query parameters as a string
* Set whether this url points to an external resource
*
* @param bool $state
*
* @return $this
*/
public function setIsExternal($state = true)
{
$this->external = (bool) $state;
return $this;
}
/**
* Return whether this url points to an external resource
*
* @return bool
*/
public function isExternal()
{
return $this->external;
}
/**
* Return the relative url
*
* @return string
*/
public function getRelativeUrl($separator = '&')
{
if ($this->params->isEmpty()) {
return $this->path . $this->anchor;
} else {
return $this->path . '?' . $this->params->toString($separator) . $this->anchor;
$path = $this->buildPathQueryAndFragment($separator);
if ($path && $path[0] === '/') {
return '';
}
return $path;
}
/**
* Return this url's path with its query parameters and fragment as string
*
* @return string
*/
protected function buildPathQueryAndFragment($querySeparator)
{
$anchor = $this->getAnchor();
if ($anchor) {
$anchor = '#' . $anchor;
}
$query = $this->getQueryString($querySeparator);
if ($query) {
$query = '?' . $query;
}
return $this->getPath() . $query . $anchor;
}
public function setQueryString($queryString)
@ -250,9 +320,9 @@ class Url
return $this;
}
public function getQueryString()
public function getQueryString($separator = null)
{
return (string) $this->params;
return $this->params->toString($separator);
}
/**
@ -262,7 +332,17 @@ class Url
*/
public function getAbsoluteUrl($separator = '&')
{
return $this->baseUrl . ($this->baseUrl !== '/' ? '/' : '') . $this->getRelativeUrl($separator);
$path = $this->buildPathQueryAndFragment($separator);
if ($path && ($path === '#' || $path[0] === '/')) {
return $path;
}
$baseUrl = $this->getBaseUrl();
if (! $baseUrl) {
$baseUrl = '/';
}
return $baseUrl . ($baseUrl !== '/' && $path ? '/' : '') . $path;
}
/**
@ -380,10 +460,20 @@ class Url
*/
public function setAnchor($anchor)
{
$this->anchor = '#' . $anchor;
$this->anchor = $anchor;
return $this;
}
/**
* Return the url anchor-part
*
* @return string The site's anchor string without the '#'
*/
public function getAnchor()
{
return $this->anchor;
}
/**
* Remove provided key (if string) or keys (if array of string) from the query parameter array
*

View File

@ -9,6 +9,8 @@ use Icinga\Exception\ConfigurationError;
use Icinga\Exception\NotReadableError;
use Icinga\Exception\ProgrammingError;
use Icinga\User;
use Icinga\Web\Navigation\DashboardPane;
use Icinga\Web\Navigation\Navigation;
use Icinga\Web\Widget\Dashboard\Pane;
use Icinga\Web\Widget\Dashboard\Dashlet as DashboardDashlet;
use Icinga\Web\Url;
@ -68,16 +70,22 @@ class Dashboard extends AbstractWidget
*/
public function load()
{
$manager = Icinga::app()->getModuleManager();
foreach ($manager->getLoadedModules() as $module) {
if ($this->getUser()->can($manager::MODULE_PERMISSION_NS . $module->getName())) {
$this->mergePanes($module->getPaneItems());
$navigation = new Navigation();
$navigation->load('dashboard-pane');
$panes = array();
foreach ($navigation as $dashboardPane) {
/** @var DashboardPane $dashboardPane */
$pane = new Pane($dashboardPane->getLabel());
foreach ($dashboardPane->getDashlets() as $title => $url) {
$pane->addDashlet($title, $url);
}
$panes[] = $pane;
}
$this->mergePanes($panes);
$this->loadUserDashboards();
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\Web\Url;
use Icinga\Web\Widget\Tabextension\DashboardAction;
use Icinga\Web\Widget\Tabextension\MenuAction;
class AlertsummaryController extends Controller
{
@ -53,7 +54,7 @@ class AlertsummaryController extends Controller
'label' => $this->translate('Alert Summary'),
'url' => Url::fromRequest()
)
)->extend(new DashboardAction())->activate('alertsummary');
)->extend(new DashboardAction())->extend(new MenuAction())->activate('alertsummary');
$this->view->title = $this->translate('Alert Summary');
$this->view->intervalBox = $this->createIntervalBox();

View File

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

View File

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

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\ToggleInstanceFeaturesCommandForm;
use Icinga\Web\Widget\Tabextension\DashboardAction;
use Icinga\Web\Widget\Tabextension\MenuAction;
/**
* Display process and performance information of the monitoring host and program-wide commands
@ -43,7 +44,7 @@ class HealthController extends Controller
'url' =>'monitoring/health/stats'
)
)
->extend(new DashboardAction());
->extend(new DashboardAction())->extend(new MenuAction());
}
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,75 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Module\Monitoring\Forms\Navigation;
use Icinga\Data\Filter\Filter;
use Icinga\Exception\QueryException;
use Icinga\Forms\Navigation\NavigationItemForm;
class ActionForm extends NavigationItemForm
{
/**
* {@inheritdoc}
*/
public function createElements(array $formData)
{
parent::createElements($formData);
$this->addElement(
'text',
'filter',
array(
'allowEmpty' => true,
'label' => $this->translate('Filter'),
'description' => $this->translate(
'Display this action only for objects matching this filter. Leave it blank'
. ' if you want this action being displayed regardless of the object'
)
)
);
}
/**
* {@inheritdoc}
*/
public function isValid($formData)
{
if (! parent::isValid($formData)) {
return false;
}
if (($filterString = $this->getValue('filter')) !== null) {
$filter = Filter::matchAll();
$filter->setAllowedFilterColumns(array(
'host_name',
'hostgroup_name',
'instance_name',
'service_description',
'servicegroup_name',
function ($c) {
return preg_match('/^_(?:host|service)_/', $c);
}
));
try {
$filter->addFilter(Filter::fromQueryString($filterString));
} catch (QueryException $_) {
$this->getElement('filter')->addError(sprintf(
$this->translate('Invalid filter provided. You can only use the following columns: %s'),
implode(', ', array(
'instance_name',
'host_name',
'hostgroup_name',
'service_description',
'servicegroup_name',
'_(host|service)_<customvar-name>'
))
));
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,8 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Module\Monitoring\Forms\Navigation;
class HostActionForm extends ActionForm
{
}

View File

@ -0,0 +1,8 @@
<?php
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
namespace Icinga\Module\Monitoring\Forms\Navigation;
class ServiceActionForm extends ActionForm
{
}

View File

@ -5,7 +5,7 @@
<div class="content" data-base-target="_next">
<p>
<a href="<?= $this->href('/monitoring/config/createbackend'); ?>">
<a href="<?= $this->href('monitoring/config/createbackend'); ?>">
<?= $this->icon('plus'); ?> <?= $this->translate('Create New Monitoring Backend'); ?>
</a>
</p>
@ -20,7 +20,7 @@
<td>
<?= $this->qlink(
$backendName,
'/monitoring/config/editbackend',
'monitoring/config/editbackend',
array('backend-name' => $backendName),
array(
'icon' => 'edit',
@ -35,7 +35,7 @@
<td>
<?= $this->qlink(
'',
'/monitoring/config/removebackend',
'monitoring/config/removebackend',
array('backend-name' => $backendName),
array(
'icon' => 'trash',
@ -49,7 +49,7 @@
</table>
<h1><?= $this->translate('Command Transports') ?></h1>
<p>
<a href="<?= $this->href('/monitoring/config/createtransport'); ?>">
<a href="<?= $this->href('monitoring/config/createtransport'); ?>">
<?= $this->icon('plus'); ?> <?= $this->translate('Create New Transport'); ?>
</a>
</p>
@ -64,7 +64,7 @@
<td>
<?= $this->qlink(
$transportName,
'/monitoring/config/edittransport',
'monitoring/config/edittransport',
array('transport' => $transportName),
array(
'icon' => 'edit',
@ -79,7 +79,7 @@
<td>
<?= $this->qlink(
'',
'/monitoring/config/removetransport',
'monitoring/config/removetransport',
array('transport' => $transportName),
array(
'icon' => 'trash',

View File

@ -14,7 +14,7 @@
<div class="content">
<table data-base-target="_next"
class="action comments multiselect"
data-icinga-multiselect-url="/icingaweb2/monitoring/comments/show"
data-icinga-multiselect-url="<?= $this->href('monitoring/comments/show'); ?>"
data-icinga-multiselect-related="<?= $this->href("monitoring/comments") ?>"
data-icinga-multiselect-data="comment_id">
<tbody>

View File

@ -27,7 +27,7 @@ if (count($groupData) === 0) {
<div class="box contents">
<?php foreach ($groupInfo['contacts'] as $c): ?>
<div class="box entry">
<?= $this->img('/static/gravatar', array('email' => $c->contact_email)); ?>
<?= $this->img('static/gravatar', array('email' => $c->contact_email)); ?>
<?= $this->qlink(
$c->contact_alias,
'monitoring/show/contact',

View File

@ -10,7 +10,7 @@
<div data-base-target="_next" class="content contacts">
<?php foreach ($contacts->peekAhead($this->compact) as $contact): ?>
<div class="contact">
<?= $this->img('/static/gravatar', array('email' => $contact->contact_email)); ?>
<?= $this->img('static/gravatar', array('email' => $contact->contact_email)); ?>
<strong><?= $this->qlink(
$contact->contact_name,
'monitoring/show/contact',

View File

@ -20,7 +20,7 @@ if (! $this->compact): ?>
<div class="content">
<table data-base-target="_next"
class="action multiselect"
data-icinga-multiselect-url="/icingaweb2/monitoring/downtimes/show"
data-icinga-multiselect-url="<?= $this->href('monitoring/downtimes/show'); ?>"
data-icinga-multiselect-controllers="<?= $this->href("monitoring/downtimes") ?>"
data-icinga-multiselect-data="downtime_id">
<tbody>

View File

@ -10,15 +10,18 @@ use Icinga\Module\Monitoring\Object\Host;
</td>
<td>
<?= $this->iconImage()->host($object) ?>
<strong><?= $this->escape($object->host_display_name); ?></strong>
<strong class="selectable"><?= $this->escape($object->host_display_name); ?></strong>
<?php if ($object->host_display_name !== $object->host_name): ?>
<small>(<?= $this->escape($object->host_name); ?>)</small>
<small class="selectable">(<?= $this->escape($object->host_name); ?>)</small>
<?php endif ?>
<?= $this->render('partials/host/statusicons.phtml'); ?>
<br/>
<?php if ($object->host_address6 && $object->host_address6 !== $object->host_name): ?>
<span class="selectable" title="IPv6 address"><?= $this->escape($object->host_address6); ?></span>
<?php endif ?>
<?php if ($object->host_address && $object->host_address !== $object->host_name): ?>
<br>
<?= $this->escape($object->host_address); ?>
<span class="selectable" title="IPv4 address"><?= $this->escape($object->host_address); ?></span>
<?php endif ?>
<?= $this->render('partials/host/statusicons.phtml'); ?>
</td>
</tr>
</table>

View File

@ -11,15 +11,17 @@ use Icinga\Module\Monitoring\Object\Service;
</td>
<td>
<?= $this->iconImage()->service($object) ?>
<strong><?= $this->escape($object->host_display_name); ?></strong>
<strong class="selectable"><?= $this->escape($object->host_display_name); ?></strong>
<?php if ($object->host_display_name !== $object->host_name): ?>
<small>(<?= $this->escape($object->host_name); ?>)</small>
<small class="selectable">(<?= $this->escape($object->host_name); ?>)</small>
<?php endif ?>
<br/>
<?php if ($object->host_address6 && $object->host_address6 !== $object->host_name): ?>
<span class="selectable" title="IPv6 address"><?= $this->escape($object->host_address6); ?></span>
<?php endif ?>
<?php if ($object->host_address && $object->host_address !== $object->host_name): ?>
<br>
<?= $this->escape($object->host_address); ?>
<span class="selectable" title="IPv4 address"><?= $this->escape($object->host_address); ?></span>
<?php endif ?>
<?= $this->render('partials/host/statusicons.phtml'); ?>
</td>
</tr>
<tr class="state <?= Service::getStateText($object->service_state); ?><?= $object->service_handled ? ' handled' : ''; ?>">
@ -29,7 +31,7 @@ use Icinga\Module\Monitoring\Object\Service;
</td>
<td>
<?= $this->iconImage()->host($object) ?>
<strong><?= $this->translate('Service'); ?>: <?= $this->escape($object->service_display_name); ?></strong>
<strong><?= $this->translate('Service'); ?>: <span class="selectable"><?= $this->escape($object->service_display_name); ?></span></strong>
<?php if ($object->service_display_name !== $object->service_description): ?>
<small>(<?= $this->escape($object->service_description); ?>)</small>
<?php endif ?>

View File

@ -1,25 +1,42 @@
<?php
use Icinga\Web\Navigation\Navigation;
$navigation = new Navigation();
$navigation->load($object->getType() . '-action');
foreach ($navigation as $item) {
$item->setObject($object);
}
// add warning to links that open in new tabs to improve accessibility, as recommended by WCAG20 G201
$newTabInfo = sprintf('<span class="info-box display-on-hover"> %s </span>', $this->translate('opens in new window'));
$links = $object->getActionUrls();
foreach ($links as $i => $link) {
$links[$i] = sprintf('<a href="%s" target="_blank">%s ' . $newTabInfo . '</a>', $link, 'Action');
foreach ($object->getActionUrls() as $i => $link) {
$navigation->addItem(
'Action ' . ($i + 1) . $newTabInfo,
array(
'url' => $link,
'target' => '_blank',
'renderer' => array(
'NavigationItemRenderer',
'escape_label' => false
)
)
);
}
if (isset($this->actions)) {
foreach ($this->actions as $id => $action) {
$links[] = sprintf('<a href="%s">%s</a>', $action, $id);
$navigation->addItem($id, array('url' => $action));
}
}
if (empty($links)) {
if ($navigation->isEmpty() || !$navigation->hasRenderableItems()) {
return;
}
?>
<tr>
<th><?= $this->translate('Actions') ?></th>
<td><?= implode("<br>", $links) ?></td>
</tr>
<th><?= $this->translate('Actions'); ?></th>
<?= $navigation->getRenderer()->setElementTag('td')->setCssClass('actions'); ?>
</tr>

View File

@ -10,7 +10,7 @@ foreach ($object->customvars as $name => $value) {
printf(
'<tr><th>%s</th><td class="customvar">%s</td></tr>' . "\n",
$this->escape($name),
$this->escape(ucwords(str_replace('_', ' ', strtolower($name)))),
$this->customvar($value)
);
}

View File

@ -1,26 +1,46 @@
<?php
$notes = trim($object->getNotes());
$links = $object->getNotesUrls();
if (! empty($links) || ! empty($notes)): ?>
use Icinga\Web\Navigation\Navigation;
$navigation = new Navigation();
//$navigation->load($object->getType() . '-note');
foreach ($navigation as $item) {
$item->setObject($object);
}
$notes = trim($object->getNotes());
if ($notes) {
$navigation->addItem($notes);
}
$links = $object->getNotesUrls();
if (! empty($links)) {
// add warning to links that open in new tabs to improve accessibility, as recommended by WCAG20 G201
$newTabInfo = sprintf(
'<span class="info-box display-on-hover"> %s </span>',
$this->translate('opens in new window')
);
foreach ($links as $link) {
$navigation->addItem(
$this->escape($link) . $newTabInfo,
array(
'url' => $link,
'target' => '_blank',
'renderer' => array(
'NavigationItemRenderer',
'escape_label' => false
)
)
);
}
}
if ($navigation->isEmpty() || !$navigation->hasRenderableItems()) {
return;
}
?>
<tr>
<th><?= $this->translate('Notes') ?></th>
<td>
<?php
if (! empty($notes)) {
echo $notes . '<br>';
}
// add warning to links that open in new tabs to improve accessibility, as recommended by WCAG20 G201
$newTabInfo = sprintf(
'<span class="info-box display-on-hover"> %s </span>',
$this->translate('opens in new window')
);
$linkText = '<a href="%s" target="_blank">%s ' . $newTabInfo . '</a>';
foreach ($links as $i => $link) {
$links[$i] = sprintf($linkText, $this->escape($link), $this->escape($link));
}
echo implode('<br>', $links);
?>
</td>
</tr>
<?php endif ?>
<th><?= $this->translate('Notes'); ?></th>
<?= $navigation->getRenderer()->setElementTag('td')->setCssClass('notes'); ?>
</tr>

View File

@ -65,7 +65,7 @@ if (! $beingExtended && !$this->compact): ?>
<div class="timeframe">
<span><?= $this->qlink(
$timeInfo[0]->end->format($intervalFormat),
'/monitoring/list/eventhistory',
'monitoring/list/eventhistory',
array(
'timestamp<' => $timeInfo[0]->start->getTimestamp(),
'timestamp>' => $timeInfo[0]->end->getTimestamp()

View File

@ -85,20 +85,29 @@ $this->provideSearchUrl($this->translate('Services'), 'monitoring/list/services?
$this->provideSearchUrl($this->translate('Hostgroups'), 'monitoring/list/hostgroups?limit=10', 97);
$this->provideSearchUrl($this->translate('Servicegroups'), 'monitoring/list/servicegroups?limit=10', 96);
/*
* Available navigation items
*/
$this->provideNavigationItem('host-action', $this->translate('Host Action'));
$this->provideNavigationItem('service-action', $this->translate('Service Action'));
// Notes are disabled as we're not sure whether to really make a difference between actions and notes
//$this->provideNavigationItem('host-note', $this->translate('Host Note'));
//$this->provideNavigationItem('service-note', $this->translate('Service Note'));
/*
* Problems Section
*/
$section = $this->menuSection($this->translate('Problems'), array(
$section = $this->menuSection(N_('Problems'), array(
'renderer' => array(
'SummaryMenuItemRenderer',
'SummaryNavigationItemRenderer',
'state' => 'critical'
),
'icon' => 'block',
'priority' => 20
));
$section->add($this->translate('Unhandled Hosts'), array(
$section->add(N_('Unhandled Hosts'), array(
'renderer' => array(
'Icinga\Module\Monitoring\Web\Menu\MonitoringBadgeMenuItemRenderer',
'MonitoringBadgeNavigationItemRenderer',
'columns' => array(
'hosts_down_unhandled' => $this->translate('%d unhandled hosts down')
),
@ -108,9 +117,9 @@ $section->add($this->translate('Unhandled Hosts'), array(
'url' => 'monitoring/list/hosts?host_problem=1&host_handled=0',
'priority' => 30
));
$section->add($this->translate('Unhandled Services'), array(
$section->add(N_('Unhandled Services'), array(
'renderer' => array(
'Icinga\Module\Monitoring\Web\Menu\MonitoringBadgeMenuItemRenderer',
'MonitoringBadgeNavigationItemRenderer',
'columns' => array(
'services_critical_unhandled' => $this->translate('%d unhandled services critical')
),
@ -120,19 +129,19 @@ $section->add($this->translate('Unhandled Services'), array(
'url' => 'monitoring/list/services?service_problem=1&service_handled=0&sort=service_severity',
'priority' => 40
));
$section->add($this->translate('Host Problems'), array(
$section->add(N_('Host Problems'), array(
'url' => 'monitoring/list/hosts?host_problem=1&sort=host_severity',
'priority' => 50
));
$section->add($this->translate('Service Problems'), array(
$section->add(N_('Service Problems'), array(
'url' => 'monitoring/list/services?service_problem=1&sort=service_severity&dir=desc',
'priority' => 60
));
$section->add($this->translate('Service Grid'), array(
$section->add(N_('Service Grid'), array(
'url' => 'monitoring/list/servicegrid?problems',
'priority' => 70
));
$section->add($this->translate('Current Downtimes'), array(
$section->add(N_('Current Downtimes'), array(
'url' => 'monitoring/list/downtimes?downtime_is_in_effect=1',
'priority' => 80
));
@ -140,43 +149,43 @@ $section->add($this->translate('Current Downtimes'), array(
/*
* Overview Section
*/
$section = $this->menuSection($this->translate('Overview'), array(
$section = $this->menuSection(N_('Overview'), array(
'icon' => 'sitemap',
'priority' => 30
));
$section->add($this->translate('Tactical Overview'), array(
$section->add(N_('Tactical Overview'), array(
'url' => 'monitoring/tactical',
'priority' => 40
));
$section->add($this->translate('Hosts'), array(
$section->add(N_('Hosts'), array(
'url' => 'monitoring/list/hosts',
'priority' => 50
));
$section->add($this->translate('Services'), array(
$section->add(N_('Services'), array(
'url' => 'monitoring/list/services',
'priority' => 50
));
$section->add($this->translate('Servicegroups'), array(
$section->add(N_('Servicegroups'), array(
'url' => 'monitoring/list/servicegroups',
'priority' => 60
));
$section->add($this->translate('Hostgroups'), array(
$section->add(N_('Hostgroups'), array(
'url' => 'monitoring/list/hostgroups',
'priority' => 60
));
$section->add($this->translate('Contacts'), array(
$section->add(N_('Contacts'), array(
'url' => 'monitoring/list/contacts',
'priority' => 70
));
$section->add($this->translate('Contactgroups'), array(
$section->add(N_('Contactgroups'), array(
'url' => 'monitoring/list/contactgroups',
'priority' => 70
));
$section->add($this->translate('Comments'), array(
$section->add(N_('Comments'), array(
'url' => 'monitoring/list/comments?comment_type=(comment|ack)',
'priority' => 80
));
$section->add($this->translate('Downtimes'), array(
$section->add(N_('Downtimes'), array(
'url' => 'monitoring/list/downtimes',
'priority' => 80
));
@ -184,22 +193,23 @@ $section->add($this->translate('Downtimes'), array(
/*
* History Section
*/
$section = $this->menuSection($this->translate('History'), array(
'icon' => 'rewind'
$section = $this->menuSection(N_('History'), array(
'icon' => 'rewind',
'priority' => 90
));
$section->add($this->translate('Event Grid'), array(
$section->add(N_('Event Grid'), array(
'priority' => 10,
'url' => 'monitoring/list/eventgrid'
));
$section->add($this->translate('Event Overview'), array(
$section->add(N_('Event Overview'), array(
'priority' => 20,
'url' => 'monitoring/list/eventhistory?timestamp>=-7%20days'
));
$section->add($this->translate('Notifications'), array(
$section->add(N_('Notifications'), array(
'priority' => 30,
'url' => 'monitoring/list/notifications',
));
$section->add($this->translate('Timeline'), array(
$section->add(N_('Timeline'), array(
'priority' => 40,
'url' => 'monitoring/timeline'
));
@ -207,144 +217,144 @@ $section->add($this->translate('Timeline'), array(
/*
* Reporting Section
*/
$section = $this->menuSection($this->translate('Reporting'), array(
$section = $this->menuSection(N_('Reporting'), array(
'icon' => 'barchart',
'priority' => 100
));
$section->add($this->translate('Alert Summary'), array(
$section->add(N_('Alert Summary'), array(
'url' => 'monitoring/alertsummary/index'
));
/*
* System Section
*/
$section = $this->menuSection($this->translate('System'));
$section->add($this->translate('Monitoring Health'), array(
$section = $this->menuSection(N_('System'));
$section->add(N_('Monitoring Health'), array(
'url' => 'monitoring/health/info',
'priority' => 720,
'renderer' => 'Icinga\Module\Monitoring\Web\Menu\BackendAvailabilityMenuItemRenderer'
'renderer' => 'BackendAvailabilityNavigationItemRenderer'
));
/*
* Current Incidents
*/
$dashboard = $this->dashboard($this->translate('Current Incidents'));
$dashboard = $this->dashboard(N_('Current Incidents'), array('priority' => 50));
$dashboard->add(
$this->translate('Service Problems'),
N_('Service Problems'),
'monitoring/list/services?service_problem=1&limit=10&sort=service_severity'
);
$dashboard->add(
$this->translate('Recently Recovered Services'),
N_('Recently Recovered Services'),
'monitoring/list/services?service_state=0&limit=10&sort=service_last_state_change&dir=desc'
);
$dashboard->add(
$this->translate('Host Problems'),
N_('Host Problems'),
'monitoring/list/hosts?host_problem=1&sort=host_severity'
);
/*
* Overview
*/
$dashboard = $this->dashboard($this->translate('Overview'));
$dashboard = $this->dashboard(N_('Overview'), array('priority' => 60));
$dashboard->add(
$this->translate('Service Grid'),
N_('Service Grid'),
'monitoring/list/servicegrid?limit=15,18'
);
$dashboard->add(
$this->translate('Service Groups'),
'/monitoring/list/servicegroups'
N_('Service Groups'),
'monitoring/list/servicegroups'
);
$dashboard->add(
$this->translate('Host Groups'),
'/monitoring/list/hostgroups'
N_('Host Groups'),
'monitoring/list/hostgroups'
);
/*
* Most Overdue
*/
$dashboard = $this->dashboard($this->translate('Overdue'));
$dashboard = $this->dashboard(N_('Overdue'), array('priority' => 70));
$dashboard->add(
$this->translate('Late Host Check Results'),
N_('Late Host Check Results'),
'monitoring/list/hosts?host_next_update<now'
);
$dashboard->add(
$this->translate('Late Service Check Results'),
N_('Late Service Check Results'),
'monitoring/list/services?service_next_update<now'
);
$dashboard->add(
$this->translate('Acknowledgements Active For At Least Three Days'),
N_('Acknowledgements Active For At Least Three Days'),
'monitoring/list/comments?comment_type=Ack&comment_timestamp<-3 days&sort=comment_timestamp&dir=asc'
);
$dashboard->add(
$this->translate('Downtimes Active For More Than Three Days'),
N_('Downtimes Active For More Than Three Days'),
'monitoring/list/downtimes?downtime_is_in_effect=1&downtime_scheduled_start<-3%20days&sort=downtime_start&dir=asc'
);
/*
* Muted Objects
*/
$dashboard = $this->dashboard($this->translate('Muted'));
$dashboard = $this->dashboard(N_('Muted'), array('priority' => 80));
$dashboard->add(
$this->translate('Disabled Service Notifications'),
N_('Disabled Service Notifications'),
'monitoring/list/services?service_notifications_enabled=0&limit=10'
);
$dashboard->add(
$this->translate('Disabled Host Notifications'),
N_('Disabled Host Notifications'),
'monitoring/list/hosts?host_notifications_enabled=0&limit=10'
);
$dashboard->add(
$this->translate('Disabled Service Checks'),
N_('Disabled Service Checks'),
'monitoring/list/services?service_active_checks_enabled=0&limit=10'
);
$dashboard->add(
$this->translate('Disabled Host Checks'),
N_('Disabled Host Checks'),
'monitoring/list/hosts?host_active_checks_enabled=0&limit=10'
);
$dashboard->add(
$this->translate('Acknowledged Problem Services'),
N_('Acknowledged Problem Services'),
'monitoring/list/services?service_acknowledgement_type=2&service_problem=1&sort=service_state&limit=10'
);
$dashboard->add(
$this->translate('Acknowledged Problem Hosts'),
N_('Acknowledged Problem Hosts'),
'monitoring/list/hosts?host_acknowledgement_type=2&host_problem=1&sort=host_severity&limit=10'
);
/*
* Activity Stream
*/
$dashboard = $this->dashboard($this->translate('Activity Stream'));
$dashboard = $this->dashboard(N_('Activity Stream'), array('priority' => 90));
$dashboard->add(
$this->translate('Recent Events'),
N_('Recent Events'),
'monitoring/list/eventhistory?timestamp>=-3%20days&sort=timestamp&dir=desc&limit=8'
);
$dashboard->add(
$this->translate('Recent Hard State Changes'),
N_('Recent Hard State Changes'),
'monitoring/list/eventhistory?timestamp>=-3%20days&type=hard_state&sort=timestamp&dir=desc&limit=8'
);
$dashboard->add(
$this->translate('Recent Notifications'),
N_('Recent Notifications'),
'monitoring/list/eventhistory?timestamp>=-3%20days&type=notify&sort=timestamp&dir=desc&limit=8'
);
$dashboard->add(
$this->translate('Downtimes Recently Started'),
N_('Downtimes Recently Started'),
'monitoring/list/eventhistory?timestamp>=-3%20days&type=dt_start&sort=timestamp&dir=desc&limit=8'
);
$dashboard->add(
$this->translate('Downtimes Recently Ended'),
N_('Downtimes Recently Ended'),
'monitoring/list/eventhistory?timestamp>=-3%20days&type=dt_end&sort=timestamp&dir=desc&limit=8'
);
/*
* Stats
*/
$dashboard = $this->dashboard($this->translate('Stats'));
$dashboard = $this->dashboard(N_('Stats'), array('priority' => 99));
$dashboard->add(
$this->translate('Check Stats'),
N_('Check Stats'),
'monitoring/health/stats'
);
$dashboard->add(
$this->translate('Process Information'),
N_('Process Information'),
'monitoring/health/info'
);

View File

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

View File

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

View File

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

View File

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

View File

@ -185,7 +185,11 @@ abstract class DataView implements QueryInterface, SortRules, FilterColumns, Ite
*/
public function isValidFilterTarget($column)
{
return in_array($column, $this->getFilterColumns());
// Customvar
if ($column[0] === '_' && preg_match('/^_(?:host|service)_/i', $column)) {
return true;
}
return in_array($column, $this->getColumns()) || in_array($column, $this->getStaticFilterColumns());
}
/**

View File

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

View File

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

View File

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

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