Merge branch 'feature/custom-menu-items-5600'
resolves #5600 fixes #10169 fixes #10046 fixes #10082
This commit is contained in:
commit
d4c1ca9e18
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,346 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Controllers;
|
||||
|
||||
use Exception;
|
||||
use Icinga\Application\Config;
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Exception\NotFoundError;
|
||||
use Icinga\Data\DataArray\ArrayDatasource;
|
||||
use Icinga\Forms\ConfirmRemovalForm;
|
||||
use Icinga\Forms\Navigation\NavigationConfigForm;
|
||||
use Icinga\Web\Controller;
|
||||
use Icinga\Web\Form;
|
||||
use Icinga\Web\Notification;
|
||||
use Icinga\Web\Url;
|
||||
|
||||
/**
|
||||
* Navigation configuration
|
||||
*/
|
||||
class NavigationController extends Controller
|
||||
{
|
||||
/**
|
||||
* The default item types provided by Icinga Web 2
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultItemTypes;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
|
||||
$this->defaultItemTypes = array(
|
||||
'menu-item' => $this->translate('Menu Entry'),
|
||||
'dashlet' => 'Dashlet'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of available navigation item types
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function listItemTypes()
|
||||
{
|
||||
$moduleManager = Icinga::app()->getModuleManager();
|
||||
|
||||
$types = $this->defaultItemTypes;
|
||||
foreach ($moduleManager->getLoadedModules() as $module) {
|
||||
if ($this->hasPermission($moduleManager::MODULE_PERMISSION_NS . $module->getName())) {
|
||||
$moduleTypes = $module->getNavigationItems();
|
||||
if (! empty($moduleTypes)) {
|
||||
$types = array_merge($types, $moduleTypes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the current user a list of his/her navigation items
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
$user = $this->Auth()->getUser();
|
||||
|
||||
$ds = new ArrayDatasource(array_merge(
|
||||
Config::app('navigation')->select()->where('owner', $user->getUsername())->fetchAll(),
|
||||
iterator_to_array($user->loadNavigationConfig())
|
||||
));
|
||||
$ds->setKeyColumn('name');
|
||||
$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('Shared Navigation')
|
||||
),
|
||||
$query
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all shared navigation items
|
||||
*/
|
||||
public function sharedAction()
|
||||
{
|
||||
$this->assertPermission('config/application/navigation');
|
||||
$config = Config::app('navigation');
|
||||
$config->getConfigObject()->setKeyColumn('name');
|
||||
$query = $config->select();
|
||||
|
||||
$removeForm = new Form();
|
||||
$removeForm->setUidDisabled();
|
||||
$removeForm->setAction(Url::fromPath('navigation/unshare'));
|
||||
$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->setItemTypes($this->listItemTypes());
|
||||
$form->setTitle($this->translate('Create New Navigation Item'));
|
||||
$form->addDescription($this->translate('Create a new navigation item, such as a menu entry or dashlet.'));
|
||||
$form->setUser($this->Auth()->getUser());
|
||||
$form->setShareConfig(Config::app('navigation'));
|
||||
$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 (isset($data['type']) && $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');
|
||||
$referrer = $this->params->get('referrer', 'index');
|
||||
|
||||
$form = new NavigationConfigForm();
|
||||
$form->setRedirectUrl($referrer === 'shared' ? 'navigation/shared' : 'navigation');
|
||||
$form->setItemTypes($this->listItemTypes());
|
||||
$form->setTitle(sprintf($this->translate('Edit Navigation Item %s'), $itemName));
|
||||
$form->setUser($this->Auth()->getUser());
|
||||
$form->setShareConfig(Config::app('navigation'));
|
||||
$form->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');
|
||||
|
||||
$navigationConfigForm = new NavigationConfigForm();
|
||||
$navigationConfigForm->setUser($this->Auth()->getUser());
|
||||
$navigationConfigForm->setShareConfig(Config::app('navigation'));
|
||||
$form = new ConfirmRemovalForm();
|
||||
$form->setRedirectUrl('navigation');
|
||||
$form->setTitle(sprintf($this->translate('Remove Navigation Item %s'), $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');
|
||||
|
||||
$navigationConfigForm = new NavigationConfigForm();
|
||||
$navigationConfigForm->setUser($this->Auth()->getUser());
|
||||
$navigationConfigForm->setShareConfig(Config::app('navigation'));
|
||||
|
||||
$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')));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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')
|
||||
)
|
||||
)
|
||||
);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)'
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,824 @@
|
|||
<?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
|
||||
*
|
||||
* @return Config
|
||||
*/
|
||||
public function getUserConfig()
|
||||
{
|
||||
if ($this->userConfig === null) {
|
||||
$this->setUserConfig($this->getUser()->loadNavigationConfig());
|
||||
}
|
||||
|
||||
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
|
||||
*
|
||||
* @return Config
|
||||
*/
|
||||
public function getShareConfig()
|
||||
{
|
||||
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() as $sectionName => $sectionConfig) {
|
||||
if (
|
||||
$sectionName !== $this->itemToLoad
|
||||
&& $sectionConfig->type === $type
|
||||
&& $sectionConfig->owner === ($owner ?: $this->getUser()->getUsername())
|
||||
&& !in_array($sectionName, $children, true)
|
||||
) {
|
||||
$names[] = $sectionName;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->getUserConfig() as $sectionName => $sectionConfig) {
|
||||
if (
|
||||
$sectionName !== $this->itemToLoad
|
||||
&& $sectionConfig->type === $type
|
||||
&& !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
|
||||
* @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');
|
||||
}
|
||||
|
||||
$shared = false;
|
||||
$config = $this->getUserConfig();
|
||||
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();
|
||||
$shared = true;
|
||||
} else {
|
||||
unset($data['users']);
|
||||
unset($data['groups']);
|
||||
}
|
||||
} elseif (isset($data['parent']) && $data['parent'] && $this->hasBeenShared($data['parent'])) {
|
||||
$data['owner'] = $this->getUser()->getUsername();
|
||||
$config = $this->getShareConfig();
|
||||
$shared = true;
|
||||
}
|
||||
|
||||
$itemName = $data['name'];
|
||||
$exists = $config->hasSection($itemName);
|
||||
if (! $exists) {
|
||||
if ($shared) {
|
||||
$exists = $this->getUserConfig()->hasSection($itemName);
|
||||
} else {
|
||||
$exists = (bool) $this->getShareConfig()
|
||||
->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 {
|
||||
$owner = new User($ownerName);
|
||||
$exists = $owner->loadNavigationConfig()->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 {
|
||||
$owner = new User($itemConfig->owner);
|
||||
$config = $owner->loadNavigationConfig();
|
||||
}
|
||||
|
||||
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);
|
||||
$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'
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$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
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasBeenShared($name)
|
||||
{
|
||||
return $this->getConfigForItem($name) === $this->getShareConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Forms\Navigation;
|
||||
|
||||
use Icinga\Web\Form;
|
||||
|
||||
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'
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -22,7 +22,7 @@ if ($this->layout()->autorefreshInterval) {
|
|||
<?php if (Auth::getInstance()->isAuthenticated()): ?>
|
||||
<?= $this->qlink(
|
||||
'',
|
||||
'/dashboard',
|
||||
'dashboard',
|
||||
null,
|
||||
array(
|
||||
'icon' => '../logo_icinga-inv.png',
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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'); ?>
|
|
@ -0,0 +1,6 @@
|
|||
<div class="controls">
|
||||
<?= $tabs->showOnlyCloseButton(); ?>
|
||||
</div>
|
||||
<div class="content">
|
||||
<?= $form; ?>
|
||||
</div>
|
|
@ -0,0 +1,53 @@
|
|||
<?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 $name => $item): ?>
|
||||
<tr>
|
||||
<td><?= $this->qlink(
|
||||
$name,
|
||||
'navigation/edit',
|
||||
array('name' => $name),
|
||||
array(
|
||||
'title' => sprintf($this->translate('Edit navigation item %s'), $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' => $name),
|
||||
array(
|
||||
'icon' => 'trash',
|
||||
'title' => sprintf($this->translate('Remove navigation item %s'), $name)
|
||||
)
|
||||
); ?></td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif ?>
|
||||
</div>
|
|
@ -0,0 +1,58 @@
|
|||
<?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">
|
||||
<?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 $name => $item): ?>
|
||||
<tr>
|
||||
<td><?= $this->qlink(
|
||||
$name,
|
||||
'navigation/edit',
|
||||
array(
|
||||
'name' => $name,
|
||||
'referrer' => 'shared'
|
||||
),
|
||||
array(
|
||||
'title' => sprintf($this->translate('Edit shared navigation item %s'), $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', $name); ?></td>
|
||||
<?php endif ?>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif ?>
|
||||
</div>
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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,20 @@ class Module
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a new type of configurable navigation item with a optional label
|
||||
*
|
||||
* @param string $type
|
||||
* @param string $label
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function provideNavigationItem($type, $label = null)
|
||||
{
|
||||
$this->navigationItems[$type] = $label ?: $type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register module namespaces on our class loader
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,204 @@ 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::app('navigation')->getConfigObject();
|
||||
$config->setKeyColumn('name');
|
||||
|
||||
if ($type === 'dashboard-pane') {
|
||||
$panes = array();
|
||||
foreach ($config->select()->where('type', 'dashlet') 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->select()->where('type', $type) 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
|
||||
*/
|
||||
|
|
|
@ -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')) {
|
||||
|
||||
/**
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,56 @@ class User
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and return this user's navigation configuration
|
||||
*
|
||||
* @return Config
|
||||
*/
|
||||
public function loadNavigationConfig()
|
||||
{
|
||||
return Config::fromIni(
|
||||
Config::resolvePath('preferences')
|
||||
. DIRECTORY_SEPARATOR
|
||||
. $this->getUsername()
|
||||
. DIRECTORY_SEPARATOR
|
||||
. 'navigation.ini'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and return this user's configured navigation of the given type
|
||||
*
|
||||
* @param string $type
|
||||
*
|
||||
* @return Navigation
|
||||
*/
|
||||
public function getNavigation($type)
|
||||
{
|
||||
$config = $this->loadNavigationConfig();
|
||||
$config->getConfigObject()->setKeyColumn('name');
|
||||
|
||||
if ($type === 'dashboard-pane') {
|
||||
$panes = array();
|
||||
foreach ($config->select()->where('type', 'dashlet') 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->select()->where('type', $type));
|
||||
}
|
||||
|
||||
return $navigation;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,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())
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,552 @@
|
|||
<?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)
|
||||
{
|
||||
// Shareables
|
||||
$this->merge(Icinga::app()->getSharedNavigation($type));
|
||||
|
||||
// User Preferences
|
||||
$user = Auth::getInstance()->getUser();
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 '';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
<?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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->view()->escape($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 . '"';
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) : '';
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
{
|
||||
|
@ -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,43 @@ 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] === '/') {
|
||||
$baseUrl = '';
|
||||
} elseif (! $baseUrl) {
|
||||
$baseUrl = $request->getBaseUrl();
|
||||
}
|
||||
|
||||
if ($baseUrl && !$urlObject->isExternal() && strpos($urlPath, $baseUrl) === 0) {
|
||||
$urlObject->setPath(substr($urlPath, strlen($baseUrl)));
|
||||
} else {
|
||||
$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 +182,7 @@ class Url
|
|||
$urlObject->setAnchor($urlParts['fragment']);
|
||||
}
|
||||
|
||||
$urlObject->setBaseUrl($baseUrl);
|
||||
$urlObject->setParams($params);
|
||||
return $urlObject;
|
||||
}
|
||||
|
@ -179,19 +210,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;
|
||||
}
|
||||
|
||||
|
@ -231,17 +256,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 +319,9 @@ class Url
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function getQueryString()
|
||||
public function getQueryString($separator = null)
|
||||
{
|
||||
return (string) $this->params;
|
||||
return $this->params->toString($separator);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -262,7 +331,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 +459,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
|
||||
*
|
||||
|
|
|
@ -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,21 @@ 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->loadUserDashboards();
|
||||
|
||||
$this->mergePanes($panes);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
}
|
|
@ -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
|
||||
{
|
||||
}
|
|
@ -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',
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,25 +1,38 @@
|
|||
<?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'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
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>
|
|
@ -1,26 +1,42 @@
|
|||
<?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'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
|
@ -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()
|
||||
|
|
|
@ -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'
|
||||
);
|
||||
|
||||
|
|
|
@ -5,9 +5,11 @@ namespace Icinga\Module\Monitoring\Object;
|
|||
|
||||
use InvalidArgumentException;
|
||||
use Icinga\Application\Config;
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Data\Filter\Filter;
|
||||
use Icinga\Data\Filterable;
|
||||
use Icinga\Exception\InvalidPropertyException;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use Icinga\Module\Monitoring\Backend\MonitoringBackend;
|
||||
use Icinga\Web\UrlParams;
|
||||
|
||||
|
@ -208,6 +210,59 @@ abstract class MonitoredObject implements Filterable
|
|||
// Left out on purpose. Interface is deprecated.
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this object matches the given filter
|
||||
*
|
||||
* @param Filter $filter
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @throws ProgrammingError In case the object cannot be found
|
||||
*/
|
||||
public function matches(Filter $filter)
|
||||
{
|
||||
if ($this->properties === null && $this->fetch() === false) {
|
||||
throw new ProgrammingError(
|
||||
'Unable to apply filter. Object %s of type %s not found.',
|
||||
$this->getName(),
|
||||
$this->getType()
|
||||
);
|
||||
}
|
||||
|
||||
$queryString = $filter->toQueryString();
|
||||
$row = clone $this->properties;
|
||||
|
||||
if (strpos($queryString, '_host_') !== false || strpos($queryString, '_service_') !== false) {
|
||||
if ($this->customvars === null) {
|
||||
$this->fetchCustomvars();
|
||||
}
|
||||
|
||||
foreach ($this->customvars as $name => $value) {
|
||||
if (! is_object($value)) {
|
||||
$row->{'_' . $this->getType() . '_' . strtolower(str_replace(' ', '_', $name))} = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (strpos($queryString, 'hostgroup_name') !== false) {
|
||||
if ($this->hostgroups === null) {
|
||||
$this->fetchHostgroups();
|
||||
}
|
||||
|
||||
$row->hostgroup_name = array_keys($this->hostgroups);
|
||||
}
|
||||
|
||||
if (strpos($queryString, 'servicegroup_name') !== false) {
|
||||
if ($this->servicegroups === null) {
|
||||
$this->fetchServicegroups();
|
||||
}
|
||||
|
||||
$row->servicegroup_name = array_keys($this->servicegroups);
|
||||
}
|
||||
|
||||
return $filter->matches($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Require the object's type to be one of the given types
|
||||
*
|
||||
|
@ -529,12 +584,15 @@ abstract class MonitoredObject implements Filterable
|
|||
*/
|
||||
public function fetchServicegroups()
|
||||
{
|
||||
$this->servicegroups = $this->backend->select()
|
||||
$query = $this->backend->select()
|
||||
->from('servicegroup', array('servicegroup_name', 'servicegroup_alias'))
|
||||
->where('host_name', $this->host_name)
|
||||
->where('service_description', $this->service_description)
|
||||
->applyFilter($this->getFilter())
|
||||
->fetchPairs();
|
||||
->where('host_name', $this->host_name);
|
||||
|
||||
if ($this->type === self::TYPE_SERVICE) {
|
||||
$query->where('service_description', $this->service_description);
|
||||
}
|
||||
|
||||
$this->servicegroups = $query->applyFilter($this->getFilter())->fetchPairs();
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Module\Monitoring\Web\Navigation;
|
||||
|
||||
use Icinga\Data\Filter\Filter;
|
||||
use Icinga\Web\Navigation\NavigationItem;
|
||||
use Icinga\Module\Monitoring\Object\Macro;
|
||||
use Icinga\Module\Monitoring\Object\MonitoredObject;
|
||||
|
||||
/**
|
||||
* Action for monitored objects
|
||||
*/
|
||||
class Action extends NavigationItem
|
||||
{
|
||||
/**
|
||||
* Whether this action's macros were already resolved
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $resolved = false;
|
||||
|
||||
/**
|
||||
* This action's object
|
||||
*
|
||||
* @var MonitoredObject
|
||||
*/
|
||||
protected $object;
|
||||
|
||||
/**
|
||||
* The filter to use when being asked whether to render this action
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $filter;
|
||||
|
||||
/**
|
||||
* Set this action's object
|
||||
*
|
||||
* @param MonitoredObject $object
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setObject(MonitoredObject $object)
|
||||
{
|
||||
$this->object = $object;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this action's object
|
||||
*
|
||||
* @return MonitoredObject
|
||||
*/
|
||||
public function getObject()
|
||||
{
|
||||
return $this->object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the filter to use when being asked whether to render this action
|
||||
*
|
||||
* @param string $filter
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setFilter($filter)
|
||||
{
|
||||
$this->filter = $filter;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the filter to use when being asked whether to render this action
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFilter()
|
||||
{
|
||||
return $this->filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUrl()
|
||||
{
|
||||
$url = parent::getUrl();
|
||||
if (! $this->resolved && $url !== null) {
|
||||
$this->setUrl(Macro::resolveMacros($url->getAbsoluteUrl(), $this->getObject()));
|
||||
$this->resolved = true;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRender()
|
||||
{
|
||||
if ($this->render === null) {
|
||||
$filter = $this->getFilter();
|
||||
$this->render = $filter ? $this->getObject()->matches(Filter::fromQueryString($filter)) : true;
|
||||
}
|
||||
|
||||
return $this->render;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Module\Monitoring\Web\Navigation;
|
||||
|
||||
/**
|
||||
* A host action
|
||||
*/
|
||||
class HostAction extends Action
|
||||
{
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Module\Monitoring\Web\Navigation;
|
||||
|
||||
/**
|
||||
* A host note
|
||||
*/
|
||||
class HostNote extends Action
|
||||
{
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Module\Monitoring\Web\Menu;
|
||||
namespace Icinga\Module\Monitoring\Web\Navigation\Renderer;
|
||||
|
||||
use Icinga\Web\Menu;
|
||||
use Icinga\Web\Menu\BadgeMenuItemRenderer;
|
||||
use Exception;
|
||||
use Icinga\Web\Navigation\Renderer\BadgeNavigationItemRenderer;
|
||||
use Icinga\Module\Monitoring\Backend\MonitoringBackend;
|
||||
|
||||
class BackendAvailabilityMenuItemRenderer extends BadgeMenuItemRenderer
|
||||
class BackendAvailabilityNavigationItemRenderer extends BadgeNavigationItemRenderer
|
||||
{
|
||||
/**
|
||||
* Get whether or not the monitoring backend is currently running
|
||||
|
@ -43,10 +43,15 @@ class BackendAvailabilityMenuItemRenderer extends BadgeMenuItemRenderer
|
|||
*/
|
||||
public function getCount()
|
||||
{
|
||||
if (! $this->isCurrentlyRunning()) {
|
||||
return 1;
|
||||
try {
|
||||
if ($this->isCurrentlyRunning()) {
|
||||
return 0;
|
||||
}
|
||||
} catch (Exception $_) {
|
||||
// pass
|
||||
}
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
|
@ -1,23 +1,26 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Module\Monitoring\Web\Menu;
|
||||
namespace Icinga\Module\Monitoring\Web\Navigation\Renderer;
|
||||
|
||||
use Exception;
|
||||
use Icinga\Authentication\Auth;
|
||||
use Icinga\Data\ConfigObject;
|
||||
use Icinga\Data\Filter\Filter;
|
||||
use Icinga\Data\Filterable;
|
||||
use Icinga\Web\Menu;
|
||||
use Icinga\Module\Monitoring\Backend\MonitoringBackend;
|
||||
use Icinga\Web\Menu\BadgeMenuItemRenderer;
|
||||
use Icinga\Web\Navigation\Renderer\SummaryNavigationItemRenderer;
|
||||
|
||||
/**
|
||||
* Render generic dataView columns as badges in MenuItems
|
||||
*
|
||||
* Renders numeric data view column values into menu item badges, fully configurable
|
||||
* and with a caching mechanism to prevent needless requests to the same data view
|
||||
* and with a caching mechanism to prevent needless requests to the same data view.
|
||||
*
|
||||
* It is possible to configure the class of the rendered badge as option 'class', the
|
||||
* column to fetch using the option 'column' and the dataView from which the columns
|
||||
* will be fetched using the option 'dataView'.
|
||||
*/
|
||||
class MonitoringBadgeMenuItemRenderer extends BadgeMenuItemRenderer
|
||||
class MonitoringBadgeNavigationItemRenderer extends SummaryNavigationItemRenderer
|
||||
{
|
||||
/**
|
||||
* Caches the responses for all executed summaries
|
||||
|
@ -35,7 +38,7 @@ class MonitoringBadgeMenuItemRenderer extends BadgeMenuItemRenderer
|
|||
protected static $dataViews = array();
|
||||
|
||||
/**
|
||||
* The data view displayed by this menu item
|
||||
* The dataview referred to by the navigation item
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
|
@ -49,36 +52,56 @@ class MonitoringBadgeMenuItemRenderer extends BadgeMenuItemRenderer
|
|||
protected $columns;
|
||||
|
||||
/**
|
||||
* The titles that will be used to render this menu item tooltip
|
||||
* Set the dataview referred to by the navigation item
|
||||
*
|
||||
* @var String[]
|
||||
* @param string $dataView
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected $titles;
|
||||
|
||||
/**
|
||||
* The class of the badge element
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* Create a new instance of ColumnMenuItemRenderer
|
||||
*
|
||||
* It is possible to configure the class of the rendered badge as option 'class', the column
|
||||
* to fetch using the option 'column' and the dataView from which the columns will be
|
||||
* fetched using the option 'dataView'.
|
||||
*
|
||||
* @param $configuration ConfigObject The configuration to use
|
||||
*/
|
||||
public function __construct(ConfigObject $configuration)
|
||||
public function setDataView($dataView)
|
||||
{
|
||||
parent::__construct($configuration);
|
||||
$this->dataView = $dataView;
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->columns = $configuration->get('columns');
|
||||
$this->state = $configuration->get('state');
|
||||
$this->dataView = $configuration->get('dataView');
|
||||
/**
|
||||
* Return the dataview referred to by the navigation item
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDataView()
|
||||
{
|
||||
return $this->dataView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the columns and titles displayed in the badge
|
||||
*
|
||||
* @param array $columns
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setColumns(array $columns)
|
||||
{
|
||||
$this->columns = $columns;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the columns and titles displayed in the badge
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getColumns()
|
||||
{
|
||||
return $this->columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
// clear the outdated summary cache, since new columns are being added. Optimally all menu item are constructed
|
||||
// before any rendering is going on to avoid trashing too man old requests
|
||||
if (isset(self::$summaries[$this->dataView])) {
|
||||
|
@ -89,11 +112,11 @@ class MonitoringBadgeMenuItemRenderer extends BadgeMenuItemRenderer
|
|||
if (! isset(self::$dataViews[$this->dataView])) {
|
||||
self::$dataViews[$this->dataView] = array();
|
||||
}
|
||||
|
||||
foreach ($this->columns as $column => $title) {
|
||||
if (! array_search($column, self::$dataViews[$this->dataView])) {
|
||||
self::$dataViews[$this->dataView][] = $column;
|
||||
}
|
||||
$this->titles[$column] = $title;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,12 +139,11 @@ class MonitoringBadgeMenuItemRenderer extends BadgeMenuItemRenderer
|
|||
}
|
||||
|
||||
/**
|
||||
* Fetch the response from the database or access cache
|
||||
* Fetch the dataview from the database or access cache
|
||||
*
|
||||
* @param $view
|
||||
* @param string $view
|
||||
*
|
||||
* @return null
|
||||
* @throws \Icinga\Exception\ConfigurationError
|
||||
* @return object
|
||||
*/
|
||||
protected static function summary($view)
|
||||
{
|
||||
|
@ -133,51 +155,29 @@ class MonitoringBadgeMenuItemRenderer extends BadgeMenuItemRenderer
|
|||
static::applyRestriction('monitoring/filter/objects', $summary);
|
||||
self::$summaries[$view] = $summary->fetchRow();
|
||||
}
|
||||
return isset(self::$summaries[$view]) ? self::$summaries[$view] : null;
|
||||
|
||||
return self::$summaries[$view];
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the color of the badge
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getState()
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
/**
|
||||
* The amount of items to display in the badge
|
||||
*
|
||||
* @return int
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCount()
|
||||
{
|
||||
$sum = self::summary($this->dataView);
|
||||
$count = 0;
|
||||
try {
|
||||
$summary = self::summary($this->getDataView());
|
||||
} catch (Exception $_) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
foreach ($this->columns as $col => $title) {
|
||||
if (isset($sum->$col)) {
|
||||
$count += $sum->$col;
|
||||
$count = 0;
|
||||
foreach ($this->getColumns() as $column => $title) {
|
||||
if (isset($summary->$column) && $summary->$column > 0) {
|
||||
$this->titles[] = sprintf($title, $summary->$column);
|
||||
$count += $summary->$column;
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* The tooltip title
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
$titles = array();
|
||||
$sum = $this->summary($this->dataView);
|
||||
foreach ($this->columns as $column => $value) {
|
||||
if (isset($sum->$column) && $sum->$column > 0) {
|
||||
$titles[] = sprintf($this->titles[$column], $sum->$column);
|
||||
}
|
||||
}
|
||||
return implode(', ', $titles);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Module\Monitoring\Web\Navigation;
|
||||
|
||||
/**
|
||||
* A service action
|
||||
*/
|
||||
class ServiceAction extends Action
|
||||
{
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||
|
||||
namespace Icinga\Module\Monitoring\Web\Navigation;
|
||||
|
||||
/**
|
||||
* A service note
|
||||
*/
|
||||
class ServiceNote extends Action
|
||||
{
|
||||
}
|
|
@ -17,6 +17,11 @@ table.action td .pluginoutput {
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
td.notes ul.nav,
|
||||
td.actions ul.nav {
|
||||
.non-list-like-list;
|
||||
}
|
||||
|
||||
div.pluginoutput {
|
||||
overflow: auto;
|
||||
color: #888;
|
||||
|
|
|
@ -290,6 +290,7 @@ class GettextTranslationHelper
|
|||
'--keyword=t:1,2c',
|
||||
'--keyword=tp:1,2',
|
||||
'--keyword=tp:1,2,4c',
|
||||
'--keyword=N_',
|
||||
'--sort-output',
|
||||
'--force-po',
|
||||
'--omit-header',
|
||||
|
|
|
@ -36,6 +36,11 @@ input, select, textarea {
|
|||
font-family: Calibri, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
margin-top: 0.3em;
|
||||
margin-bottom: 0.1em;
|
||||
}
|
||||
|
||||
input:focus, select:focus {
|
||||
border-color: #333;
|
||||
}
|
||||
|
@ -360,6 +365,10 @@ i.autosubmit-warning {
|
|||
}
|
||||
}
|
||||
|
||||
input[type=checkbox] + i.autosubmit-warning {
|
||||
margin-top: 0.15em;
|
||||
}
|
||||
|
||||
html.no-js i.autosubmit-warning {
|
||||
.sr-only;
|
||||
}
|
||||
|
|
|
@ -57,15 +57,11 @@
|
|||
* TODO: How should we handle POST requests? e.g. search VS login
|
||||
*/
|
||||
pushCurrentState: function () {
|
||||
|
||||
var icinga = this.icinga;
|
||||
|
||||
// No history API, no action
|
||||
if (!this.enabled) {
|
||||
if (! this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
icinga.logger.debug('Pushing current state to history');
|
||||
var url = '';
|
||||
|
||||
// We only store URLs of containers sitting directly under #main:
|
||||
|
@ -85,6 +81,7 @@
|
|||
|
||||
// Did we find any URL? Then push it!
|
||||
if (url !== '') {
|
||||
this.icinga.logger.debug('Pushing current state to history');
|
||||
this.push(url);
|
||||
}
|
||||
},
|
||||
|
@ -112,6 +109,9 @@
|
|||
push: function (url) {
|
||||
url = url.replace(/[\?&]?_(render|reload)=[a-z0-9]+/g, '');
|
||||
if (this.lastPushUrl === url) {
|
||||
this.icinga.logger.debug(
|
||||
'Ignoring history state push for url ' + url + ' as it\' currently on top of the stack'
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.lastPushUrl = url;
|
||||
|
|
|
@ -331,7 +331,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
this.redirectToUrl(redirect, req.$target, req.getResponseHeader('X-Icinga-Rerender-Layout'));
|
||||
this.redirectToUrl(redirect, req.$target, req.url, req.getResponseHeader('X-Icinga-Rerender-Layout'));
|
||||
return true;
|
||||
},
|
||||
|
||||
|
@ -340,9 +340,10 @@
|
|||
*
|
||||
* @param {string} url
|
||||
* @param {object} $target
|
||||
* @param {string] origin
|
||||
* @param {boolean} rerenderLayout
|
||||
*/
|
||||
redirectToUrl: function (url, $target, rerenderLayout) {
|
||||
redirectToUrl: function (url, $target, origin, rerenderLayout) {
|
||||
var icinga = this.icinga;
|
||||
|
||||
if (typeof rerenderLayout === 'undefined') {
|
||||
|
@ -365,7 +366,15 @@
|
|||
// Retain detail URL if the layout is rerendered
|
||||
parts = document.location.hash.split('#!').splice(1);
|
||||
if (parts.length) {
|
||||
r.loadNext = parts;
|
||||
r.loadNext = $.grep(parts, function (url) {
|
||||
if (url !== origin) {
|
||||
icinga.logger.debug('Retaining detail url ' + url);
|
||||
return true;
|
||||
}
|
||||
|
||||
icinga.logger.debug('Discarding detail url ' + url + ' as it\'s the origin of the redirect');
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -609,7 +618,7 @@
|
|||
|
||||
this.processRedirectHeader(req);
|
||||
|
||||
if (typeof req.loadNext !== 'undefined') {
|
||||
if (typeof req.loadNext !== 'undefined' && req.loadNext.length) {
|
||||
if ($('#col2').length) {
|
||||
this.loadUrl(req.loadNext[0], $('#col2'));
|
||||
this.icinga.ui.layout2col();
|
||||
|
|
|
@ -82,13 +82,13 @@ class UrlTest extends BaseTestCase
|
|||
$url = Url::fromPath('/my/test/url.html');
|
||||
|
||||
$this->assertEquals(
|
||||
'/',
|
||||
'',
|
||||
$url->getBaseUrl(),
|
||||
'Url::fromPath does not recognize the correct base url'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'my/test/url.html',
|
||||
$url->getPath(),
|
||||
'/my/test/url.html',
|
||||
$url->getAbsoluteUrl(),
|
||||
'Url::fromPath does not recognize the correct url path'
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ require_once realpath(dirname(__FILE__) . '/../../../../bootstrap.php');
|
|||
|
||||
use Mockery;
|
||||
use Icinga\Test\BaseTestCase;
|
||||
use Icinga\User;
|
||||
use Icinga\Web\Widget\Dashboard;
|
||||
use Icinga\Web\Widget\Dashboard\Pane;
|
||||
use Icinga\Web\Widget\Dashboard\Dashlet;
|
||||
|
@ -46,23 +45,6 @@ class DashboardTest extends BaseTestCase
|
|||
Mockery::close(); // Necessary because some tests run in a separate process
|
||||
}
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$moduleMock = Mockery::mock('Icinga\Application\Modules\Module');
|
||||
$moduleMock->shouldReceive('getPaneItems')->andReturn(array(
|
||||
'test-pane' => new Pane('Test Pane')
|
||||
));
|
||||
$moduleMock->shouldReceive('getName')->andReturn('test');
|
||||
|
||||
$moduleManagerMock = Mockery::mock('Icinga\Application\Modules\Manager');
|
||||
$moduleManagerMock->shouldReceive('getLoadedModules')->andReturn(array(
|
||||
'test-module' => $moduleMock
|
||||
));
|
||||
|
||||
$bootstrapMock = $this->setupIcingaMock();
|
||||
$bootstrapMock->shouldReceive('getModuleManager')->andReturn($moduleManagerMock);
|
||||
}
|
||||
|
||||
public function testWhetherCreatePaneCreatesAPane()
|
||||
{
|
||||
$dashboard = new Dashboard();
|
||||
|
@ -126,24 +108,6 @@ class DashboardTest extends BaseTestCase
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testWhetherCreatePaneCreatesAPane
|
||||
*/
|
||||
public function testLoadPaneItemsProvidedByEnabledModules()
|
||||
{
|
||||
$user = new User('test');
|
||||
$user->setPermissions(array('*' => '*'));
|
||||
$dashboard = new Dashboard();
|
||||
$dashboard->setUser($user);
|
||||
$dashboard->load();
|
||||
|
||||
$this->assertCount(
|
||||
1,
|
||||
$dashboard->getPanes(),
|
||||
'Dashboard::load() could not load panes from enabled modules'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Icinga\Exception\ProgrammingError
|
||||
* @depends testWhetherCreatePaneCreatesAPane
|
||||
|
|
Loading…
Reference in New Issue