Preserve multiselection during refresh

refs #5765
This commit is contained in:
Matthias Jentsch 2014-04-23 19:41:07 +02:00
parent b791883fa8
commit c641988233
12 changed files with 238 additions and 98 deletions

View File

@ -113,6 +113,7 @@ class Monitoring_ListController extends Controller
))->activate('hosts');
$this->setAutorefreshInterval(10);
$this->view->query = $this->_request->getQuery();
$this->view->title = 'Host Status';
$this->compactView = 'hosts-compact';
$dataview = HostStatusView::fromRequest(
@ -176,6 +177,7 @@ class Monitoring_ListController extends Controller
}
}
$this->view->title = 'Service Status';
$this->view->query = $this->_request->getQuery();
$this->setAutorefreshInterval(10);
$query = $this->fetchServices();
$this->applyRestrictions($query);

View File

@ -45,14 +45,13 @@ class Monitoring_MultiController extends ActionController
{
public function init()
{
$this->view->queries = $this->getAllParamsAsArray();
$this->backend = Backend::createBackend($this->_getParam('backend'));
$this->createTabs();
}
public function hostAction()
{
$filters = $this->view->queries;
$multiFilter = $this->getAllParamsAsArray();
$errors = array();
// Fetch Hosts
@ -68,18 +67,20 @@ class Monitoring_MultiController extends ActionController
'host_notifications_enabled',
'host_event_handler_enabled',
'host_flap_detection_enabled',
'host_active_checks_enabled'
'host_active_checks_enabled',
// columns intended for filter-request
'host_problem',
'host_handled'
)
)->getQuery();
if ($this->_getParam('host') !== '*') {
$this->applyQueryFilter($hostQuery, $filters);
}
$this->applyQueryFilter($hostQuery, $multiFilter);
$hosts = $hostQuery->fetchAll();
// Fetch comments
$commentQuery = $this->applyQueryFilter(
CommentView::fromRequest($this->_request)->getQuery(),
$filters,
CommentView::fromParams(array('backend' => $this->_request->getParam('backend')))->getQuery(),
$multiFilter,
'comment_host'
);
$comments = array_keys($this->getUniqueValues($commentQuery->fetchAll(), 'comment_internal_id'));
@ -94,23 +95,25 @@ class Monitoring_MultiController extends ActionController
$this->view->states = $this->countStates($hosts, 'host', 'host_name');
$this->view->pie = $this->createPie($this->view->states, $this->view->getHelper('MonitoringState')->getHostStateColors());
// need the query content to list all hosts
$this->view->query = $this->_request->getQuery();
// Handle configuration changes
$this->handleConfigurationForm(array(
'host_passive_checks_enabled' => 'Passive Checks',
'host_active_checks_enabled' => 'Active Checks',
'host_obsessing' => 'Obsessing',
'host_notifications_enabled' => 'Notifications',
'host_event_handler_enabled' => 'Event Handler',
'host_flap_detection_enabled' => 'Flap Detection'
'host_flap_detection_enabled' => 'Flap Detection',
'host_obsessing' => 'Obsessing'
));
}
public function serviceAction()
{
$filters = $this->view->queries;
$multiFilter = $this->getAllParamsAsArray();
$errors = array();
$backendQuery = ServiceStatusView::fromRequest(
$this->_request,
array(
@ -124,24 +127,33 @@ class Monitoring_MultiController extends ActionController
'service_notifications_enabled',
'service_event_handler_enabled',
'service_flap_detection_enabled',
'service_active_checks_enabled'
'service_active_checks_enabled',
'service_obsessing',
// also accept all filter-requests from ListView
'service_problem',
'service_severity',
'service_last_check',
'service_state_type',
'host_severity',
'host_address',
'host_last_check'
)
)->getQuery();
if ($this->_getParam('service') !== '*' && $this->_getParam('host') !== '*') {
$this->applyQueryFilter($backendQuery, $filters);
$this->applyQueryFilter($backendQuery, $filters);
}
$this->applyQueryFilter($backendQuery, $multiFilter);
$services = $backendQuery->fetchAll();
// Comments
$commentQuery = $this->applyQueryFilter(
CommentView::fromRequest($this->_request)->getQuery(),
$filters,
CommentView::fromParams(array('backend' => $this->_request->getParam('backend')))->getQuery(),
$multiFilter,
'comment_host',
'comment_service'
);
$comments = array_keys($this->getUniqueValues($commentQuery->fetchAll(), 'comment_internal_id'));
// populate the view
$this->view->objects = $this->view->services = $services;
$this->view->problems = $this->getProblems($services);
$this->view->comments = isset($comments) ? $comments : $this->getComments($services);
@ -154,12 +166,16 @@ class Monitoring_MultiController extends ActionController
$this->view->host_pie = $this->createPie($this->view->host_states, $this->view->getHelper('MonitoringState')->getHostStateColors());
$this->view->errors = $errors;
// need the query content to list all hosts
$this->view->query = $this->_request->getQuery();
$this->handleConfigurationForm(array(
'service_passive_checks_enabled' => 'Passive Checks',
'service_active_checks_enabled' => 'Active Checks',
'service_notifications_enabled' => 'Notifications',
'service_event_handler_enabled' => 'Event Handler',
'service_flap_detection_enabled' => 'Flap Detection'
'service_flap_detection_enabled' => 'Flap Detection',
'service_obsessing' => 'Obsessing',
));
}
@ -265,13 +281,8 @@ class Monitoring_MultiController extends ActionController
private function createPie($states, $colors)
{
$chart = new InlinePie(
array_values($states),
$colors
);
$chart->setLabels(array_keys($states))
->setHeight(100)
->setWidth(100);
$chart = new InlinePie(array_values($states), $colors);
$chart->setLabels(array_keys($states))->setHeight(100)->setWidth(100);
return $chart;
}

View File

@ -14,7 +14,7 @@ class Zend_View_Helper_SelectionToolbar extends Zend_View_Helper_Abstract
{
if ($type == 'multi') {
return '<div class="selection-toolbar">'
. '<a href="' . $target . '" data-base-target="_next"> Select All </a> </div>';
. '<a href="' . $target . '" data-base-target="_next"> Show All </a> </div>';
} else {
return '';
}

View File

@ -8,7 +8,7 @@ $helper = $this->getHelper('MonitoringState');
</div>
<?= $this->paginationControl($hosts, null, null, array('preserve' => $this->preserve)); ?>
<?= $this->selectionToolbar('multi', $this->href('monitoring/multi/host',array( 'host' => '*' ))); ?>
<?= $this->selectionToolbar('multi', $this->href('monitoring/multi/host', $query)); ?>
</div>
<div class="content">

View File

@ -2,7 +2,8 @@
<?= $this->tabs ?>
<div style="margin: 1em" class="dontprint">
Sort by <?= $this->sortControl->render($this) ?>
<!--<?= $this->selectionToolbar('single') ?>-->
<?= $this->selectionToolbar('single') ?>
</div>
<?= $this->paginationControl($notifications, null, null, array('preserve' => $this->preserve)) ?>
</div>

View File

@ -9,7 +9,10 @@ if (!$this->compact): ?>
Sort by <?= $this->sortControl ?>
</div>
<?= $this->paginationControl($services, null, null, array('preserve' => $this->preserve)); ?>
<?= $this->paginationControl($services, null, null, array('preserve' => $this->preserve));?>
<?= $this->selectionToolbar('multi', $this->href('monitoring/multi/service', $query)); ?>
</div>
<div class="content">

View File

@ -24,10 +24,10 @@
<?= $this->icon('acknowledgement_petrol.png') ?> Acknowledge
</a>
</td>
<td>Change <a href=" <?= $this->href(
<td>Change <a data-base-target='_next' href=" <?= $this->href(
'monitoring/list/comments',
array('comment_id' => implode(',', $this->comments))
array('comment_internal_id' => implode(',', $this->comments))
);
?>"> <?= count($comments) ?> </a> comments.
?>"> <?= count($comments) ?> comments </a>.
</td>
</tr>

View File

@ -18,8 +18,8 @@
</a>
</td>
<td>
Change <a href="<?= $this->href('') ?>"
> <?= count($downtimes) ?>
</a> downtimes.
Change <a data-base-target='_next' href="<?= $this->href('') ?>"
> <?= count($downtimes) ?> downtimes
</a>.
</td>
</tr>

View File

@ -1,6 +1,6 @@
<?php for ($i = 0; $i < count($objects) && $i < 5; $i++) {
$object = $objects[$i]; ?>
<a href="<?php
<a data-base-target='_next' href="<?php
if ($this->is_service) {
echo $this->href(
'monitoring/show/service',
@ -16,10 +16,9 @@
}?>,
<?php } ?> <?php
if (count($objects) > 5) {
$text = ' ' . (count($objects) - 5) . ' more ...';
} else {
$text = ' show all ... ';
echo '<i> ... ' . (count($objects) - 5) . ' more ... </i>';
}
if (!$this->is_service) {
$link = 'monitoring/list/hosts';
$target = array(
@ -27,10 +26,9 @@
);
} else {
$link = 'monitoring/list/services';
// TODO: Show multiple targets for services
$target = array(
'host' => $this->hostquery,
'service' => $this->servicequery
);
}
?> <a href="<?= $this->href($link, $target); ?>"> <?= $text ?></a>
?> <a data-base-target='_next' href='<?= $this->href($link, $this->query); ?>'><b> list all </b></a>

View File

@ -113,8 +113,8 @@
$(document).on('click', 'a', { self: this }, this.linkClicked);
// Select a table row
$(document).on('click', 'table tr[href]', { self: this }, this.rowSelected);
$(document).on('click', 'table tr a', { self: this }, this.rowSelected);
$(document).on('click', 'table.action tr[href]', { self: this }, this.rowSelected);
$(document).on('click', 'table.action tr a', { self: this }, this.rowSelected);
$(document).on('click', 'button', { self: this }, this.submitForm);
@ -256,7 +256,7 @@
var triState = parseInt($tristate.data('icinga-tristate'), 10);
// load current values
var old = $tristate.data('icinga-old');
var old = $tristate.data('icinga-old').toString();
var value = $tristate.parent().find('input:radio:checked').first().prop('checked', false).val();
// calculate the new value
@ -270,15 +270,15 @@
// 0 => 1
value = value === '1' ? '0' : '1';
}
// update form value
$tristate.parent().find('input:radio[value="' + value + '"]').prop('checked', true);
// update dummy
console.log(value + ' === ' + old + ' ?');
if (value !== old) {
$tristate.parent().find('b.tristate-changed').css('visibility', 'visible');
} else {
$tristate.parent().find('b.tristate-changed').hide();
$tristate.parent().find('b.tristate-changed').css('visibility', 'hidden');
}
self.icinga.ui.setTriState(value.toString(), $tristate);
},
@ -356,7 +356,7 @@
var icinga = self.icinga;
var $tr = $(this);
var $table = $tr.closest('table.multiselect');
var data = $table.data('icinga-multiselect-data') && $table.data('icinga-multiselect-data').split(',');
var data = self.icinga.ui.getSelectionKeys($table);
var multisel = $table.hasClass('multiselect');
var url = $table.data('icinga-multiselect-url');
@ -381,7 +381,7 @@
return;
}
// Update selection
// update selection
if (event.ctrlKey && multisel) {
icinga.ui.toogleTableRowSelection($tr);
// multi selection
@ -392,23 +392,27 @@
// single selection
icinga.ui.setTableRowSelection($tr);
}
// focuse only the current table.
// focus only the current table.
icinga.ui.focusTable($table[0]);
// Update url
// update url
var $target = self.getLinkTargetFor($tr);
if (multisel) {
var $trs = $table.find('tr[href].active');
if ($trs.length > 1) {
// display multiple rows
var query = icinga.events.selectionToQuery($trs, data, icinga);
var queries = [];
var selectionData = icinga.ui.getSelectionSetData($trs, data);
var query = icinga.ui.selectionDataToQuery(selectionData, data, icinga);
icinga.loader.loadUrl(url + '?' + query, $target);
icinga.ui.storeSelectionData(selectionData);
} else if ($trs.length === 1) {
// display a single row
icinga.loader.loadUrl($tr.attr('href'), $target);
icinga.ui.storeSelectionData($tr.attr('href'));
} else {
// display nothing
icinga.loader.loadUrl('#');
icinga.ui.storeSelectionData(null);
}
} else {
icinga.loader.loadUrl($tr.attr('href'), $target);
@ -416,42 +420,6 @@
return false;
},
selectionToQuery: function ($selection, data, icinga) {
var selections = [], queries = [];
if ($selection.length === 0) {
return '';
}
// read all current selections
$selection.each(function(ind, selected) {
var url = $(selected).attr('href');
var params = icinga.utils.parseUrl(url).params;
var tuple = {};
for (var i = 0; i < data.length; i++) {
var key = data[i];
if (params[key]) {
tuple[key] = params[key];
}
}
selections.push(tuple);
});
// create new url
if (selections.length < 2) {
// single-selection
$.each(selections[0], function(key, value){
queries.push(key + '=' + encodeURIComponent(value));
});
} else {
// multi-selection
$.each(selections, function(i, el){
$.each(el, function(key, value) {
queries.push(key + '[' + i + ']=' + encodeURIComponent(value));
});
});
}
return queries.join('&');
},
/**
* Someone clicked a link or tr[href]
@ -590,8 +558,8 @@
$(window).off('beforeunload', this.onUnload);
$(document).off('scroll', '.container', this.onContainerScroll);
$(document).off('click', 'a', this.linkClicked);
$(document).off('click', 'table tr[href]', this.rowSelected);
$(document).off('click', 'table tr a', this.rowSelected);
$(document).off('click', 'table.action tr[href]', this.rowSelected);
$(document).off('click', 'table.action tr a', this.rowSelected);
$(document).off('submit', 'form', this.submitForm);
$(document).off('click', 'button', this.submitForm);
$(document).off('change', 'form select.autosubmit', this.submitForm);

View File

@ -248,8 +248,8 @@
// TODO: Hook for response/url?
var $forms = $('[action="' + this.icinga.utils.parseUrl(url).path + '"]');
console.log('Old URL: ' + url);
var $matches = $.merge($('[href="' + url + '"]'), $forms);
$matches.each(function (idx, el) {
if ($(el).closest('#menu').length) {
$('#menu .active').removeClass('active');
@ -432,7 +432,39 @@
}
if (active) {
$('[href="' + active + '"]', req.$target).addClass('active');
var focusedUrl = this.icinga.ui.getFocusedContainerDataUrl();
var oldSelectionData = this.icinga.ui.loadSelectionData();
if (typeof oldSelectionData === 'string') {
$('[href="' + oldSelectionData + '"]', req.$target).addClass('active');
} else if (oldSelectionData !== null) {
var $container;
if (!focusedUrl) {
$container = $('document').first();
} else {
$container = $('.container[data-icinga-url="' + focusedUrl + '"]');;
}
var $table = $container.find('table.action').first();
var keys = self.icinga.ui.getSelectionKeys($table);
// build map of selected queries
var oldSelectionQueries = {};
$.each(oldSelectionData, function(i, query){
oldSelectionQueries[self.icinga.ui.selectionDataToQueryComp(query)] = true;
});
// set all new selections to active
$table.find('tr[href]').filter(function(){
var $tr = $(this);
var rowData = self.icinga.ui.getSelectionData($tr, keys, self.icinga);
var newSelectionQuery = self.icinga.ui.selectionDataToQueryComp(rowData);
if (oldSelectionQueries[newSelectionQuery]) {
return true;
}
return false;
}).addClass('active');
}
}
req.$target.trigger('rendered');
},

View File

@ -7,6 +7,13 @@
'use strict';
// Stores the icinga-data-url of the last focused table.
var focusedTableDataUrl = null;
// The stored selection data, useful for preserving selections over
// multiple reload-cycles.
var selectionData = null;
Icinga.UI = function (icinga) {
this.icinga = icinga;
@ -291,9 +298,6 @@
*/
setTableRowSelection: function ($tr) {
var $table = $tr.closest('table.multiselect');
if ($tr.hasClass('active')) {
return false;
}
$table.find('tr[href].active').removeClass('active');
$tr.addClass('active');
return true;
@ -351,6 +355,116 @@
return false;
},
/**
* Read the data from a whole set of selections.
*
* @param $selections {jQuery} All selected rows in a jQuery-selector.
* @param keys {Array} An array containing all valid keys.
* @returns {Array} An array containing an object with the data for each selection.
*/
getSelectionSetData: function($selections, keys) {
var selections = [];
var icinga = this.icinga;
// read all current selections
$selections.each(function(ind, selected) {
selections.push(icinga.ui.getSelectionData($(selected), keys, icinga));
});
return selections;
},
getSelectionKeys: function($selection)
{
var d = $selection.data('icinga-multiselect-data') && $selection.data('icinga-multiselect-data').split(',');
return d || [];
},
/**
* Read the data from the given selected object.
*
* @param $selection {jQuery} The selected object.
* @param keys {Array} An array containing all valid keys.
* @param icinga {Icinga} The main icinga object.
* @returns {Object} An object containing all key-value pairs associated with this selection.
*/
getSelectionData: function($selection, keys, icinga)
{
var url = $selection.attr('href');
var params = this.icinga.utils.parseUrl(url).params;
var tuple = {};
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (params[key]) {
tuple[key] = params[key];
}
}
return tuple;
},
/**
* Convert a set of selection data to a single query.
*
* @param selectionData {Array} The selection data generated from getSelectionData
* @returns {String} The formatted and uri-encoded query-string.
*/
selectionDataToQuery: function (selectionData) {
var queries = [];
// create new url
if (selectionData.length < 2) {
// single-selection
$.each(selectionData[0], function(key, value){
queries.push(key + '=' + encodeURIComponent(value));
});
} else {
// multi-selection
$.each(selectionData, function(i, el){
$.each(el, function(key, value) {
queries.push(key + '[' + i + ']=' + encodeURIComponent(value));
});
});
}
return queries.join('&');
},
/**
* Create a single query-argument (not compatible to selectionDataToQuery)
*
* @param data
* @returns {string}
*/
selectionDataToQueryComp: function(data) {
var queries = [];
$.each(data, function(key, value){
queries.push(key + '=' + encodeURIComponent(value));
});
return queries.join('&');
},
/**
* Store a set of selection-data to preserve it accross page-reloads
*
* @param data {Array|String|Null} The selection-data be an Array of Objects,
* containing the selection data (when multiple rows where selected), a
* String containing a single url (when only a single row was selected) or
* Null when nothing was selected.
*/
storeSelectionData: function(data) {
selectionData = data;
},
/**
* Load the last stored set of selection-data
*
* @returns {Array|String|Null} May be an Array of Objects, containing the selection data
* (when multiple rows where selected), a String containing a single url
* (when only a single row was selected) or Null when nothing was selected.
*/
loadSelectionData: function() {
return selectionData;
},
/**
* Focus the given table by deselecting all selections on all other tables.
*
@ -363,6 +477,17 @@
*/
focusTable: function (table) {
$('table').filter(function(){ return this !== table; }).find('tr[href]').removeClass('active');
var n = $(table).closest('.container').data('icinga-url');
focusedTableDataUrl = n;
},
/**
* Return the URL of the last focused table container.
*
* @returns {String} The data-icinga-url of the last focused table, which should be unique in each site.
*/
getFocusedContainerDataUrl: function() {
return focusedTableDataUrl;
},
refreshDebug: function () {
@ -473,7 +598,7 @@
// hide input boxess and remove text nodes
$target.find("input").hide();
$target.contents().filter(function() { return this.nodeType == 3; }).remove();
$target.contents().filter(function() { return this.nodeType === 3; }).remove();
// has three states?
var triState = $target.find('input[value="unchanged"]').size() > 0 ? 1 : 0;