Merge branch 'bugfix/sorting-as-widget-4601'

fixes #4601
This commit is contained in:
Eric Lippmann 2013-09-04 17:15:41 +02:00
commit a44d17f0a6
22 changed files with 576 additions and 228 deletions

View File

@ -30,6 +30,7 @@
use \Zend_Controller_Action_Exception as ActionException; use \Zend_Controller_Action_Exception as ActionException;
use \Icinga\Web\Controller\ActionController; use \Icinga\Web\Controller\ActionController;
use \Icinga\Application\Icinga; use \Icinga\Application\Icinga;
use \Icinga\Application\Config as IcingaConfig;
use \Icinga\Application\Logger; use \Icinga\Application\Logger;
class StaticController extends ActionController class StaticController extends ActionController
@ -72,12 +73,7 @@ class StaticController extends ActionController
} else { } else {
$extension = 'fixme'; $extension = 'fixme';
} }
$hash = md5_file($filePath);
if ($hash === $this->getRequest()->getHeader('If-None-Match')) {
$this->getResponse()->setHttpResponseCode(304);
return;
}
header('ETag: ' . $hash);
header('Content-Type: image/' . $extension); header('Content-Type: image/' . $extension);
header('Cache-Control: max-age=3600'); header('Cache-Control: max-age=3600');
header('Last-Modified: ' . gmdate( header('Last-Modified: ' . gmdate(
@ -119,11 +115,15 @@ class StaticController extends ActionController
echo '/** Module has no js files **/'; echo '/** Module has no js files **/';
return; return;
} }
$hash = md5_file($filePath);
$response = $this->getResponse(); $response = $this->getResponse();
$response->setHeader('ETag', $hash); $response->setHeader('Content-Type', 'text/javascript');
$response->setHeader('Content-Type', 'application/javascript'); if (!IcingaConfig::app()->global->get('environment') == 'development') {
$response->setHeader('Cache-Control', 'max-age=3600', true); $this->setCacheHeader(3600);
} else {
$response->setHeader('Pragma', 'no-cache', true);
$response->setHeader('Cache-Control', 'max-age=-3600', true);
}
$response->setHeader( $response->setHeader(
'Last-Modified', 'Last-Modified',
gmdate( gmdate(
@ -132,15 +132,28 @@ class StaticController extends ActionController
) . ' GMT' ) . ' GMT'
); );
$hash = md5_file($filePath); readfile($filePath);
if ($hash === $this->getRequest()->getHeader('If-None-Match')) {
$response->setHttpResponseCode(304);
return;
} else {
readfile($filePath);
}
return; return;
} }
/**
* Set cache header for this respone
*
* @param integer $maxAge The maximum age to set
*/
private function setCacheHeader($maxAge)
{
$response = $this->getResponse();
$response->setHeader('Cache-Control', 'max-age=3600', true);
$response->setHeader('Pragma', 'cache', true);
$response->setHeader(
'Expires',
gmdate(
'D, d M Y H:i:s',
time()+3600
) . ' GMT',
true
);
}
} }
// @codingStandardsIgnoreEnd // @codingStandardsIgnoreEnd

View File

@ -2,18 +2,26 @@
<div class="row"> <div class="row">
<!-- Only required for left/right tabs --> <!-- Only required for left/right tabs -->
<div class="col-md-2"> <div class="col-sm-12 col-xs-12 col-md-2 col-lg-2">
<?php echo $this->render('parts/navigation.phtml') ?> <?php echo $this->render('parts/navigation.phtml') ?>
</div> </div>
<div class="col-md-10"> <div class="col-sm-12 col-xs-12 col-md-10 col-lg-10">
<div id="icingamain" class="col-md-8"> <div id="icingamain" class="col-sm-12 col-xs-12 col-md-8 col-lg-8">
<?= $this->render('inline.phtml') ?> <?= $this->render('inline.phtml') ?>
</div> </div>
<div id="icingadetail" class="col-md-2"> <div id="icingadetail" class="hidden-sm hidden-xs col-md-4 col-lg-4">
Details Details
</div> </div>
</div> </div>
</div> </div>
<br/>
<!-- Make some space at the end of the page -->
<div class="panel">
<div class="panel-body text-center">
Icinga 2 Web &copy; 2013 Icinga Team
</div>
</div>

View File

@ -43,11 +43,11 @@
<script src="./js/vendor/respond.min.js"></script> <script src="./js/vendor/respond.min.js"></script>
<![endif]--> <![endif]-->
<script data-main="<?php echo $this->baseUrl('js/main.js')?>"
src="<?php echo $this->baseUrl('js/vendor/require.js') ?>"></script>
</head> </head>
<body> <body>
<?= $this->render('body.phtml') ?> <?= $this->render('body.phtml') ?>
<script data-main="<?php echo $this->baseUrl('js/main.js')?>"
src="<?php echo $this->baseUrl('js/vendor/require.js') ?>"></script>
</body> </body>
</html> </html>

View File

@ -101,3 +101,22 @@ You can now either extend your Tabs object using the DashboardAction's `apply()`
`extend()` method (which is more fluent): `extend()` method (which is more fluent):
$tabs->extend(new DashboardAction()); $tabs->extend(new DashboardAction());
## The SortBox widget
The "SortBox" Widget allows you to create a generic sort input for sortable views.
It automatically creates a form containing a select box with all sort options and a dropbox with the sort direction. It
also handles automatic submission of sorting changes and draws an additional submit button when JavaScript is disabled.
The constructor takes an string for the component name ad an array containing the select options, where the key is
the value to be submitted and the value is the label that will be shown. You then should call applyRequest in order to
make sure the form is correctly populated when a request with a sort parameter is being made.
$this->view->sortControl = new SortBox(
$this->getRequest()->getActionName(),
$columns
);
$this->view->sortControl->applyRequest($this->getRequest());
By default the sortBox uses the GET parameter 'sort' for the sorting key and 'dir' for the sorting direction

View File

@ -121,22 +121,11 @@ class Web extends ApplicationBootstrap
*/ */
private function setupRoute() private function setupRoute()
{ {
// TODO: Find a better solution
$this->frontController->getRouter()->addRoute(
'module_overview',
new Zend_Controller_Router_Route(
'js/modules/list.js',
array(
'controller' => 'static',
'action' => 'modulelist',
)
)
);
$this->frontController->getRouter()->addRoute( $this->frontController->getRouter()->addRoute(
'module_javascript', 'module_javascript',
new Zend_Controller_Router_Route( new Zend_Controller_Router_Route(
'js/modules/:module_name/:file', 'js/components/:module_name/:file',
array( array(
'controller' => 'static', 'controller' => 'static',
'action' => 'javascript' 'action' => 'javascript'

View File

@ -328,7 +328,6 @@ namespace Icinga\Test {
if ($token !== null) { if ($token !== null) {
$requestData[$form->getTokenElementName()] = $token; $requestData[$form->getTokenElementName()] = $token;
} }
$request = $this->getRequest(); $request = $this->getRequest();
$request->setMethod('POST'); $request->setMethod('POST');
$request->setPost($requestData); $request->setPost($requestData);
@ -363,6 +362,7 @@ namespace Icinga\Test {
require_once self::$libDir . '/Web/Form/Decorator/ConditionalHidden.php'; require_once self::$libDir . '/Web/Form/Decorator/ConditionalHidden.php';
require_once self::$libDir . '/Web/Form/Decorator/HelpText.php'; require_once self::$libDir . '/Web/Form/Decorator/HelpText.php';
require_once self::$libDir . '/Web/Form/Decorator/BootstrapForm.php';
require_once self::$libDir . '/Web/Form/Validator/DateFormatValidator.php'; require_once self::$libDir . '/Web/Form/Validator/DateFormatValidator.php';
require_once self::$libDir . '/Web/Form/Validator/TimeFormatValidator.php'; require_once self::$libDir . '/Web/Form/Validator/TimeFormatValidator.php';

View File

@ -25,6 +25,7 @@
namespace Icinga\Web; namespace Icinga\Web;
use \Zend_Controller_Request_Abstract; use \Zend_Controller_Request_Abstract;
use \Zend_Form; use \Zend_Form;
use \Zend_Config; use \Zend_Config;
@ -34,6 +35,7 @@ use \Zend_View_Interface;
use \Icinga\Web\Form\Element\Note; use \Icinga\Web\Form\Element\Note;
use \Icinga\Exception\ProgrammingError; use \Icinga\Exception\ProgrammingError;
use \Icinga\Web\Form\Decorator\HelpText; use \Icinga\Web\Form\Decorator\HelpText;
use \Icinga\Web\Form\Decorator\BootstrapForm;
use \Icinga\Web\Form\InvalidCSRFTokenException; use \Icinga\Web\Form\InvalidCSRFTokenException;
use \Icinga\Application\Config as IcingaConfig; use \Icinga\Application\Config as IcingaConfig;
@ -122,6 +124,13 @@ class Form extends Zend_Form
*/ */
private $last_note_id = 0; private $last_note_id = 0;
/**
* Decorator that replaces the DtDd Zend-Form default
*
* @var Form\Decorator\BootstrapFormDecorator
*/
private $formDecorator;
/** /**
* Getter for the session ID * Getter for the session ID
* *
@ -554,4 +563,52 @@ class Form extends Zend_Form
list ($seed, $token) = $this->generateCsrfToken($this->getSessionId()); list ($seed, $token) = $this->generateCsrfToken($this->getSessionId());
return sprintf('%s|%s', $seed, $token); return sprintf('%s|%s', $seed, $token);
} }
/**
* Add a new element
*
* Additionally, all DtDd tags will be removed and the Bootstrap compatible
* BootstrapForm decorator will be added to the elements
*
*
* @param string|Zend_Form_Element $element String element type, or an object of type Zend_Form_Element
* @param string $name The name of the element to add if $element is a string
* @param array $options The settings for the element if $element is a string
*
* @return Form
* @see Zend_Form::addElement()
*/
public function addElement($element, $name = null, $options = null)
{
parent::addElement($element, $name, $options);
$el = $name ? $this->getElement($name) : $element;
if ($el) {
$el->removeDecorator('HtmlTag');
$el->removeDecorator('Label');
$el->removeDecorator('DtDdWrapper');
$el->addDecorator(new BootstrapForm());
$el->setAttrib('class', $el->getAttrib('class') . ' form-control input-sm');
}
return $this;
}
/**
* Load the default decorators
*
* @return Zend_Form
*/
public function loadDefaultDecorators()
{
if ($this->loadDefaultDecoratorsIsDisabled()) {
return $this;
}
$decorators = $this->getDecorators();
if (empty($decorators)) {
$this->addDecorator('FormElements')
->addDecorator('Form');
}
return $this;
}
} }

View File

@ -0,0 +1,105 @@
<?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\Web\Form\Decorator;
use Zend_Form_Decorator_Abstract;
/**
* Decorator that styles forms in the DOM Bootstrap wants for it's forms
*
* This component replaces the dt/dd wrapping of elements with the approach used by bootstrap.
*
* Labels are drawn for all elements, except hidden, button and submit elements. If you want a
* placeholder for this elements, set the 'addLabelPlaceholder' property. This can be useful in
* cases where you want to put inputs with and inputs without labels on the same line and don't
* want buttons to 'jump'
*/
class BootstrapForm extends Zend_Form_Decorator_Abstract
{
/**
* An array of elements that won't get a <label> dom added per default
*
* @var array
*/
private static $noLabel = array(
'Zend_Form_Element_Hidden',
'Zend_Form_Element_Button',
'Zend_Form_Element_Submit'
);
/**
* Return the DOM for the element label
*
* @param String $elementName The name of the element
*
* @return String The DOM for the form element's label
*/
public function getLabel($elementName)
{
$label = $this->getElement()->getLabel();
if (!$label) {
$label = '&nbsp;';
}
if (in_array($this->getElement()->getType(), self::$noLabel)
&& !$this->getElement()->getAttrib('addLabelPlaceholder', false)) {
$label = '';
} else {
if (in_array($this->getElement()->getType(), self::$noLabel)) {
$label = '&nbsp;';
}
$label = '<label for="' . $elementName . '">' . $label . '</label>';
}
return $label;
}
/**
* Render this element
*
* Renders as the following:
* <div class="form-group">
* $dtLabel
* $dtElement
* </div>
*
* @param String $content The content of the form element
*
* @return String The decorated form element
*/
public function render($content)
{
$el = $this->getElement();
$elementName = $el->getName();
$label = $this->getLabel($elementName);
return '<div class="form-group" id="' . $elementName . '-element">'
. $label
. $content
. '</div>';
}
}

View File

@ -46,13 +46,15 @@ class HelpText extends Zend_Form_Decorator_Abstract
public function render($content = '') public function render($content = '')
{ {
$attributes = $this->getElement()->getAttribs(); $attributes = $this->getElement()->getAttribs();
$visible = true;
if (isset($attributes['helptext'])) { if (isset($attributes['condition'])) {
$content = '<div>' $visible = $attributes['condition'] == '1';
. $content }
. '<span class="helptext">' if (isset($attributes['helptext']) && $visible) {
$content = $content
. '<p class="help-block">'
. $attributes['helptext'] . $attributes['helptext']
. '</span></div><br/>'; . '</p>';
} }
return $content; return $content;
} }

View File

@ -0,0 +1,175 @@
<?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\Web\Widget;
use Icinga\Web\Form;
use Icinga\Web\Request;
use Zend_View_Abstract;
use Icinga\Web\Form\Decorator\ConditionalHidden;
use Zend_Form_Element_Submit;
/**
* Sortbox widget
*
* The "SortBox" Widget allows you to create a generic sort input for sortable views.
* It automatically creates a form containing a select box with all sort options and a
* dropbox with the sort direction. It also handles automatic submission of sorting changes and draws an additional
* submit button when JavaScript is disabled.
*
* The constructor takes an string for the component name ad an array containing the select options, where the key is
* the value to be submitted and the value is the label that will be shown. You then should call applyRequest in order
* to make sure the form is correctly populated when a request with a sort parameter is being made.
*
* Example:
* <pre><code>
* $this->view->sortControl = new SortBox(
* $this->getRequest()->getActionName(),
* $columns
* );
* $this->view->sortControl->applyRequest($this->getRequest());
* </code></pre>
* By default the sortBox uses the GET parameter 'sort' for the sorting key and 'dir' for the sorting direction
*
*/
class SortBox implements Widget
{
/**
* An array containing all sort columns with their associated labels
*
* @var array
*/
private $sortFields;
/**
* The name of the form that will be created
*
* @var string
*/
private $name;
/**
* A request object used for initial form population
*
* @var Icinga\Web\Request
*/
private $request;
/**
* Create a SortBox with the entries from $sortFields
*
* @param string $name The name of the sort form
* @param array $sortFields An array containing the columns and their labels to be displayed
* in the sort select box
*/
public function __construct($name, array $sortFields)
{
$this->name = $name;
$this->sortFields = $sortFields;
}
/**
* Apply the parameters from the given request on this SortBox
*
* @param Request $request The request to use for populating the form
*/
public function applyRequest($request)
{
$this->request = $request;
}
/**
* Create a submit button that is hidden via the ConditionalDecorator
* in order to allow sorting changes to be submitted in a JavaScript-less environment
*
* @return Zend_Form_Element_Submit The submit button that is hidden by default
* @see ConditionalDecorator
*/
private function createFallbackSubmitButton()
{
$manualSubmitButton = new Zend_Form_Element_Submit(
array(
'name' => 'submit_' . $this->name,
'label' => 'Sort',
'class' => 'btn btn-default',
'condition' => 0,
'value' => '{{SUBMIT_ICON}}'
)
);
$manualSubmitButton->addDecorator(new ConditionalHidden());
$manualSubmitButton->setAttrib('addLabelPlaceholder', true);
return $manualSubmitButton;
}
/**
* Renders this widget via the given view and returns the
* HTML as a string
*
* @param Zend_View_Abstract $view
*
* @return string
*/
public function render(Zend_View_Abstract $view)
{
$form = new Form();
$form->setAttrib('class', 'form-inline');
$form->setMethod('GET');
$form->setTokenDisabled();
$form->setName($this->name);
$form->addElement(
'select',
'sort_' . $this->name,
array(
'name' => 'sort',
'label' => 'Sort By',
'multiOptions' => $this->sortFields
)
);
$form->addElement(
'select',
'dir_' . $this->name,
array(
'name' => 'dir',
'multiOptions' => array(
'desc' => 'Desc',
'asc' => 'Asc'
)
)
);
$form->enableAutoSubmit(array('sort_' . $this->name, 'dir_' . $this->name));
$form->addElement($this->createFallbackSubmitButton());
if ($this->request) {
$form->populate($this->request->getParams());
}
return $form->render($view);
}
}

View File

@ -32,11 +32,11 @@ use \Icinga\Data\Db\Query;
use \Icinga\File\Csv; use \Icinga\File\Csv;
use \Icinga\Web\Controller\ActionController; use \Icinga\Web\Controller\ActionController;
use \Icinga\Web\Hook; use \Icinga\Web\Hook;
use \Icinga\Web\Widget\Tabextension\BasketAction;
use \Icinga\Web\Widget\Tabextension\DashboardAction; use \Icinga\Web\Widget\Tabextension\DashboardAction;
use \Icinga\Web\Widget\Tabextension\OutputFormat; use \Icinga\Web\Widget\Tabextension\OutputFormat;
use \Icinga\Web\Widget\Tabs; use \Icinga\Web\Widget\Tabs;
use \Icinga\Module\Monitoring\Backend; use Icinga\Module\Monitoring\Backend;
use \Icinga\Web\Widget\SortBox;
class Monitoring_ListController extends ActionController class Monitoring_ListController extends ActionController
{ {
@ -46,7 +46,6 @@ class Monitoring_ListController extends ActionController
* @var Backend * @var Backend
*/ */
protected $backend; protected $backend;
/** /**
* Set to a string containing the compact layout name to use when * Set to a string containing the compact layout name to use when
* 'compact' is set as the layout parameter, otherwise null * 'compact' is set as the layout parameter, otherwise null
@ -107,6 +106,14 @@ class Monitoring_ListController extends ActionController
'host_last_comment' 'host_last_comment'
) )
); );
$this->setupSortControl(array(
'host_last_check' => 'Last Host Check',
'host_severity' => 'Host Severity',
'host_state' => 'Current Host State',
'host_name' => 'Host Name',
'host_address' => 'Address',
'host_state' => 'Hard State'
));
} }
/** /**
@ -114,13 +121,6 @@ class Monitoring_ListController extends ActionController
*/ */
public function servicesAction() public function servicesAction()
{ {
if ($this->_getParam('_statetype', 'soft') === 'soft') {
$state_column = 'service_state';
$state_change_column = 'service_last_state_change';
} else {
$state_column = 'service_hard_state';
$state_change_column = 'service_last_hard_state_change';
}
$this->compactView = "services-compact"; $this->compactView = "services-compact";
$this->view->services = $this->query('status', array( $this->view->services = $this->query('status', array(
@ -132,12 +132,12 @@ class Monitoring_ListController extends ActionController
'host_handled', 'host_handled',
'service_description', 'service_description',
'service_display_name', 'service_display_name',
'service_state' => $state_column, 'service_state' => 'service_state',
'service_in_downtime', 'service_in_downtime',
'service_acknowledged', 'service_acknowledged',
'service_handled', 'service_handled',
'service_output', 'service_output',
'service_last_state_change' => $state_change_column, 'service_last_state_change' => 'service_last_state_change',
'service_icon_image', 'service_icon_image',
'service_long_output', 'service_long_output',
'service_is_flapping', 'service_is_flapping',
@ -150,7 +150,19 @@ class Monitoring_ListController extends ActionController
'service_notes_url', 'service_notes_url',
'service_last_comment' 'service_last_comment'
)); ));
$this->inheritCurrentSortColumn();
$this->setupSortControl(array(
'service_last_check' => 'Last Service Check',
'service_severity' => 'Severity',
'service_state' => 'Current Service State',
'service_description' => 'Service Name',
'service_state_type' => 'Hard State',
'host_severity' => 'Host Severity',
'host_state' => 'Current Host State',
'host_name' => 'Host Name',
'host_address' => 'Host Address',
'host_last_check' => 'Last Host Check'
));
} }
/** /**
@ -158,28 +170,38 @@ class Monitoring_ListController extends ActionController
*/ */
public function downtimesAction() public function downtimesAction()
{ {
$query = $this->backend->select() $this->setDefaultSortColumn('downtime_is_in_effect');
->from('downtime',array( $this->view->downtimes = $this->query('downtime', array(
'host_name', 'host_name',
'object_type', 'object_type',
'service_description', 'service_description',
'downtime_entry_time', 'downtime_entry_time',
'downtime_internal_downtime_id', 'downtime_internal_downtime_id',
'downtime_author_name', 'downtime_author_name',
'downtime_comment_data', 'downtime_comment_data',
'downtime_duration', 'downtime_duration',
'downtime_scheduled_start_time', 'downtime_scheduled_start_time',
'downtime_scheduled_end_time', 'downtime_scheduled_end_time',
'downtime_is_fixed', 'downtime_is_fixed',
'downtime_is_in_effect', 'downtime_is_in_effect',
'downtime_triggered_by_id', 'downtime_triggered_by_id',
'downtime_trigger_time' 'downtime_trigger_time'
)); ));
if (!$this->_getParam('sort')) {
$query->order('downtime_is_in_effect'); $this->setupSortControl(array(
} 'downtime_is_in_effect' => 'Is In Effect',
$this->view->downtimes = $query->applyRequest($this->_request); 'object_type' => 'Service/Host',
$this->inheritCurrentSortColumn(); 'host_name' => 'Host Name',
'service_description' => 'Service Name',
'downtime_entry_time' => 'Entry Time',
'downtime_author_name' => 'Author',
'downtime_comment_data' => 'Comment',
'downtime_scheduled_start_time' => 'Start',
'downtime_scheduled_end_time' => 'End',
'downtime_trigger_time' => 'Trigger Time',
'downtime_internal_downtime_id' => 'Downtime ID',
'downtime_duration' => 'Duration',
));
} }
/** /**
@ -187,6 +209,7 @@ class Monitoring_ListController extends ActionController
*/ */
public function notificationsAction() public function notificationsAction()
{ {
$this->setDefaultSortColumn('notification_start_time', 'DESC');
$this->view->notifications = $this->query( $this->view->notifications = $this->query(
'notification', 'notification',
array( array(
@ -200,11 +223,9 @@ class Monitoring_ListController extends ActionController
'notification_command' 'notification_command'
) )
); );
if (!$this->_getParam('sort')) { $this->setupSortControl(array(
$this->view->notifications->order('notification_start_time DESC'); 'notification_start_time' => 'Notification Start'
} ));
$this->inheritCurrentSortColumn();
} }
/** /**
@ -263,6 +284,34 @@ class Monitoring_ListController extends ActionController
} }
} }
/**
* Set the default sort column for this action if none is provided
*
* @param string $column The column to use for sorting
* @param string $dir The direction ('ASC' or 'DESC')
*/
private function setDefaultSortColumn($column, $dir = 'DESC')
{
$this->setParam('sort', $this->getParam('sort', $column));
$this->setParam('dir', $this->getParam('dir', $dir));
}
/**
* Create a sort control box at the 'sortControl' view parameter
*
* @param array $columns An array containing the sort columns, with the
* submit value as the key and the value as the label
*/
private function setupSortControl(array $columns)
{
$this->view->sortControl = new SortBox(
$this->getRequest()->getActionName(),
$columns
);
$this->view->sortControl->applyRequest($this->getRequest());
}
/** /**
* Return all tabs for this controller * Return all tabs for this controller
* *
@ -273,18 +322,7 @@ class Monitoring_ListController extends ActionController
$tabs = $this->getTabs(); $tabs = $this->getTabs();
$tabs->extend(new OutputFormat()) $tabs->extend(new OutputFormat())
->extend(new DashboardAction()) ->extend(new DashboardAction());
->extend(new BasketAction());
}
/**
* Let the current response inherit the used sort column by applying it to the view property `sort`
*/
private function inheritCurrentSortColumn()
{
if ($this->_getParam('sort')) {
$this->view->sort = $this->_getParam('sort');
}
} }
} }
// @codingStandardsIgnoreEnd // @codingStandardsIgnoreEnd

View File

@ -61,7 +61,7 @@ class Monitoring_ShowController extends ActionController
// TODO: Do not allow wildcards in names! // TODO: Do not allow wildcards in names!
if ($host !== null) { if ($host !== null) {
// TODO: $this->assertPermission('host/read', $host); // TODO: $this->assertPermission('host/read', $host);
if ($this->action_name !== 'host' && $service !== null && $service !== '*') { if ($this->getRequest()->getActionName() !== 'host' && $service !== null && $service !== '*') {
// TODO: $this->assertPermission('service/read', $service); // TODO: $this->assertPermission('service/read', $service);
$object = Service::fetch($this->backend, $host, $service); $object = Service::fetch($this->backend, $host, $service);
} else { } else {

View File

@ -16,41 +16,8 @@ function formatDateString($self,$dateString){
$paginator = $downtimes->paginate(); $paginator = $downtimes->paginate();
$downtimes = $downtimes->fetchAll(); $downtimes = $downtimes->fetchAll();
?> ?>
<form method="get" class="form-inline" action="<?= $this->href(
'monitoring/list/downtimes?', <?= $this->sortControl->render($this); ?>
array(
'action' => 'downtimes'
));
?>">
<label>Sort By</label>
<br/>
<?=
$this->formSelect(
'sort',
$this->sort,
array('class' => 'autosubmit'),
array(
'downtime_is_in_effect' => 'Is In Effect',
'object_type' => 'Service/Host',
'host_name' => 'Host Name',
'service_description' => 'Service Name',
'downtime_entry_time' => 'Entry Time',
'downtime_author_name' => 'Author',
'downtime_comment_data' => 'Comment',
'downtime_scheduled_start_time' => 'Start',
'downtime_scheduled_end_time' => 'End',
'downtime_trigger_time' => 'Trigger Time',
'downtime_internal_downtime_id' => 'Downtime ID',
'downtime_duration' => 'Duration',
)
);
?>
<noscript>
<button class="btn btn-small btn-default" >
<i>{{REFRESH_ICON}}</i>
</button>
</noscript>
</form>
<?= <?=
$this->paginationControl( $this->paginationControl(
$paginator, $paginator,

View File

@ -5,31 +5,14 @@ $hosts = $this->hosts->paginate();
$viewHelper = $this->getHelper('MonitoringState'); $viewHelper = $this->getHelper('MonitoringState');
?> ?>
<form method="get" class="form-inline" action="<?= $this->href('monitoring/list/hosts', $this->hosts->getAppliedFilter()->toParams());?>"> <?= $this->sortControl->render($this); ?>
<label>Sort By</label>
<?= $this->formSelect(
'sort',
$this->sort,
array('class' => array('autosubmit')),
array(
'host_severity' => 'Severity',
'host_last_state_change' => 'Last state change',
'host_name' => 'Host',
)
); ?>
<noscript>
<button class="btn btn-small btn-default">
<i>{{REFRESH_ICON}}</i>
</button>
</noscript>
</form>
<?= $this->paginationControl($hosts, null, null, array('preserve' => $this->preserve)); ?> <?= $this->paginationControl($hosts, null, null, array('preserve' => $this->preserve)); ?>
<table class="table table-condensed"> <table class="table table-condensed">
<thead> <thead>
<tr> <tr>
<th colspan="2">Status</th> <th colspan="3">Status</th>
<th>Host</th> <th>Host</th>
<th>Output</th> <th>Output</th>
<th></th> <th></th>

View File

@ -4,31 +4,7 @@
$formatter = $this->getHelper('MonitoringProperties'); $formatter = $this->getHelper('MonitoringProperties');
?> ?>
<form method="get" action="<?= <?= $this->sortControl->render($this); ?>
$this->href(
'monitoring/list/notifications',
$this->notifications->getAppliedFilter()->toParams()
);
?>">
<label>Sort By</label> <?=
$this->formSelect(
'sort',
$this->sort,
array(
'class' => 'autosubmit'
),
array(
'notification_start_time' => 'Time'
)
);
?>
<noscript>
<button class="btn btn-small btn-default">
{{REFRESH_ICON}}
</button>
</noscript>
</form>
<?php <?php
$notifications = $this->notifications->paginate(); $notifications = $this->notifications->paginate();
echo $this->paginationControl($notifications, null, null, array('preserve' => $this->preserve)); echo $this->paginationControl($notifications, null, null, array('preserve' => $this->preserve));
@ -50,10 +26,17 @@ echo $this->paginationControl($notifications, null, null, array('preserve' => $t
<?php foreach ($notifications as $notification): ?> <?php foreach ($notifications as $notification): ?>
<tr> <tr>
<td> <td>
<?= $notification->host_name ?> <a href="<?= $this->href('monitoring/show/host', array('host' => $notification->host_name)); ?>">
<?= $notification->host_name ?>
</a>
</td> </td>
<td> <td>
<?= empty($notification->service_description) ? '' : $notification->service_description; ?> <a href="<?= $this->href('monitoring/show/host', array(
'host' => $notification->host_name,
'service' => $notification->service_description
)
); ?>">
<?= empty($notification->service_description) ? '' : $notification->service_description; ?>
</td> </td>
<td><?= $formatter->getNotificationType($notification); ?> <td><?= $formatter->getNotificationType($notification); ?>
</td> </td>

View File

@ -6,41 +6,13 @@ $viewHelper = $this->getHelper('MonitoringState');
$trimArea = $this->getHelper('Trim'); $trimArea = $this->getHelper('Trim');
?> ?>
<?php if (empty($services)): ?> <?= $this->sortControl->render($this); ?>
<div>
Sorry, no services found for this search
</div>
<?php return; endif ?>
<form method="get" action="<?= $this->href('monitoring/list/services', $this->services->getAppliedFilter()->toParams()); ?>">
<label>Sort By</label>
<?= $this->formSelect(
'sort',
$this->sort,
array('class' => 'autosubmit'),
array(
'service_severity' => 'Severity',
'service_last_state_change' => 'Last state change',
'service_last_time_unknown' => 'Last UNKNOWN',
'service_last_time_critical' => 'Last CRITICAL',
'service_last_time_warning' => 'Last WARNING',
'service_last_time_ok' => 'Last OK',
'service_description' => 'Service',
)
); ?>
<noscript>
<button class="btn btn-small btn-default">
<i>ICON REFRESH</i>
</button>
</noscript>
</form>
<?= $this->paginationControl($paginator, null, null, array('preserve' => $this->preserve)) ?> <?= $this->paginationControl($paginator, null, null, array('preserve' => $this->preserve)) ?>
<table class="table table-condensed"> <table class="table table-condensed">
<thead> <thead>
<tr> <tr>
<th colspan="2">Status</th> <th colspan="3">Status</th>
<th>Service</th> <th>Service</th>
<th>Host</th> <th>Host</th>
<th>Output</th> <th>Output</th>
@ -115,8 +87,15 @@ $trimArea = $this->getHelper('Trim');
<i>{{COMMENT_ICON}}</i> <i>{{COMMENT_ICON}}</i>
</a> </a>
<?php endif; ?> <?php endif; ?>
<a href="<?= $this->href(
<b> <?= $service->service_display_name; ?></b> 'monitoring/show/service',
array(
'host' => $service->host_name,
'service' => $service->service_description
)
); ?>">
<b> <?= $service->service_display_name; ?></b>
</a>
<br/> <br/>
<?php if ($service->service_action_url != ""): ?> <?php if ($service->service_action_url != ""): ?>
@ -130,7 +109,14 @@ $trimArea = $this->getHelper('Trim');
</td> </td>
<td title="<?= $viewHelper->getStateTitle($service, 'host'); ?>"> <td title="<?= $viewHelper->getStateTitle($service, 'host'); ?>">
<?= $service->host_name; ?> <a href="<?= $this->href(
'monitoring/show/host',
array(
'host' => $service->host_name,
)
); ?>">
<?= $service->host_name; ?>
</a>
<div> <div>
(<?= ucfirst($viewHelper->monitoringState($service, 'host')); ?>) (<?= ucfirst($viewHelper->monitoringState($service, 'host')); ?>)

View File

@ -119,7 +119,7 @@ abstract class MonitoringControllerTest extends Zend_Test_PHPUnit_ControllerTest
require_once('Data/Db/Connection.php'); require_once('Data/Db/Connection.php');
require_once('Data/Db/Query.php'); require_once('Data/Db/Query.php');
require_once('Exception/ProgrammingError.php'); require_once('Exception/ProgrammingError.php');
require_once('Web/Widget/SortBox.php');
require_once('library/Monitoring/Backend/AbstractBackend.php'); require_once('library/Monitoring/Backend/AbstractBackend.php');
} }

View File

@ -44,7 +44,7 @@ define(['jquery', 'logging', 'icinga/componentRegistry'], function ($, log, regi
*/ */
var loadComponent = function(cmpType, target, fin, err) { var loadComponent = function(cmpType, target, fin, err) {
requirejs( requirejs(
['modules/' + cmpType], ['components/' + cmpType],
function (Cmp) { function (Cmp) {
var cmp; var cmp;
try { try {

View File

@ -26,9 +26,22 @@
// {{{ICINGA_LICENSE_HEADER}}} // {{{ICINGA_LICENSE_HEADER}}}
/*global Icinga:false, document: false, define:false require:false base_url:false console:false, window:false */ /*global Icinga:false, document: false, define:false require:false base_url:false console:false, window:false */
/**
* Icinga Logger
*
* Allows platform independent logging of via logger.info, logger.warn, logger.error and logger.emergency
*
*/
define(function() { define(function() {
'use strict'; 'use strict';
/**
* Log a message to the console (if available), using the provided tag
*
* @param {String} tag The tag to use, error and emergency are logged as console.error
* @param {String} logArgs The arguments to log
*/
function logTagged(tag, logArgs) { function logTagged(tag, logArgs) {
var now = new Date(); var now = new Date();
var ms = now.getMilliseconds() + ''; var ms = now.getMilliseconds() + '';
@ -42,13 +55,13 @@ define(function() {
for (var el in logArgs) { for (var el in logArgs) {
args.push(logArgs[el]); args.push(logArgs[el]);
} }
try { try {
if (console[tag]) { if (console[tag]) {
console[tag].apply(console, logArgs);
console[tag].apply(console,logArgs); } else if (tag === 'emergency') {
console.error.apply(console, logArgs);
} else { } else {
console.log.apply(console,args); console.log.apply(console, args);
} }
} catch (e) { // IE fallback } catch (e) { // IE fallback
@ -59,7 +72,11 @@ define(function() {
if(!window.console) { if(!window.console) {
window.console = { log: function() {} }; window.console = { log: function() {} };
} }
var features = {
/**
* Callinterface for this module
*/
return {
debug: function() { debug: function() {
if (!window.ICINGA_DEBUG) { if (!window.ICINGA_DEBUG) {
return; return;
@ -77,6 +94,4 @@ define(function() {
// TODO: log *emergency* errors to the backend // TODO: log *emergency* errors to the backend
} }
}; };
return features;
}); });

View File

@ -1,11 +1,19 @@
requirejs.config({ requirejs.config({
baseUrl: window.base_url + '/js', 'baseUrl': window.base_url + '/js',
paths: { 'paths': {
'jquery': 'vendor/jquery-1.8.3', 'jquery': 'vendor/jquery-1.8.3',
'bootstrap': 'vendor/bootstrap/bootstrap.min', 'bootstrap': 'vendor/bootstrap/bootstrap.min',
'history': 'vendor/history', 'history': 'vendor/history',
'logging': 'icinga/util/logging', 'logging': 'icinga/util/logging',
'datetimepicker': 'vendor/bootstrap/datetimepicker.min' 'datetimepicker': 'vendor/bootstrap/datetimepicker.min'
},
'shim': {
'datetimepicker': {
'exports': 'datetimepicker'
},
'jquery' : {
exports: 'jquery'
}
} }
}); });

View File

@ -9,7 +9,6 @@ var rjsmock = require('requiremock.js');
GLOBAL.document = $('body'); GLOBAL.document = $('body');
var component; var component;
/** /**
* Set up the test fixture * Set up the test fixture
* *
@ -28,28 +27,30 @@ var setUp = function(registry)
/* /*
* Available components * Available components
*/ */
'modules/app/component1': function(cmp) { 'components/app/component1': function(cmp) {
cmp.test = 'changed-by-component-1'; cmp.test = 'changed-by-component-1';
this.type = function() { this.type = function() {
return "app/component1"; return "app/component1";
}; };
return this;
}, },
'modules/app/component2': function(cmp) { 'components/app/component2': function(cmp) {
cmp.test = 'changed-by-component-2'; cmp.test = 'changed-by-component-2';
this.type = function() { this.type = function() {
return "app/component2"; return "app/component2";
}; };
return this;
}, },
'modules/module/component3': function(cmp) { 'components/module/component3': function(cmp) {
cmp.test = 'changed-by-component-3-from-module'; cmp.test = 'changed-by-component-3-from-module';
this.type = function() { this.type = function() {
return "module/component3"; return "module/component3";
}; };
return this;
} }
}); });
$('body').empty(); $('body').empty();
requireNew('icinga/componentLoader.js'); requireNew('icinga/componentLoader.js');
component = rjsmock.getDefine(); component = rjsmock.getDefine();
}; };
@ -72,7 +73,6 @@ describe('Component loader', function() {
it('Component loaded with automatic id', function() { it('Component loaded with automatic id', function() {
setUp(); setUp();
addComponent('app/component1'); addComponent('app/component1');
component.load(function() { component.load(function() {
var cmpNode = $('#icinga-component-0'); var cmpNode = $('#icinga-component-0');
cmpNode.length.should.equal(1); cmpNode.length.should.equal(1);

View File

@ -65,7 +65,7 @@ class GeneralFormTest extends BaseTestCase
if ($child->hasAttributes() === false) { if ($child->hasAttributes() === false) {
continue; continue;
} }
if (strpos($child->attributes->item(0)->value, $value) !== false) { if (strpos($child->attributes->getNamedItem('id')->value, $value . '-element') !== false) {
return true; return true;
} }
} }