mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-04-08 17:15:08 +02:00
Change url handling to detail on hashtag, add service filter
The url is now http://%mainUrl%#%anchor%!detail=%detailUrl% which allows us to better support IE and prevents the detail url from appearing on the server side. refs #4868
This commit is contained in:
parent
3df8cacea8
commit
4a95ba3468
application
library/Icinga
Data/DataArray
Filter
Web
modules/monitoring
public/js
test
@ -33,9 +33,6 @@ use Icinga\Filter\Filter;
|
||||
use Icinga\Filter\FilterAttribute;
|
||||
use Icinga\Filter\Type\TextFilter;
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Module\Monitoring\Filter\Type\StatusFilter;
|
||||
use Icinga\Module\Monitoring\Filter\UrlViewFilter;
|
||||
use Icinga\Module\Monitoring\DataView\HostStatus;
|
||||
use Icinga\Web\Url;
|
||||
|
||||
/**
|
||||
@ -50,6 +47,8 @@ class FilterController extends ActionController
|
||||
*/
|
||||
private $registry;
|
||||
|
||||
private $moduleRegistry;
|
||||
|
||||
/**
|
||||
* Entry point for filtering, uses the filter_domain and filter_module request parameter
|
||||
* to determine which filter registry should be used
|
||||
@ -57,19 +56,27 @@ class FilterController extends ActionController
|
||||
public function indexAction()
|
||||
{
|
||||
$this->registry = new Filter();
|
||||
$query = $this->getRequest()->getParam('query', '');
|
||||
$target = $this->getRequest()->getParam('filter_domain', '');
|
||||
|
||||
if ($this->getRequest()->getHeader('accept') == 'application/json') {
|
||||
$this->getResponse()->setHeader('Content-Type', 'application/json');
|
||||
|
||||
$this->setupQueries(
|
||||
$this->getParam('filter_domain', ''),
|
||||
$target,
|
||||
$this->getParam('filter_module', '')
|
||||
);
|
||||
|
||||
$this->_helper->json($this->parse($this->getRequest()->getParam('query', '')));
|
||||
$this->_helper->json($this->parse($query, $target));
|
||||
} else {
|
||||
$this->redirect('index/welcome');
|
||||
$this->setupQueries(
|
||||
$target,
|
||||
$this->getParam('filter_module')
|
||||
);
|
||||
$urlTarget = $this->parse($query, $target);
|
||||
die(print_r($urlTarget,true));
|
||||
$this->redirect($urlTarget['urlParam']);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,6 +89,7 @@ class FilterController extends ActionController
|
||||
{
|
||||
$class = '\\Icinga\\Module\\' . ucfirst($module) . '\\Filter\\Registry';
|
||||
$factory = strtolower($domain) . 'Filter';
|
||||
$this->moduleRegistry = $class;
|
||||
$this->registry->addDomain($class::$factory());
|
||||
}
|
||||
|
||||
@ -91,17 +99,16 @@ class FilterController extends ActionController
|
||||
* @param String $text The query to parse
|
||||
* @return array The result structure to be returned in json format
|
||||
*/
|
||||
private function parse($text)
|
||||
private function parse($text, $target)
|
||||
{
|
||||
try {
|
||||
$view = HostStatus::fromRequest($this->getRequest());
|
||||
$urlParser = new UrlViewFilter($view);
|
||||
$queryTree = $this->registry->createQueryTreeForFilter($text);
|
||||
|
||||
$queryTree = $this->registry->createQueryTreeForFilter($text);
|
||||
$registry = $this->moduleRegistry;
|
||||
return array(
|
||||
'state' => 'success',
|
||||
'proposals' => $this->registry->getProposalsForQuery($text),
|
||||
'urlParam' => $urlParser->fromTree($queryTree)
|
||||
'urlParam' => $registry::getUrlForTarget($target, $queryTree)
|
||||
);
|
||||
} catch (\Exception $exc) {
|
||||
Logger::error($exc);
|
||||
|
@ -31,6 +31,8 @@
|
||||
/**
|
||||
* Helper to render main and detail contents into a container
|
||||
*/
|
||||
use Icinga\Application\Icinga;
|
||||
|
||||
class Zend_View_Helper_MainDetail extends Zend_View_Helper_Abstract
|
||||
{
|
||||
/**
|
||||
|
@ -31,10 +31,10 @@ $modules = $this->modules->paginate();
|
||||
<td>
|
||||
<? if ($module->enabled): ?>
|
||||
<i>{{OK_ICON}}</i>
|
||||
<a href="<?= $disableUrl ?>"><?= $this->escape($module->name); ?></a>
|
||||
<a href="<?= $disableUrl ?>" data-icinga-target="main"><?= $this->escape($module->name); ?></a>
|
||||
<? else: ?>
|
||||
<i>{{REMOVE_ICON}}</i>
|
||||
<a href="<?= $enableUrl ?>"><?= $this->escape($module->name); ?></a>
|
||||
<a href="<?= $enableUrl ?>" data-icinga-target="main"><?= $this->escape($module->name); ?></a>
|
||||
<? endif ?>
|
||||
(<?=
|
||||
$module->enabled ? ($module->loaded ? 'enabled' : 'failed') : 'disabled' ?>)
|
||||
|
@ -72,16 +72,10 @@ class Datasource implements DatasourceInterface
|
||||
return $this;
|
||||
}
|
||||
$result = array();
|
||||
$filters = $query->listFilters();
|
||||
|
||||
$columns = $query->getColumns();
|
||||
foreach ($this->data as & $row) {
|
||||
|
||||
// Skip rows that do not match applied filters
|
||||
foreach ($filters as $f) {
|
||||
if ($row->{$f[0]} !== $f[1]) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Get only desired columns if asked so
|
||||
if (empty($columns)) {
|
||||
|
@ -29,11 +29,41 @@
|
||||
|
||||
namespace Icinga\Filter;
|
||||
|
||||
/**
|
||||
* Interface for filterable data sources
|
||||
*/
|
||||
interface Filterable
|
||||
{
|
||||
/**
|
||||
* Return true when this field is filterable, otherwise false
|
||||
*
|
||||
* @param string $field The field to test for being filterable
|
||||
* @return boolean True when this field is filterable, otherwise false
|
||||
*/
|
||||
public function isValidFilterTarget($field);
|
||||
|
||||
/**
|
||||
* Return the internal, resolved name of the given field
|
||||
*
|
||||
* @param string $field The field to resolve
|
||||
* @return string The resolved name or null if the field is not resolvable
|
||||
*/
|
||||
public function getMappedField($field);
|
||||
|
||||
/**
|
||||
* Apply all filters of this filterable on the datasource
|
||||
*/
|
||||
public function applyFilter();
|
||||
|
||||
/**
|
||||
* Remove all filters from this datasource
|
||||
*/
|
||||
public function clearFilter();
|
||||
|
||||
/**
|
||||
* Add a filter to this datasource
|
||||
*
|
||||
* @param mixed $filter The filter to use
|
||||
*/
|
||||
public function addFilter($filter);
|
||||
}
|
||||
|
43
library/Icinga/Filter/Registry.php
Normal file
43
library/Icinga/Filter/Registry.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
|
||||
namespace Icinga\Filter;
|
||||
|
||||
|
||||
use Icinga\Filter\Query\Tree;
|
||||
|
||||
/**
|
||||
* Interface for filter registries
|
||||
* Class Registry
|
||||
* @package Icinga\Filter
|
||||
*/
|
||||
interface Registry
|
||||
{
|
||||
public static function getUrlForTarget($domain, Tree $filter);
|
||||
}
|
@ -106,23 +106,6 @@ class ActionController extends Zend_Controller_Action
|
||||
}
|
||||
}
|
||||
|
||||
private function dispatchDetailView($url)
|
||||
{
|
||||
// strip the base URL from the detail $url
|
||||
$url = substr($url, strlen($this->getRequest()->getBaseUrl()));
|
||||
// the host is mandatory, but ignored in Zend
|
||||
$req = new Request('http://ignoredhost/' . $url);
|
||||
$req->setUser($this->getRequest()->getUser());
|
||||
$req->setBaseUrl($this->getRequest()->getBaseUrl());
|
||||
$router = Zend_Controller_Front::getInstance()->getRouter();
|
||||
$router->route($req);
|
||||
Zend_Controller_Front::getInstance()->setRequest($req);
|
||||
$detailHtml = $this->view->action($req->getActionName(), $req->getControllerName(), $req->getModuleName());
|
||||
Zend_Controller_Front::getInstance()->setRequest($this->getRequest());
|
||||
$this->_helper->layout->assign('detailContent', $detailHtml);
|
||||
$this->_helper->layout->assign('detailClass', 'col-sm-12 col-xs-12 col-md-12 col-lg-6');
|
||||
$this->_helper->layout->assign('mainClass', 'col-sm-12 col-xs-12 col-md-12 col-lg-6');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the controller requires a login. That is when the controller requires authentication and the
|
||||
@ -223,30 +206,9 @@ class ActionController extends Zend_Controller_Action
|
||||
Benchmark::measure('Action::postDispatch()');
|
||||
|
||||
if ($this->_request->isXmlHttpRequest()) {
|
||||
$this->_helper->layout()->setLayout('body');
|
||||
$target = ($this->getParam('render') === 'detail') ? 'inline' : 'body';
|
||||
$this->_helper->layout()->setLayout($target);
|
||||
}
|
||||
|
||||
if ($this->getParam('detail', false)) {
|
||||
$detail = $this->getParam('detail');
|
||||
|
||||
// Zend uses the GET variables when calling getParam, therefore we have to persist the params,
|
||||
// clear the $_GET array, call the detail view with the url set in $detail and afterwards recreate
|
||||
// the $_GET array. If this is not done the following issues occur:
|
||||
//
|
||||
// - A stackoverflow issue due to infinite nested calls of buildDetailView (as the detailview has the same
|
||||
// postDispatch method) when 'detail' is not set to null
|
||||
//
|
||||
// - Params (like filters in the URL) from the detail view would be applied on all links of the master view
|
||||
// as those would be in the $_GET array after building the detail view. E.g. if you have a grid in the
|
||||
// master and a detail view filtering showing one host in detail, the pagination links of the grid would
|
||||
// contain the host filter of the detail view
|
||||
//
|
||||
$params = $_GET;
|
||||
$_GET['detail'] = null;
|
||||
$this->dispatchDetailView($detail);
|
||||
$_GET = $params;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,4 +62,6 @@ class Request extends Zend_Controller_Request_Http
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ class FilterBadgeRenderer implements Widget
|
||||
$url = $this->urlFilter->fromTree($newTree);
|
||||
$url = $basePath . (empty($allParams) ? '?' : '&') . $url;
|
||||
|
||||
return ' <a class="btn btn-default btn-xs" href="' . $url . '">'
|
||||
return ' <a class="filter-badge btn btn-default btn-xs" href="' . $url . '">'
|
||||
. $this->conjunctionCellar . ' '
|
||||
. ucfirst($node->left) . ' '
|
||||
. $node->operator . ' '
|
||||
|
@ -99,10 +99,10 @@ EOT;
|
||||
$form->setTokenDisabled();
|
||||
$form->addElement(
|
||||
'text',
|
||||
'filter',
|
||||
'query',
|
||||
array(
|
||||
'label' => 'Filter Results',
|
||||
'name' => 'filter',
|
||||
'name' => 'query',
|
||||
'data-icinga-component' => 'app/semanticsearch',
|
||||
'data-icinga-filter-domain' => $this->domain,
|
||||
'data-icinga-filter-module' => $this->module
|
||||
@ -111,7 +111,6 @@ EOT;
|
||||
$form->removeAttrib('data-icinga-component');
|
||||
|
||||
$form->setIgnoreChangeDiscarding(true);
|
||||
|
||||
$badges = new FilterBadgeRenderer($this->initialFilter);
|
||||
$html = str_replace('{{FORM}}', $form->render($view), self::$TPL);
|
||||
return str_replace('{{BADGES}}', $badges->render($view), $html);
|
||||
|
@ -34,6 +34,7 @@ use Zend_View_Abstract;
|
||||
use Icinga\Web\Form\Decorator\ConditionalHidden;
|
||||
use Zend_Form_Element_Submit;
|
||||
|
||||
|
||||
/**
|
||||
* Sortbox widget
|
||||
*
|
||||
@ -77,7 +78,7 @@ class SortBox implements Widget
|
||||
/**
|
||||
* A request object used for initial form population
|
||||
*
|
||||
* @var Icinga\Web\Request
|
||||
* @var \Icinga\Web\Request
|
||||
*/
|
||||
private $request;
|
||||
|
||||
@ -166,6 +167,7 @@ class SortBox implements Widget
|
||||
$form->addElement($this->createFallbackSubmitButton());
|
||||
|
||||
if ($this->request) {
|
||||
$form->setAction($this->request->getRequestUri());
|
||||
$form->populate($this->request->getParams());
|
||||
}
|
||||
return $form->render($view);
|
||||
|
@ -221,7 +221,7 @@ class Tab implements Widget
|
||||
*/
|
||||
public function render(Zend_View_Abstract $view)
|
||||
{
|
||||
$class = $this->active ? ' class="active"' : '';
|
||||
$class = $this->active ? ' class="active" ' : '';
|
||||
$caption = $this->title;
|
||||
|
||||
if ($this->icon !== null) {
|
||||
@ -238,7 +238,7 @@ class Tab implements Widget
|
||||
$tagParams .= ' ' . $key . '="' . $value . '"';
|
||||
}
|
||||
}
|
||||
$tab = '<a' . $tagParams .' href="' . $this->url->getAbsoluteUrl() . '">' . $caption . '</a>';
|
||||
$tab = '<a' . $tagParams .' href="' . $this->url->getAbsoluteUrl() . '" data-icinga-target="self">' . $caption . '</a>';
|
||||
} else {
|
||||
$tab = $caption;
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ use Icinga\Module\Monitoring\DataView\Comment as CommentView;
|
||||
use Icinga\Module\Monitoring\DataView\Groupsummary as GroupsummaryView;
|
||||
use Icinga\Module\Monitoring\DataView\EventHistory as EventHistoryView;
|
||||
use Icinga\Module\Monitoring\Filter\UrlViewFilter;
|
||||
use Icinga\Module\Monitoring\DataView\ServiceStatus;
|
||||
use Icinga\Filter\Filterable;
|
||||
|
||||
class Monitoring_ListController extends MonitoringController
|
||||
@ -131,7 +132,8 @@ class Monitoring_ListController extends MonitoringController
|
||||
)
|
||||
);
|
||||
$query = $dataview->getQuery();
|
||||
$this->setupFilterControl($dataview);
|
||||
$this->setupFilterControl($dataview, 'host');
|
||||
|
||||
$this->setupSortControl(array(
|
||||
'host_last_check' => 'Last Host Check',
|
||||
'host_severity' => 'Host Severity',
|
||||
@ -152,6 +154,8 @@ class Monitoring_ListController extends MonitoringController
|
||||
{
|
||||
$this->compactView = 'services-compact';
|
||||
$this->view->services = $this->fetchServices();
|
||||
|
||||
$this->setupFilterControl(ServiceStatus::fromRequest($this->getRequest()), 'service');
|
||||
$this->setupSortControl(array(
|
||||
'service_last_check' => 'Last Service Check',
|
||||
'service_severity' => 'Severity',
|
||||
@ -437,12 +441,12 @@ class Monitoring_ListController extends MonitoringController
|
||||
$this->view->sortControl->applyRequest($this->getRequest());
|
||||
}
|
||||
|
||||
private function setupFilterControl(Filterable $dataview)
|
||||
private function setupFilterControl(Filterable $dataview, $domain)
|
||||
{
|
||||
$parser = new UrlViewFilter($dataview);
|
||||
$this->view->filterBox = new FilterBox(
|
||||
$parser->parseUrl(),
|
||||
'host',
|
||||
$parser->fromRequest($this->getRequest()),
|
||||
$domain,
|
||||
'monitoring'
|
||||
);
|
||||
|
||||
|
@ -55,7 +55,16 @@ class Monitoring_ShowController extends MonitoringController
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->view->object = AbstractObject::fromRequest($this->getRequest());
|
||||
|
||||
if ($this->getRequest()->getActionName() === 'host') {
|
||||
$this->view->object = new Host($this->getRequest());
|
||||
} elseif ($this->getRequest()->getActionName() === 'service') {
|
||||
$this->view->object = new Service($this->getRequest());
|
||||
|
||||
} else {
|
||||
$this->view->object = AbstractObject::fromRequest($this->getRequest());
|
||||
}
|
||||
|
||||
$this->createTabs();
|
||||
}
|
||||
|
||||
|
@ -5,8 +5,19 @@ $viewHelper = $this->getHelper('MonitoringState');
|
||||
<?= $this->tabs->render($this); ?>
|
||||
<h1>Services Status</h1>
|
||||
<div data-icinga-component="app/mainDetailGrid">
|
||||
<?= $this->sortControl->render($this); ?>
|
||||
<?= $this->paginationControl($services, null, null, array('preserve' => $this->preserve)); ?>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<?= $this->filterBox->render($this); ?>
|
||||
</div>
|
||||
<div class="col-md-7">
|
||||
<?= $this->sortControl->render($this); ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<?= $this->paginationControl($services, null, null, array('preserve' => $this->preserve)); ?>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-condensed">
|
||||
<tbody>
|
||||
<?php foreach ($services as $service): ?>
|
||||
|
@ -13,6 +13,7 @@ use Icinga\Web\Controller\ActionController;
|
||||
*/
|
||||
class Controller extends ActionController
|
||||
{
|
||||
|
||||
/**
|
||||
* Retrieve services from either given parameters or request
|
||||
*
|
||||
|
@ -74,7 +74,7 @@ abstract class DataView implements Filterable
|
||||
|
||||
$view = new static(Backend::createBackend($request->getParam('backend')), $columns);
|
||||
$parser = new UrlViewFilter($view);
|
||||
$view->getQuery()->setFilter($parser->parseUrl());
|
||||
$view->getQuery()->setFilter($parser->fromRequest($request));
|
||||
|
||||
$order = $request->getParam('dir');
|
||||
if ($order !== null) {
|
||||
@ -102,8 +102,11 @@ abstract class DataView implements Filterable
|
||||
public static function fromParams(array $params, array $columns = null)
|
||||
{
|
||||
$view = new static(Backend::createBackend($params['backend']), $columns);
|
||||
|
||||
foreach ($params as $key => $value) {
|
||||
$view->getQuery()->where($key, $value);
|
||||
if ($view->isValidFilterTarget($key)) {
|
||||
$view->getQuery()->where($key, $value);
|
||||
}
|
||||
}
|
||||
$order = isset($params['order']) ? $params['order'] : null;
|
||||
if ($order !== null) {
|
||||
|
@ -28,20 +28,35 @@
|
||||
|
||||
namespace Icinga\Module\Monitoring\Filter;
|
||||
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Application\Logger;
|
||||
use Icinga\Filter\Domain;
|
||||
use Icinga\Filter\FilterAttribute;
|
||||
use Icinga\Filter\Query\Node;
|
||||
use Icinga\Filter\Query\Tree;
|
||||
use Icinga\Filter\Type\BooleanFilter;
|
||||
use Icinga\Filter\Type\TextFilter;
|
||||
use Icinga\Filter\Type\TimeRangeSpecifier;
|
||||
use Icinga\Module\Monitoring\DataView\HostStatus;
|
||||
use Icinga\Module\Monitoring\DataView\ServiceStatus;
|
||||
use Icinga\Module\Monitoring\Filter\Type\StatusFilter;
|
||||
use Icinga\Filter\Registry as FilterRegistry;
|
||||
use Icinga\Module\Monitoring\Object\Host;
|
||||
use Icinga\Web\Request;
|
||||
use Zend_Controller_Request_Exception;
|
||||
use Icinga\Web\Url;
|
||||
|
||||
/**
|
||||
* Factory class to create filter for different monitoring objects
|
||||
*
|
||||
*/
|
||||
class Registry
|
||||
class Registry implements FilterRegistry
|
||||
{
|
||||
/**
|
||||
* Return a TimeRangeSpecifier for the 'Next Check' query
|
||||
*
|
||||
* @return TimeRangeSpecifier
|
||||
*/
|
||||
public static function getNextCheckFilterType()
|
||||
{
|
||||
$type = new TimeRangeSpecifier();
|
||||
@ -54,6 +69,11 @@ class Registry
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a TimeRangeSpecifier for the 'Last Check' query
|
||||
*
|
||||
* @return TimeRangeSpecifier
|
||||
*/
|
||||
public static function getLastCheckFilterType()
|
||||
{
|
||||
$type = new TimeRangeSpecifier();
|
||||
@ -68,6 +88,11 @@ class Registry
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registry function for the host domain
|
||||
*
|
||||
* @return Domain the domain to use in the filter registry
|
||||
*/
|
||||
public static function hostFilter()
|
||||
{
|
||||
$domain = new Domain('Host');
|
||||
@ -78,7 +103,6 @@ class Registry
|
||||
->setField('host_name')
|
||||
)->registerAttribute(
|
||||
FilterAttribute::create(StatusFilter::createForHost())
|
||||
->setHandledAttributes('State', 'Status', 'Current Status')
|
||||
->setField('host_state')
|
||||
)->registerAttribute(
|
||||
FilterAttribute::create(new BooleanFilter(
|
||||
@ -103,4 +127,104 @@ class Registry
|
||||
);
|
||||
return $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registry function for the service domain
|
||||
*
|
||||
* @return Domain the domain to use in the filter registry
|
||||
*/
|
||||
public static function serviceFilter()
|
||||
{
|
||||
$domain = new Domain('Service');
|
||||
|
||||
$domain->registerAttribute(
|
||||
FilterAttribute::create(new TextFilter())
|
||||
->setHandledAttributes('Name', 'Servicename')
|
||||
->setField('service_name')
|
||||
)->registerAttribute(
|
||||
FilterAttribute::create(StatusFilter::createForService())
|
||||
->setField('service_state')
|
||||
)->registerAttribute(
|
||||
FilterAttribute::create(StatusFilter::createForHost())
|
||||
->setHandledAttributes('Host')
|
||||
->setField('host_state')
|
||||
)->registerAttribute(
|
||||
FilterAttribute::create(new BooleanFilter(
|
||||
array(
|
||||
'service_is_flapping' => 'Flapping',
|
||||
'service_problem' => 'In Problem State',
|
||||
'service_notifications_enabled' => 'Sending Notifications',
|
||||
'service_active_checks_enabled' => 'Active',
|
||||
'service_passive_checks_enabled' => 'Accepting Passive Checks',
|
||||
'service_handled' => 'Handled',
|
||||
'service_in_downtime' => 'In Downtime',
|
||||
'host_in_downtime' => 'In Host Downtime'
|
||||
)
|
||||
))
|
||||
)->registerAttribute(
|
||||
FilterAttribute::create(self::getLastCheckFilterType())
|
||||
->setHandledAttributes('Last Check', 'Check')
|
||||
->setField('service_last_check')
|
||||
)->registerAttribute(
|
||||
FilterAttribute::create(self::getNextCheckFilterType())
|
||||
->setHandledAttributes('Next Check')
|
||||
->setField('service_next_check')
|
||||
);
|
||||
return $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the given filter to an url, using the referer as the base url and base filter
|
||||
*
|
||||
* @param $domain The domain to filter for
|
||||
* @param Tree $filter The tree representing the fiter
|
||||
*
|
||||
* @return string An url
|
||||
* @throws Zend_Controller_Request_Exception Called if no referer is available
|
||||
*/
|
||||
public static function getUrlForTarget($domain, Tree $filter)
|
||||
{
|
||||
if (!isset($_SERVER['HTTP_REFERER'])) {
|
||||
throw new Zend_Controller_Request_Exception('You can\'t use this method without an referer');
|
||||
}
|
||||
$request = Icinga::app()->getFrontController()->getRequest();
|
||||
switch ($domain) {
|
||||
case 'host':
|
||||
$view = HostStatus::fromRequest($request);
|
||||
break;
|
||||
case 'service':
|
||||
$view = ServiceStatus::fromRequest($request);
|
||||
break;
|
||||
default:
|
||||
Logger::error('Invalid filter domain requested : %s', $domain);
|
||||
throw new Exception('Unknown Domain ' . $domain);
|
||||
}
|
||||
$urlParser = new UrlViewFilter($view);
|
||||
$lastQuery = parse_url($_SERVER['HTTP_REFERER'], PHP_URL_QUERY);
|
||||
$lastPath = parse_url($_SERVER['HTTP_REFERER'], PHP_URL_PATH);
|
||||
$lastFilter = $urlParser->parseUrl($lastQuery);
|
||||
$lastParameters = array();
|
||||
|
||||
parse_str($lastQuery, $lastParameters);
|
||||
if ($lastFilter->root) {
|
||||
$filter->insert($lastFilter->root);
|
||||
}
|
||||
$params = array();
|
||||
foreach ($lastParameters as $key => $param) {
|
||||
if (!$filter->hasNodeWithAttribute($key)) {
|
||||
$params[$key] = $param;
|
||||
}
|
||||
}
|
||||
|
||||
$baseUrl = Url::fromPath($lastPath, $params);
|
||||
$urlString = $baseUrl->getRelativeUrl();
|
||||
if (stripos($urlString, '?') === false) {
|
||||
$urlString .= '?';
|
||||
} else {
|
||||
$urlString .= '&';
|
||||
}
|
||||
$urlString .= $urlParser->fromTree($filter);
|
||||
return '/' . $urlString;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -127,7 +127,6 @@ class StatusFilter extends FilterType
|
||||
public static function createForService()
|
||||
{
|
||||
$status = new StatusFilter();
|
||||
$status->setType(self::TYPE_SERVICE);
|
||||
$status->setBaseStates(
|
||||
array(
|
||||
'Ok' => 0,
|
||||
|
@ -33,6 +33,7 @@ namespace Icinga\Module\Monitoring\Filter;
|
||||
use Icinga\Filter\Filterable;
|
||||
use Icinga\Filter\Query\Tree;
|
||||
use Icinga\Filter\Query\Node;
|
||||
use Icinga\Web\Request;
|
||||
use Icinga\Web\Url;
|
||||
use Icinga\Application\Logger;
|
||||
|
||||
@ -114,6 +115,15 @@ class UrlViewFilter
|
||||
return $tree->getCopyForFilterable($this->target);
|
||||
}
|
||||
|
||||
public function fromRequest($request)
|
||||
{
|
||||
if($request->getParam('query')) {
|
||||
return $this->parseUrl(urldecode($request->getParam('query')));
|
||||
} else {
|
||||
return $this->parseUrl(parse_url($request->getBaseUrl(), PHP_URL_QUERY));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a tree node and it's subnodes to a request string
|
||||
*
|
||||
|
@ -52,6 +52,7 @@ abstract class AbstractObject
|
||||
$this->comments = Comment::fromRequest(
|
||||
$this->request,
|
||||
array(
|
||||
'comment_internal_id',
|
||||
'comment_timestamp',
|
||||
'comment_author',
|
||||
'comment_data',
|
||||
@ -59,6 +60,7 @@ abstract class AbstractObject
|
||||
)
|
||||
)->getQuery()
|
||||
->where('comment_objecttype_id', 1)
|
||||
|
||||
->fetchAll();
|
||||
|
||||
return $this;
|
||||
@ -175,7 +177,6 @@ abstract class AbstractObject
|
||||
|
||||
abstract public function populate();
|
||||
|
||||
|
||||
public static function fromRequest(Request $request)
|
||||
{
|
||||
if ($request->has('service') && $request->has('host')) {
|
||||
|
@ -74,7 +74,7 @@ define(['components/app/container', 'jquery'], function(Container, $) {
|
||||
}
|
||||
}).done(function() {
|
||||
var container = getOwnerContainer(form);
|
||||
container.replaceDomFromUrl(container.getContainerHref());
|
||||
container.setUrl(container.getUrl());
|
||||
}).error(function() {
|
||||
submit.removeAttr('disabled');
|
||||
});
|
||||
|
@ -26,11 +26,11 @@
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
define(['jquery', 'logging', 'icinga/componentLoader', 'URIjs/URI', 'URIjs/URITemplate'],
|
||||
function($, logger, componentLoader, URI) {
|
||||
define(['jquery', 'logging', 'icinga/componentLoader', 'URIjs/URI', 'URIjs/URITemplate', 'icinga/util/url'],
|
||||
function($, logger, componentLoader, URI, Tpl, urlMgr) {
|
||||
"use strict";
|
||||
|
||||
var Icinga;
|
||||
var Icinga;
|
||||
|
||||
/**
|
||||
* Enumeration of possible container types
|
||||
@ -57,6 +57,7 @@ define(['jquery', 'logging', 'icinga/componentLoader', 'URIjs/URI', 'URIjs/URITe
|
||||
*/
|
||||
var detailContainer = null;
|
||||
|
||||
var pendingDetailRequest = null;
|
||||
/**
|
||||
* A handler for accessing icinga containers, i.e. the #icingamain, #icingadetail containers and specific 'app/container'
|
||||
* components.
|
||||
@ -74,12 +75,6 @@ define(['jquery', 'logging', 'icinga/componentLoader', 'URIjs/URI', 'URIjs/URITe
|
||||
*/
|
||||
var Container = function(target) {
|
||||
|
||||
/**
|
||||
* Set to true when no history changes should be made
|
||||
*
|
||||
* @type {boolean} true to disable History.js calls, false to reenable them
|
||||
*/
|
||||
this.freezeHistory = false;
|
||||
|
||||
/**
|
||||
* Return the container that is at the nearest location to this element, or the element itself if it is a container
|
||||
@ -116,7 +111,6 @@ define(['jquery', 'logging', 'icinga/componentLoader', 'URIjs/URI', 'URIjs/URITe
|
||||
} else {
|
||||
this.containerType = CONTAINER_TYPES.GENERIC;
|
||||
}
|
||||
this.containerDom.attr('data-icinga-href', this.getContainerHref());
|
||||
|
||||
if (this.containerDom.data('loadIndicator') !== true) {
|
||||
this.installDefaultLoadIndicator();
|
||||
@ -124,6 +118,7 @@ define(['jquery', 'logging', 'icinga/componentLoader', 'URIjs/URI', 'URIjs/URITe
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the window without the hostname
|
||||
*
|
||||
@ -133,99 +128,6 @@ define(['jquery', 'logging', 'icinga/componentLoader', 'URIjs/URI', 'URIjs/URITe
|
||||
return window.location.pathname + window.location.search + window.location.hash;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract and return the main container's location from the current Url
|
||||
*
|
||||
* This takes the window's Url and removes the detail part
|
||||
*
|
||||
* @returns {string} The Url of the main container
|
||||
*/
|
||||
var getMainContainerHrefFromUrl = function(baseUrl) {
|
||||
// main has the url without the icingadetail part
|
||||
var href = URI(getWindowLocationWithoutHost(baseUrl));
|
||||
href.removeQuery('detail');
|
||||
return href.href();
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the detail container's location from the current Url
|
||||
*
|
||||
* This takes the detail parameter of the url and returns it or
|
||||
* undefined if no location is given
|
||||
*
|
||||
* @returns {string|undefined} The Url of the detail container or undefined if no detail container is active
|
||||
*/
|
||||
var getDetailContainerHrefFromUrl = function(baseUrl) {
|
||||
var location = new URI(baseUrl);
|
||||
var href = URI.parseQuery(location.query()).detail;
|
||||
if (!href) {
|
||||
return;
|
||||
}
|
||||
// detail is a query param, so it is possible that (due to a bug or whatever) multiple
|
||||
// detail fields are declared and returned as arrays
|
||||
if (typeof href !== 'string') {
|
||||
href = href[0];
|
||||
}
|
||||
// transform the detail parmameter to an Url
|
||||
return URI(href).href();
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the Url of this container
|
||||
*
|
||||
* This is mostly determined by the Url of the window, but for generic containers we have to rely on the
|
||||
* "data-icinga-href" attribute of the container (which is also available for main and detail, but less
|
||||
* reliable during history changes)
|
||||
*
|
||||
* @returns {String|undefined} The Url of the container or undefined if the container has no Url set
|
||||
*/
|
||||
this.getContainerHref = function(baseUrl) {
|
||||
baseUrl = baseUrl || getWindowLocationWithoutHost();
|
||||
switch (this.containerType) {
|
||||
case CONTAINER_TYPES.MAIN:
|
||||
return getMainContainerHrefFromUrl(baseUrl);
|
||||
case CONTAINER_TYPES.DETAIL:
|
||||
return getDetailContainerHrefFromUrl(baseUrl);
|
||||
case CONTAINER_TYPES.GENERIC:
|
||||
if (this.containerDom.attr('data-icinga-href')) {
|
||||
return URI(this.containerDom.attr('data-icinga-href'));
|
||||
} else {
|
||||
return URI(baseUrl).href();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a href with representing the current view, but url as the main container
|
||||
*
|
||||
* @param {URI} url The main Url to use as an URI.js object
|
||||
*
|
||||
* @returns {URI} The modified URI.js containing the new main and the current detail link
|
||||
*/
|
||||
var setMainContainerHref = function(url, baseUrl) {
|
||||
var detail = getDetailContainerHrefFromUrl(baseUrl);
|
||||
if (detail) {
|
||||
url.addQuery('detail', detail);
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a complete Href string representing the current detail href and the provided main Url
|
||||
*
|
||||
* @param {URI} url The detail Url to use as an URI.js object
|
||||
*
|
||||
* @returns {URI} The modified URI.js containing the new detail and the current main link
|
||||
*/
|
||||
var setDetailContainerHref = function(url, baseUrl) {
|
||||
var location = new URI(baseUrl);
|
||||
location.removeQuery('detail');
|
||||
if (typeof url !== 'undefined') { // no detail Url given
|
||||
location.addQuery('detail', url);
|
||||
}
|
||||
return location;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create default load mask
|
||||
*
|
||||
@ -234,7 +136,6 @@ define(['jquery', 'logging', 'icinga/componentLoader', 'URIjs/URI', 'URIjs/URITe
|
||||
var createDefaultLoadIndicator = function() {
|
||||
|
||||
this.showDetail();
|
||||
|
||||
if (this.containerDom.find('div.load-indicator').length === 0) {
|
||||
var content = '<div class="load-indicator">' +
|
||||
'<div class="mask"></div>' +
|
||||
@ -253,46 +154,6 @@ define(['jquery', 'logging', 'icinga/componentLoader', 'URIjs/URI', 'URIjs/URITe
|
||||
this.containerDom.find('div.load-indicator').remove();
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the Url of this container and let the Url reflect the new changes, if required
|
||||
*
|
||||
* This updates the window Url and the data-icinga-href attribute of the container. The latter one is required
|
||||
* to see which url is the last one the container displayed (e.g. after History changes, the url has changed
|
||||
* but the containers data-icinga-href still points to the containers element).
|
||||
*
|
||||
* @param {String|URI} url An Url string or a URI.js object representing the new Url for this container
|
||||
*
|
||||
* @return {String} url The new Url of the application (main and detail)
|
||||
*/
|
||||
this.updateContainerHref = function(url, baseUrl) {
|
||||
baseUrl = baseUrl || getWindowLocationWithoutHost();
|
||||
if (typeof url === "string") {
|
||||
url = URI(url);
|
||||
}
|
||||
var containerUrl, windowUrl;
|
||||
switch (this.containerType) {
|
||||
case CONTAINER_TYPES.MAIN:
|
||||
windowUrl = setMainContainerHref(url, baseUrl);
|
||||
containerUrl = windowUrl.clone().removeQuery('detail');
|
||||
break;
|
||||
case CONTAINER_TYPES.DETAIL:
|
||||
windowUrl = setDetailContainerHref(url, baseUrl);
|
||||
containerUrl = url;
|
||||
break;
|
||||
case CONTAINER_TYPES.GENERIC:
|
||||
containerUrl = url;
|
||||
windowUrl = baseUrl;
|
||||
break;
|
||||
}
|
||||
|
||||
if (containerUrl) {
|
||||
this.containerDom.attr('data-icinga-href', containerUrl);
|
||||
} else {
|
||||
this.containerDom.removeAttr('data-icinga-href');
|
||||
}
|
||||
|
||||
return windowUrl.href();
|
||||
};
|
||||
|
||||
/**
|
||||
* Load the provided url, stop all pending requests for this container and call replaceDom for the returned html
|
||||
@ -301,9 +162,59 @@ define(['jquery', 'logging', 'icinga/componentLoader', 'URIjs/URI', 'URIjs/URITe
|
||||
*
|
||||
* @param {String, URI} url The Url to load or and URI.js object encapsulating it
|
||||
*/
|
||||
this.replaceDomFromUrl = function(url) {
|
||||
this.updateFromUrl = function(url) {
|
||||
|
||||
if (this.containerType === CONTAINER_TYPES.DETAIL) {
|
||||
urlMgr.setDetailUrl(url);
|
||||
} else {
|
||||
urlMgr.setMainUrl(url);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
this.replaceDomAsync = function(url) {
|
||||
if (url === '') {
|
||||
this.containerDom.empty();
|
||||
this.hideDetail();
|
||||
return;
|
||||
}
|
||||
if (pendingDetailRequest) {
|
||||
pendingDetailRequest.abort();
|
||||
}
|
||||
this.containerDom.trigger('showLoadIndicator');
|
||||
Icinga.replaceBodyFromUrl(this.updateContainerHref(url));
|
||||
pendingDetailRequest = $.ajax({
|
||||
'url' : url,
|
||||
'data' : {
|
||||
'render' : 'detail'
|
||||
}
|
||||
}).done(
|
||||
(function(response) {
|
||||
this.replaceDom($(response));
|
||||
}).bind(this)
|
||||
).fail(
|
||||
(function(response, reason) {
|
||||
var errorReason;
|
||||
if (response.statusCode.toString()[0] === '4') {
|
||||
errorReason = 'The Requested View Couldn\'t Be Found<br/>';
|
||||
} else {
|
||||
errorReason = 'An Internal Error Occured';
|
||||
}
|
||||
this.replaceDom(
|
||||
$('<div class="alert alert-danger">').text(errorReason)
|
||||
);
|
||||
}).bind(this)
|
||||
).always((function() {
|
||||
this.containerDom.trigger('hideLoadIndicator');
|
||||
}).bind(this));
|
||||
};
|
||||
|
||||
this.getUrl = function() {
|
||||
if (this.containerType === CONTAINER_TYPES.DETAIL) {
|
||||
return urlMgr.detailUrl;
|
||||
} else {
|
||||
return urlMgr.mainUrl;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@ -377,6 +288,51 @@ define(['jquery', 'logging', 'icinga/componentLoader', 'URIjs/URI', 'URIjs/URITe
|
||||
this.containerDom.off('hideLoadIndicator');
|
||||
};
|
||||
|
||||
this.onLinkClick = function(ev, target) {
|
||||
if ($.trim($(target).attr('href')) === '#') {
|
||||
return true;
|
||||
}
|
||||
var url = URI($(target).attr('href'));
|
||||
var explicitTarget = $(target).attr('data-icinga-target');
|
||||
|
||||
var isHash = ('#' + url.fragment() === url.href());
|
||||
if (isHash) {
|
||||
|
||||
explicitTarget = this.containerType === CONTAINER_TYPES.MAIN ? 'main' : 'detail';
|
||||
}
|
||||
if (explicitTarget) {
|
||||
|
||||
urlMgr[{
|
||||
'main' : 'setMainUrl',
|
||||
'detail' : 'setDetailUrl',
|
||||
'self' : 'setUrl'
|
||||
}[explicitTarget]](url.href());
|
||||
|
||||
} else if (this.containerType === CONTAINER_TYPES.MAIN) {
|
||||
urlMgr.setDetailUrl(url.href());
|
||||
} else {
|
||||
urlMgr.setMainUrl(url.href());
|
||||
}
|
||||
|
||||
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
return false;
|
||||
|
||||
};
|
||||
|
||||
this.setUrl = function(url) {
|
||||
if (typeof url === 'string') {
|
||||
url = URI(url);
|
||||
}
|
||||
console.log(url);
|
||||
if (this.containerType === CONTAINER_TYPES.MAIN) {
|
||||
urlMgr.setMainUrl(url.href());
|
||||
} else {
|
||||
urlMgr.setDetailUrl(url.href());
|
||||
}
|
||||
}
|
||||
|
||||
this.construct(target);
|
||||
};
|
||||
|
||||
@ -389,12 +345,11 @@ define(['jquery', 'logging', 'icinga/componentLoader', 'URIjs/URI', 'URIjs/URITe
|
||||
* when the link should be catched and processed internally
|
||||
*/
|
||||
Container.isExternalLink = function(link) {
|
||||
if (link[0] === '#') {
|
||||
return true;
|
||||
}
|
||||
return (/^\/\//).test(URI(link).relativeTo(window.location.href).href());
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Return the page's detail container (which is always there)
|
||||
*
|
||||
@ -402,7 +357,7 @@ define(['jquery', 'logging', 'icinga/componentLoader', 'URIjs/URI', 'URIjs/URITe
|
||||
*/
|
||||
Container.getDetailContainer = function() {
|
||||
detailContainer = detailContainer || new Container('#icingadetail');
|
||||
if(!jQuery.contains(document.body, detailContainer)) {
|
||||
if(!jQuery.contains(document.body, mainContainer)) {
|
||||
detailContainer = new Container('#icingadetail');
|
||||
}
|
||||
return detailContainer;
|
||||
@ -450,6 +405,7 @@ define(['jquery', 'logging', 'icinga/componentLoader', 'URIjs/URI', 'URIjs/URITe
|
||||
* Available as a static method on the Container object or as an instance method
|
||||
*/
|
||||
Container.prototype.hideDetail = Container.hideDetail = function() {
|
||||
urlMgr.setDetailUrl('');
|
||||
var mainDom = Container.getMainContainer().containerDom,
|
||||
detailDom = Container.getDetailContainer().containerDom;
|
||||
|
||||
@ -464,47 +420,7 @@ define(['jquery', 'logging', 'icinga/componentLoader', 'URIjs/URI', 'URIjs/URITe
|
||||
mainDom.addClass('col-sm-12');
|
||||
detailDom.addClass('hidden-sm');
|
||||
detailDom.removeAttr('data-icinga-href');
|
||||
if (typeof this.freezeHistory === 'undefined' || !this.freezeHistory) {
|
||||
History.replaceState(
|
||||
{},
|
||||
document.title,
|
||||
URI(window.location.href).removeQuery('detail').href()
|
||||
);
|
||||
}
|
||||
};
|
||||
if (Modernizr.history) {
|
||||
/**
|
||||
* Register the click behaviour of the main container, which means that every link, if not catched in a
|
||||
* more specific handler, causes an update of the main container if it's not external or a browser behaviour link
|
||||
* (those starting with '#').
|
||||
*/
|
||||
$('body').on('click', '#icingamain, #icingadetail', function(ev) {
|
||||
|
||||
var targetEl = ev.target || ev.toElement || ev.relatedTarget;
|
||||
if (targetEl.tagName.toLowerCase() !== 'a') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Container.isExternalLink($(targetEl).attr('href'))) {
|
||||
return true;
|
||||
} else {
|
||||
if ($(targetEl).attr('data-icinga-target') === 'detail') {
|
||||
Icinga.replaceBodyFromUrl(
|
||||
detailContainer.updateContainerHref(URI($(targetEl).attr('href')).href())
|
||||
);
|
||||
} else {
|
||||
Icinga.replaceBodyFromUrl(
|
||||
mainContainer.updateContainerHref(URI($(targetEl).attr('href')).href())
|
||||
);
|
||||
}
|
||||
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects the icinga object into the Container class
|
||||
*
|
||||
@ -516,5 +432,28 @@ define(['jquery', 'logging', 'icinga/componentLoader', 'URIjs/URI', 'URIjs/URITe
|
||||
Icinga = icingaObj;
|
||||
};
|
||||
|
||||
$('body').on('click', '*[data-icinga-component="app/container"], #icingamain, #icingadetail', function(ev) {
|
||||
var targetEl = ev.target || ev.toElement || ev.relatedTarget;
|
||||
|
||||
if (targetEl.tagName.toLowerCase() !== 'a') {
|
||||
targetEl = $(targetEl).parents('a')[0];
|
||||
if (!targetEl) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return (new Container(targetEl)).onLinkClick(ev, targetEl);
|
||||
|
||||
});
|
||||
|
||||
$(window).on('hashchange', (function() {
|
||||
urlMgr.syncWithUrl();
|
||||
Container.getDetailContainer().replaceDomAsync(urlMgr.detailUrl);
|
||||
}));
|
||||
|
||||
|
||||
if (urlMgr.detailUrl) {
|
||||
Container.getDetailContainer().replaceDomAsync(urlMgr.detailUrl);
|
||||
}
|
||||
|
||||
return Container;
|
||||
});
|
||||
|
@ -25,8 +25,8 @@
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
define(['components/app/container', 'jquery', 'logging', 'URIjs/URI', 'URIjs/URITemplate'],
|
||||
function(Container, $, logger, URI) {
|
||||
define(['components/app/container', 'jquery', 'logging', 'URIjs/URI', 'URIjs/URITemplate', 'icinga/util/url'],
|
||||
function(Container, $, logger, URI, tpl, urlMgr) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
@ -141,7 +141,7 @@ function(Container, $, logger, URI) {
|
||||
}
|
||||
}
|
||||
|
||||
Container.getDetailContainer().replaceDomFromUrl($('a', this).attr('href'));
|
||||
urlMgr.setDetailUrl($('a', this).attr('href'));
|
||||
if (!ev.ctrlKey && !ev.metaKey) {
|
||||
$('tr', $(this).parent()).removeClass('active');
|
||||
}
|
||||
@ -160,24 +160,31 @@ function(Container, $, logger, URI) {
|
||||
controlForms.on('submit', function(evt) {
|
||||
var container = (new Container(this));
|
||||
var form = $(this);
|
||||
var url = URI(container.getContainerHref());
|
||||
url.search(URI.parseQuery(form.serialize()));
|
||||
container.replaceDomFromUrl(url.href());
|
||||
var url = container.getUrl();
|
||||
|
||||
if (url.indexOf('?') >= 0) {
|
||||
url += '&';
|
||||
} else {
|
||||
url += '?';
|
||||
}
|
||||
url += form.serialize();
|
||||
container.setUrl(url);
|
||||
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
return false;
|
||||
|
||||
});
|
||||
$('.pagination li a', contentNode.parent()).on('click', function(ev) {
|
||||
$('.pagination li a, a.filter-badge', contentNode.parent()).on('click', function(ev) {
|
||||
|
||||
var container = (new Container(this));
|
||||
logger.debug("Pagination clicked in " + container.containerType);
|
||||
|
||||
// Detail will be removed when main pagination changes
|
||||
if (container.containerType === 'icingamain') {
|
||||
Icinga.replaceBodyFromUrl(URI($(this).attr('href')).removeQuery('detail'));
|
||||
urlMgr.setMainUrl(URI($(this).attr('href')));
|
||||
urlMgr.setDetailUrl('');
|
||||
} else {
|
||||
container.replaceDomFromUrl($(this).attr('href'));
|
||||
urlMgr.setDetailUrl(URI($(this).attr('href')));
|
||||
}
|
||||
|
||||
ev.preventDefault();
|
||||
@ -187,7 +194,7 @@ function(Container, $, logger, URI) {
|
||||
};
|
||||
|
||||
var getSelectedRows = function() {
|
||||
return $('a[href="' + Container.getDetailContainer().getContainerHref() + '"]', determineContentTable()).
|
||||
return $('a[href="' + urlMgr.getDetailUrl() + '"]', determineContentTable()).
|
||||
parentsUntil('table', 'tr');
|
||||
};
|
||||
|
||||
@ -214,9 +221,11 @@ function(Container, $, logger, URI) {
|
||||
this.container.removeDefaultLoadIndicator();
|
||||
controlForms = determineControlForms();
|
||||
contentNode = determineContentTable();
|
||||
this.syncSelectionWithDetail();
|
||||
this.registerControls();
|
||||
this.registerTableLinks();
|
||||
this.registerHistoryChanges();
|
||||
|
||||
};
|
||||
|
||||
this.construct(gridDomNode);
|
||||
|
@ -53,8 +53,7 @@ define(['jquery', 'logging', 'URIjs/URI', 'components/app/container'], function(
|
||||
* Request new proposals for the given input box
|
||||
*/
|
||||
this.getProposal = function() {
|
||||
var text = this.inputDom.val().trim();
|
||||
|
||||
var text = $.trim(this.inputDom.val());
|
||||
|
||||
if (this.pendingRequest) {
|
||||
this.pendingRequest.abort();
|
||||
@ -114,9 +113,9 @@ define(['jquery', 'logging', 'URIjs/URI', 'components/app/container'], function(
|
||||
return {
|
||||
data: {
|
||||
'cache' : (new Date()).getTime(),
|
||||
'query' : query,
|
||||
'filter_domain' : this.domain,
|
||||
'filter_module' : this.module
|
||||
'query' : query,
|
||||
'filter_domain' : this.domain,
|
||||
'filter_module' : this.module
|
||||
},
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
@ -131,7 +130,8 @@ define(['jquery', 'logging', 'URIjs/URI', 'components/app/container'], function(
|
||||
* @param {Object} response The jquery response object inheritn XHttpResponse Attributes
|
||||
*/
|
||||
this.showProposals = function(response) {
|
||||
if (response.proposals.length === 0) {
|
||||
|
||||
if (!response || !response.proposals || response.proposals.length === 0) {
|
||||
this.inputDom.popover('destroy');
|
||||
return;
|
||||
}
|
||||
@ -167,10 +167,12 @@ define(['jquery', 'logging', 'URIjs/URI', 'components/app/container'], function(
|
||||
var query = $.trim(this.inputDom.val());
|
||||
this.pendingRequest = $.ajax(this.getRequestParams(query))
|
||||
.done((function(response) {
|
||||
var container = new Container($(this.inputDom));
|
||||
var url = container.getContainerHref();
|
||||
url += ( url.indexOf('?') === -1 ? '?' : '&' ) + response.urlParam;
|
||||
container.replaceDomFromUrl(url);
|
||||
var domContainer = new Container(this.inputDom);
|
||||
var url = response.urlParam;
|
||||
|
||||
if (url) {
|
||||
domContainer.setUrl(url);
|
||||
}
|
||||
}).bind(this));
|
||||
};
|
||||
|
||||
|
@ -31,90 +31,94 @@ define([
|
||||
'logging',
|
||||
'icinga/componentLoader',
|
||||
'components/app/container',
|
||||
'URIjs/URI'
|
||||
], function ($, log, components, Container, URI) {
|
||||
'URIjs/URI',
|
||||
'icinga/util/url'
|
||||
], function ($, log, components, Container, URI, urlMgr) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Icinga prototype
|
||||
*/
|
||||
var Icinga = function() {
|
||||
var pendingRequest = null;
|
||||
|
||||
var ignoreHistoryChanges = false;
|
||||
|
||||
/**
|
||||
* Initia
|
||||
*/
|
||||
var initialize = function () {
|
||||
components.load();
|
||||
ignoreHistoryChanges = true;
|
||||
registerGenericHistoryHandler();
|
||||
ignoreHistoryChanges = false;
|
||||
log.debug("Initialization finished");
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Register handler for handling the history state generically
|
||||
* Globally open the given url and reload the main/detail box to represent it
|
||||
*
|
||||
* @param url The url to load
|
||||
*/
|
||||
var registerGenericHistoryHandler = function() {
|
||||
var lastUrl = URI(window.location.href);
|
||||
History.Adapter.bind(window, 'popstate', function() {
|
||||
if (ignoreHistoryChanges) {
|
||||
this.openUrl = function(url) {
|
||||
if (pendingRequest) {
|
||||
pendingRequest.abort();
|
||||
}
|
||||
pendingRequest = $.ajax({
|
||||
"url": url
|
||||
}).done(function(response) {
|
||||
var dom = $(response);
|
||||
var detailDom = null;
|
||||
if (urlMgr.detailUrl) {
|
||||
detailDom = $('#icingadetail');
|
||||
}
|
||||
$(document.body).empty().append(dom);
|
||||
if (detailDom && detailDom.length) {
|
||||
$('#icingadetail').replaceWith(detailDom);
|
||||
Container.showDetail();
|
||||
}
|
||||
components.load();
|
||||
Container.getMainContainer();
|
||||
}).fail(function(response, reason) {
|
||||
if (reason === 'abort') {
|
||||
return;
|
||||
}
|
||||
|
||||
gotoUrl(History.getState().url);
|
||||
lastUrl = URI(window.location.href);
|
||||
log.error("Request failed: ", response.message);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
var gotoUrl = function(href) {
|
||||
if (typeof document.body.pending !== 'undefined') {
|
||||
document.body.pending.abort();
|
||||
}
|
||||
if (typeof href === 'string') {
|
||||
href = URI(href);
|
||||
}
|
||||
document.body.pending = $.ajax({
|
||||
url: href.href()
|
||||
}).done(function(domNodes) {
|
||||
$('body').empty().append(jQuery.parseHTML(domNodes));
|
||||
ignoreHistoryChanges = true;
|
||||
History.pushState({}, document.title, href.href());
|
||||
ignoreHistoryChanges = false;
|
||||
components.load();
|
||||
}).error(function(xhr, textStatus, errorThrown) {
|
||||
if (xhr.responseText) {
|
||||
$('body').empty().append(jQuery.parseHTML(xhr.responseText));
|
||||
} else if (textStatus !== 'abort') {
|
||||
logging.emergency('Could not load URL', xhr.href, textStatus, errorThrown);
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
if (Modernizr.history) {
|
||||
$(document.body).on('click', '#icinganavigation', function(ev) {
|
||||
var targetEl = ev.target || ev.toElement || ev.relatedTarget;
|
||||
if (targetEl.tagName.toLowerCase() !== 'a') {
|
||||
return true;
|
||||
/**
|
||||
* Event handler that will be called when the url change
|
||||
*/
|
||||
urlMgr.syncWithUrl();
|
||||
var lastMain = urlMgr.mainUrl;
|
||||
$(window).on('pushstate', (function() {
|
||||
urlMgr.syncWithUrl();
|
||||
if (urlMgr.mainUrl !== lastMain) {
|
||||
this.openUrl(urlMgr.getUrl());
|
||||
lastMain = urlMgr.mainUrl;
|
||||
}
|
||||
// If an anchor is set, scroll to it's position
|
||||
if ($('#' + urlMgr.anchor).length) {
|
||||
$(document.body).scrollTo($('#' + urlMgr.anchor));
|
||||
}
|
||||
}).bind(this));
|
||||
|
||||
/**
|
||||
* Event handler for browser back/forward events
|
||||
*/
|
||||
$(window).on('popstate', (function() {
|
||||
var lastMain = urlMgr.mainUrl;
|
||||
urlMgr.syncWithUrl();
|
||||
if (urlMgr.mainUrl !== lastMain) {
|
||||
this.openUrl(urlMgr.getUrl());
|
||||
}
|
||||
|
||||
var href = $(targetEl).attr('href');
|
||||
if (Container.isExternalLink(href)) {
|
||||
return true;
|
||||
}
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
gotoUrl(href);
|
||||
return false;
|
||||
});
|
||||
}).bind(this));
|
||||
}
|
||||
|
||||
$(document).ready(initialize.bind(this));
|
||||
Container.setIcinga(this);
|
||||
|
||||
this.components = components;
|
||||
this.replaceBodyFromUrl = gotoUrl;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
194
public/js/icinga/util/url.js
Normal file
194
public/js/icinga/util/url.js
Normal file
@ -0,0 +1,194 @@
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
/**
|
||||
* This file is part of Icinga 2 Web.
|
||||
*
|
||||
* Icinga 2 Web - Head for multiple monitoring backends.
|
||||
* Copyright (C) 2013 Icinga Development Team
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* @copyright 2013 Icinga Development Team <info@icinga.org>
|
||||
* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2
|
||||
* @author Icinga Development Team <info@icinga.org>
|
||||
*/
|
||||
// {{{ICINGA_LICENSE_HEADER}}}
|
||||
|
||||
define(['jquery', 'URIjs/URI'], function($, URI, Container) {
|
||||
"use strict";
|
||||
|
||||
var currentUrl = URI(window.location.href);
|
||||
|
||||
/**
|
||||
* Utility class for Url handling
|
||||
*
|
||||
*/
|
||||
var URLMgr = function() {
|
||||
/**
|
||||
* The current url of the main part
|
||||
* @type {string}
|
||||
*/
|
||||
this.mainUrl = '';
|
||||
|
||||
/**
|
||||
* The current main anchor
|
||||
* @type {string}
|
||||
*/
|
||||
this.anchor = '';
|
||||
|
||||
/**
|
||||
* The current detail url
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
this.detailUrl = '';
|
||||
|
||||
/**
|
||||
* The current anchor of the detail url
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
this.detailAnchor = '';
|
||||
|
||||
/**
|
||||
* Extract the anchor of the main url part from the given url
|
||||
*
|
||||
* @param {String|URI} url An URL object to extract the information from
|
||||
* @returns {*}
|
||||
*/
|
||||
this.getMainAnchor = function(url) {
|
||||
url = url || URI(window.location.href);
|
||||
if (typeof url === 'string') {
|
||||
url = URI(url);
|
||||
}
|
||||
var fragment = url.fragment();
|
||||
if (fragment.length === 0) {
|
||||
return '';
|
||||
}
|
||||
var parts = fragment.split('!');
|
||||
if (parts.length > 0) {
|
||||
return parts[0];
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract the detail url a the given url. Returns a [URL, ANCHOR] Tupel
|
||||
*
|
||||
* @param String url An optional url to parse (otherwise window.location.href is used)
|
||||
* @returns {Array} A [{String} Url, {String} anchor] tupel
|
||||
*/
|
||||
this.getDetailUrl = function(url) {
|
||||
url = url || URI(window.location.href);
|
||||
if (typeof url === 'string') {
|
||||
url = URI(url);
|
||||
}
|
||||
|
||||
var fragment = url.fragment();
|
||||
if (fragment.length === 0) {
|
||||
return '';
|
||||
}
|
||||
var parts = fragment.split('!', 2);
|
||||
|
||||
if (parts.length === 2) {
|
||||
var result = /detail=(.*)$/.exec(parts[1]);
|
||||
if (!result || result.length < 2) {
|
||||
return '';
|
||||
}
|
||||
return result[1].replace('%23', '#').split('#');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Overwrite the detail Url and update the hash
|
||||
*
|
||||
* @param String url The url to use for the detail part
|
||||
*/
|
||||
this.setDetailUrl = function(url) {
|
||||
if (typeof url === 'string') {
|
||||
url = URI(url);
|
||||
}
|
||||
if( !url.fragment() || url.href() !== '#' + url.fragment()) {
|
||||
this.detailUrl = url.clone().fragment('').href();
|
||||
}
|
||||
this.detailAnchor = this.getMainAnchor(url);
|
||||
|
||||
window.location.hash = this.getUrlHash();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the hash of the current detail url and anchor i
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
this.getUrlHash = function() {
|
||||
var anchor = '#' + this.anchor +
|
||||
'!' + ($.trim(this.detailUrl) ? 'detail=' : '') + this.detailUrl +
|
||||
(this.detailAnchor ? '%23' : '') + this.detailAnchor;
|
||||
anchor = $.trim(anchor);
|
||||
if (anchor === '#!' || anchor === '#') {
|
||||
anchor = '';
|
||||
}
|
||||
return anchor;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the main url to be used
|
||||
*
|
||||
* This triggers the pushstate event or causes a page reload if the history api is
|
||||
* not available
|
||||
*
|
||||
* @param url
|
||||
*/
|
||||
this.setMainUrl = function(url) {
|
||||
this.anchor = this.getMainAnchor(url);
|
||||
this.mainUrl = URI(url).clone().fragment('').href();
|
||||
if (!Modernizr.history) {
|
||||
window.location.href = this.mainUrl + this.getUrlHash();
|
||||
} else {
|
||||
window.history.pushState({}, document.title, this.mainUrl + this.getUrlHash());
|
||||
$(window).trigger('pushstate');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the href (main path + hash)
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
this.getUrl = function() {
|
||||
return this.mainUrl + this.getUrlHash();
|
||||
};
|
||||
|
||||
/**
|
||||
* Take the current url and sync the internal state of this url manager with it
|
||||
*/
|
||||
this.syncWithUrl = function() {
|
||||
this.mainUrl = URI(window.location.href).clone().fragment('').href();
|
||||
this.anchor = this.getMainAnchor();
|
||||
var urlAnchorTupel = this.getDetailUrl();
|
||||
this.detailUrl = urlAnchorTupel[0] || '';
|
||||
this.detailAnchor = urlAnchorTupel[1] || '';
|
||||
};
|
||||
|
||||
|
||||
this.syncWithUrl();
|
||||
};
|
||||
var urlMgr = new URLMgr();
|
||||
|
||||
return urlMgr;
|
||||
});
|
@ -3,9 +3,8 @@ requirejs.config({
|
||||
'urlArgs': "bust=" + (new Date()).getTime(),
|
||||
'paths': {
|
||||
'jquery': 'vendor/jquery-1.8.3',
|
||||
'jqueryPlugins': 'vendor/jqueryPlugins/',
|
||||
'jquery_scrollto': 'vendor/jquery.scrollto',
|
||||
'bootstrap': 'vendor/bootstrap/bootstrap.min',
|
||||
'history': 'vendor/history',
|
||||
'logging': 'icinga/util/logging',
|
||||
'URIjs': 'vendor/uri',
|
||||
'datetimepicker': 'vendor/bootstrap/datetimepicker.min'
|
||||
@ -14,21 +13,23 @@ requirejs.config({
|
||||
'datetimepicker': {
|
||||
'exports': 'datetimepicker'
|
||||
},
|
||||
'jquery_scrollto': {
|
||||
exports: 'jquery_scrollto'
|
||||
},
|
||||
'jquery' : {
|
||||
exports: 'jquery'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
define(['jquery', 'history'], function ($) {
|
||||
define(['jquery'], function ($, history) {
|
||||
window.$ = $;
|
||||
window.jQuery = $;
|
||||
|
||||
requirejs(['bootstrap'], function() {
|
||||
requirejs(['bootstrap','jquery_scrollto'], function() {
|
||||
requirejs(['datetimepicker']);
|
||||
});
|
||||
|
||||
requirejs(['icinga/icinga'], function (Icinga) {
|
||||
window.$ = $;
|
||||
window.jQuery = $;
|
||||
window.Icinga = Icinga;
|
||||
});
|
||||
|
||||
|
2
public/js/vendor/history.js
vendored
2
public/js/vendor/history.js
vendored
File diff suppressed because one or more lines are too long
8
public/js/vendor/jquery.scrollto.js
vendored
Normal file
8
public/js/vendor/jquery.scrollto.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2007-2013 Ariel Flesler - aflesler<a>gmail<d>com | http://flesler.blogspot.com
|
||||
* Dual licensed under MIT and GPL.
|
||||
* @author Ariel Flesler
|
||||
* @version 1.4.6
|
||||
*/
|
||||
;(function($){var h=$.scrollTo=function(a,b,c){$(window).scrollTo(a,b,c)};h.defaults={axis:'xy',duration:parseFloat($.fn.jquery)>=1.3?0:1,limit:true};h.window=function(a){return $(window)._scrollable()};$.fn._scrollable=function(){return this.map(function(){var a=this,isWin=!a.nodeName||$.inArray(a.nodeName.toLowerCase(),['iframe','#document','html','body'])!=-1;if(!isWin)return a;var b=(a.contentWindow||a).document||a.ownerDocument||a;return/webkit/i.test(navigator.userAgent)||b.compatMode=='BackCompat'?b.body:b.documentElement})};$.fn.scrollTo=function(e,f,g){if(typeof f=='object'){g=f;f=0}if(typeof g=='function')g={onAfter:g};if(e=='max')e=9e9;g=$.extend({},h.defaults,g);f=f||g.duration;g.queue=g.queue&&g.axis.length>1;if(g.queue)f/=2;g.offset=both(g.offset);g.over=both(g.over);return this._scrollable().each(function(){if(e==null)return;var d=this,$elem=$(d),targ=e,toff,attr={},win=$elem.is('html,body');switch(typeof targ){case'number':case'string':if(/^([+-]=?)?\d+(\.\d+)?(px|%)?$/.test(targ)){targ=both(targ);break}targ=$(targ,this);if(!targ.length)return;case'object':if(targ.is||targ.style)toff=(targ=$(targ)).offset()}$.each(g.axis.split(''),function(i,a){var b=a=='x'?'Left':'Top',pos=b.toLowerCase(),key='scroll'+b,old=d[key],max=h.max(d,a);if(toff){attr[key]=toff[pos]+(win?0:old-$elem.offset()[pos]);if(g.margin){attr[key]-=parseInt(targ.css('margin'+b))||0;attr[key]-=parseInt(targ.css('border'+b+'Width'))||0}attr[key]+=g.offset[pos]||0;if(g.over[pos])attr[key]+=targ[a=='x'?'width':'height']()*g.over[pos]}else{var c=targ[pos];attr[key]=c.slice&&c.slice(-1)=='%'?parseFloat(c)/100*max:c}if(g.limit&&/^\d+$/.test(attr[key]))attr[key]=attr[key]<=0?0:Math.min(attr[key],max);if(!i&&g.queue){if(old!=attr[key])animate(g.onAfterFirst);delete attr[key]}});animate(g.onAfter);function animate(a){$elem.animate(attr,f,g.easing,a&&function(){a.call(this,targ,g)})}}).end()};h.max=function(a,b){var c=b=='x'?'Width':'Height',scroll='scroll'+c;if(!$(a).is('html,body'))return a[scroll]-$(a)[c.toLowerCase()]();var d='client'+c,html=a.ownerDocument.documentElement,body=a.ownerDocument.body;return Math.max(html[scroll],body[scroll])-Math.min(html[d],body[d])};function both(a){return typeof a=='object'?a:{top:a,left:a}}})(jQuery);
|
||||
;(function($){var h=location.href.replace(/#.*/,'');var i=$.localScroll=function(a){$('body').localScroll(a)};i.defaults={duration:1000,axis:'y',event:'click',stop:true,target:window};i.hash=function(a){if(location.hash){a=$.extend({},i.defaults,a);a.hash=false;if(a.reset){var d=a.duration;delete a.duration;$(a.target).scrollTo(0,a);a.duration=d}scroll(0,location,a)}};$.fn.localScroll=function(b){b=$.extend({},i.defaults,b);return b.lazy?this.bind(b.event,function(e){var a=$([e.target,e.target.parentNode]).filter(filter)[0];if(a)scroll(e,a,b)}):this.find('a,area').filter(filter).bind(b.event,function(e){scroll(e,this,b)}).end().end();function filter(){return!!this.href&&!!this.hash&&this.href.replace(this.hash,'')==h&&(!b.filter||$(this).is(b.filter))}};function scroll(e,a,b){var c=a.hash.slice(1),elem=document.getElementById(c)||document.getElementsByName(c)[0];if(!elem)return;if(e)e.preventDefault();var d=$(b.target);if(b.lock&&d.is(':animated')||b.onBefore&&b.onBefore(e,elem,d)===false)return;if(b.stop)d._scrollable().stop(true);if(b.hash){var f=b.offset;f=f&&f.top||f||0;var g=elem.id==c?'id':'name',$a=$('<a> </a>').attr(g,c).css({position:'absolute',top:$(window).scrollTop()+f,left:$(window).scrollLeft()});elem[g]='';$('body').prepend($a);location=a.hash;$a.remove();elem[g]=c}d.scrollTo(elem,b).trigger('notify.serialScroll',[elem])}})(jQuery);
|
@ -96,8 +96,10 @@ describe('The container component', function() {
|
||||
*/
|
||||
it('should provide access to the main and detail component', function() {
|
||||
createDOM();
|
||||
|
||||
rjsmock.registerDependencies({
|
||||
'URIjs/URI' : URI
|
||||
'URIjs/URI' : URI,
|
||||
'icinga/util/url' : 'icinga/util/url.js'
|
||||
});
|
||||
requireNew('icinga/components/container.js');
|
||||
var Container = rjsmock.getDefine();
|
||||
@ -109,41 +111,4 @@ describe('The container component', function() {
|
||||
$('#icingadetail')[0], 'Assert the DOM of the detail container being #icingadetail');
|
||||
});
|
||||
|
||||
/**
|
||||
* Test dynamic Url update
|
||||
*/
|
||||
it('should automatically update its part of the URL if assigning a new URL', function() {
|
||||
rjsmock.registerDependencies({
|
||||
'URIjs/URI' : URI
|
||||
});
|
||||
requireNew('icinga/components/container.js');
|
||||
createDOM();
|
||||
var Container = rjsmock.getDefine();
|
||||
var url = Container.getMainContainer().updateContainerHref('/some/other/url?test');
|
||||
window.setWindowUrl(url);
|
||||
Container.getMainContainer().containerDom.attr('data-icinga-href').should.equal('/some/other/url?test');
|
||||
|
||||
url.should.equal(
|
||||
'/some/other/url?test',
|
||||
'Assert the main container updating the url correctly');
|
||||
|
||||
url = Container.getDetailContainer().updateContainerHref('/some/detail/url?test');
|
||||
window.setWindowUrl(url);
|
||||
|
||||
Container.getDetailContainer().containerDom.attr('data-icinga-href').should.equal('/some/detail/url?test');
|
||||
url.should.equal(
|
||||
'/some/other/url?test&detail=' + encodeURIComponent('/some/detail/url?test'),
|
||||
'Assert the detail container only updating the "detail" portion of the URL'
|
||||
);
|
||||
|
||||
url = Container.getMainContainer().updateContainerHref('/some/other2/url?test=test');
|
||||
|
||||
window.setWindowUrl(Container.getMainContainer().getContainerHref(window.location.href));
|
||||
Container.getMainContainer().containerDom.attr('data-icinga-href').should.equal('/some/other2/url?test=test');
|
||||
url.should.equal(
|
||||
'/some/other2/url?test=test&detail=' + encodeURIComponent('/some/detail/url?test'),
|
||||
'Assert the main container keeping the detail portion untouched if being assigned a new URL'
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -107,7 +107,7 @@ class TabTest extends PHPUnit_Framework_TestCase
|
||||
$this->assertEquals(
|
||||
1,
|
||||
preg_match(
|
||||
'/<li *><a href="\/my\/url">Title text<\/a><\/li>/i',
|
||||
'/<li *><a href="\/my\/url".*>Title text<\/a><\/li>/i',
|
||||
$html
|
||||
),
|
||||
'Asserting an url being rendered inside an HTML anchor. got ' . $html
|
||||
|
Loading…
x
Reference in New Issue
Block a user