mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-07-26 23:34:08 +02:00
Add abillity for multi and range-selection to events.js
Add the abillity to select multiple rows, with a multi-selection using the CTRL-key or a range-selection using the shift-key. Also fix several issues in the Multi-Controller of the Backend. refs #5765
This commit is contained in:
parent
6973b04211
commit
6d303f1c42
@ -31,8 +31,8 @@ use \Icinga\Web\Form;
|
|||||||
use \Icinga\Web\Controller\ActionController;
|
use \Icinga\Web\Controller\ActionController;
|
||||||
use \Icinga\Web\Widget\Tabextension\OutputFormat;
|
use \Icinga\Web\Widget\Tabextension\OutputFormat;
|
||||||
use \Icinga\Module\Monitoring\Backend;
|
use \Icinga\Module\Monitoring\Backend;
|
||||||
use \Icinga\Module\Monitoring\Object\Host;
|
use \Icinga\Data\BaseQuery;
|
||||||
use \Icinga\Module\Monitoring\Object\Service;
|
use \Icinga\Module\Monitoring\Backend\Ido\Query\StatusQuery;
|
||||||
use \Icinga\Module\Monitoring\Form\Command\MultiCommandFlagForm;
|
use \Icinga\Module\Monitoring\Form\Command\MultiCommandFlagForm;
|
||||||
use \Icinga\Module\Monitoring\DataView\HostStatus as HostStatusView;
|
use \Icinga\Module\Monitoring\DataView\HostStatus as HostStatusView;
|
||||||
use \Icinga\Module\Monitoring\DataView\ServiceStatus as ServiceStatusView;
|
use \Icinga\Module\Monitoring\DataView\ServiceStatus as ServiceStatusView;
|
||||||
@ -45,7 +45,7 @@ class Monitoring_MultiController extends ActionController
|
|||||||
{
|
{
|
||||||
public function init()
|
public function init()
|
||||||
{
|
{
|
||||||
$this->view->queries = $this->getDetailQueries();
|
$this->view->queries = $this->getAllParamsAsArray();
|
||||||
$this->backend = Backend::createBackend($this->_getParam('backend'));
|
$this->backend = Backend::createBackend($this->_getParam('backend'));
|
||||||
$this->createTabs();
|
$this->createTabs();
|
||||||
}
|
}
|
||||||
@ -55,8 +55,8 @@ class Monitoring_MultiController extends ActionController
|
|||||||
$filters = $this->view->queries;
|
$filters = $this->view->queries;
|
||||||
$errors = array();
|
$errors = array();
|
||||||
|
|
||||||
// Hosts
|
// Fetch Hosts
|
||||||
$backendQuery = HostStatusView::fromRequest(
|
$hostQuery = HostStatusView::fromRequest(
|
||||||
$this->_request,
|
$this->_request,
|
||||||
array(
|
array(
|
||||||
'host_name',
|
'host_name',
|
||||||
@ -71,15 +71,19 @@ class Monitoring_MultiController extends ActionController
|
|||||||
)
|
)
|
||||||
)->getQuery();
|
)->getQuery();
|
||||||
if ($this->_getParam('host') !== '*') {
|
if ($this->_getParam('host') !== '*') {
|
||||||
$this->applyQueryFilter($backendQuery, $filters);
|
$this->applyQueryFilter($hostQuery, $filters);
|
||||||
}
|
}
|
||||||
$hosts = $backendQuery->fetchAll();
|
$hosts = $hostQuery->fetchAll();
|
||||||
|
|
||||||
// Comments
|
// Fetch comments
|
||||||
$commentQuery = CommentView::fromRequest($this->_request)->getQuery();
|
$commentQuery = $this->applyQueryFilter(
|
||||||
$this->applyQueryFilter($commentQuery, $filters);
|
CommentView::fromRequest($this->_request)->getQuery(),
|
||||||
$comments = array_keys($this->getUniqueValues($commentQuery->fetchAll(), 'comment_id'));
|
$filters,
|
||||||
|
'comment_host'
|
||||||
|
);
|
||||||
|
$comments = array_keys($this->getUniqueValues($commentQuery->fetchAll(), 'comment_internal_id'));
|
||||||
|
|
||||||
|
// Populate view
|
||||||
$this->view->objects = $this->view->hosts = $hosts;
|
$this->view->objects = $this->view->hosts = $hosts;
|
||||||
$this->view->problems = $this->getProblems($hosts);
|
$this->view->problems = $this->getProblems($hosts);
|
||||||
$this->view->comments = isset($comments) ? $comments : $this->getComments($hosts);
|
$this->view->comments = isset($comments) ? $comments : $this->getComments($hosts);
|
||||||
@ -87,6 +91,7 @@ class Monitoring_MultiController extends ActionController
|
|||||||
$this->view->downtimes = $this->getDowntimes($hosts);
|
$this->view->downtimes = $this->getDowntimes($hosts);
|
||||||
$this->view->errors = $errors;
|
$this->view->errors = $errors;
|
||||||
|
|
||||||
|
// Handle configuration changes
|
||||||
$this->handleConfigurationForm(array(
|
$this->handleConfigurationForm(array(
|
||||||
'host_passive_checks_enabled' => 'Passive Checks',
|
'host_passive_checks_enabled' => 'Passive Checks',
|
||||||
'host_active_checks_enabled' => 'Active Checks',
|
'host_active_checks_enabled' => 'Active Checks',
|
||||||
@ -99,23 +104,36 @@ class Monitoring_MultiController extends ActionController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Apply the query-filter received
|
||||||
|
*
|
||||||
* @param $backendQuery BaseQuery The query to apply the filter to
|
* @param $backendQuery BaseQuery The query to apply the filter to
|
||||||
* @param $filter array Containing the filter expressions from the request
|
* @param $filter array Containing the queries of the current request, converted into an
|
||||||
|
* array-structure.
|
||||||
|
* @param $hostColumn string The name of the host-column in the BaseQuery, defaults to 'host_name'
|
||||||
|
* @param $serviceColumn string The name of the service-column in the BaseQuery, defaults to 'service-description'
|
||||||
|
*
|
||||||
|
* @return BaseQuery The given BaseQuery
|
||||||
*/
|
*/
|
||||||
private function applyQueryFilter($backendQuery, $filter)
|
private function applyQueryFilter(
|
||||||
{
|
BaseQuery $backendQuery,
|
||||||
|
array $filter,
|
||||||
|
$hostColumn = 'host_name',
|
||||||
|
$serviceColumn = 'service_description'
|
||||||
|
) {
|
||||||
// fetch specified hosts
|
// fetch specified hosts
|
||||||
foreach ($filter as $index => $expr) {
|
foreach ($filter as $index => $expr) {
|
||||||
|
// Every query entry must define at least the host.
|
||||||
if (!array_key_exists('host', $expr)) {
|
if (!array_key_exists('host', $expr)) {
|
||||||
$errors[] = 'Query ' . $index . ' misses property host.';
|
$errors[] = 'Query ' . $index . ' misses property host.';
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// apply filter expressions from query
|
// apply filter expressions from query
|
||||||
$backendQuery->orWhere('host_name', $expr['host']);
|
$backendQuery->orWhere($hostColumn, $expr['host']);
|
||||||
if (array_key_exists('service', $expr)) {
|
if (array_key_exists('service', $expr)) {
|
||||||
$backendQuery->andWhere('service_description', $expr['service']);
|
$backendQuery->andWhere($serviceColumn, $expr['service']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return $backendQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -134,23 +152,25 @@ class Monitoring_MultiController extends ActionController
|
|||||||
if (is_array($value)) {
|
if (is_array($value)) {
|
||||||
$unique[$value[$key]] = $value[$key];
|
$unique[$value[$key]] = $value[$key];
|
||||||
} else {
|
} else {
|
||||||
$unique[$value->{$key}] = $value->{$key};
|
$unique[$value->$key] = $value->$key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $unique;
|
return $unique;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the numbers of problems in the given objects
|
* Get the numbers of problems of the given objects
|
||||||
*
|
*
|
||||||
* @param $object array The hosts or services
|
* @param $objects The objects containing the problems
|
||||||
|
*
|
||||||
|
* @return int The problem count
|
||||||
*/
|
*/
|
||||||
private function getProblems($objects)
|
private function getProblems($objects)
|
||||||
{
|
{
|
||||||
$problems = 0;
|
$problems = 0;
|
||||||
foreach ($objects as $object) {
|
foreach ($objects as $object) {
|
||||||
if (property_exists($object, 'host_unhandled_service_count')) {
|
if (property_exists($object, 'host_unhandled_service_count')) {
|
||||||
$problems += $object->{'host_unhandled_service_count'};
|
$problems += $object->host_unhandled_service_count;
|
||||||
} else if (
|
} else if (
|
||||||
property_exists($object, 'service_handled') &&
|
property_exists($object, 'service_handled') &&
|
||||||
!$object->service_handled &&
|
!$object->service_handled &&
|
||||||
@ -175,7 +195,7 @@ class Monitoring_MultiController extends ActionController
|
|||||||
{
|
{
|
||||||
$objectnames = array();
|
$objectnames = array();
|
||||||
foreach ($objects as $object) {
|
foreach ($objects as $object) {
|
||||||
$objectnames[] = $object->{$property};
|
$objectnames[] = $object->$property;
|
||||||
}
|
}
|
||||||
return $objectnames;
|
return $objectnames;
|
||||||
}
|
}
|
||||||
@ -224,7 +244,7 @@ class Monitoring_MultiController extends ActionController
|
|||||||
// Comments
|
// Comments
|
||||||
$commentQuery = CommentView::fromRequest($this->_request)->getQuery();
|
$commentQuery = CommentView::fromRequest($this->_request)->getQuery();
|
||||||
$this->applyQueryFilter($commentQuery, $filters);
|
$this->applyQueryFilter($commentQuery, $filters);
|
||||||
$comments = array_keys($this->getUniqueValues($commentQuery->fetchAll(), 'comment_id'));
|
$comments = array_keys($this->getUniqueValues($commentQuery->fetchAll(), 'comment_internal_id'));
|
||||||
|
|
||||||
$this->view->objects = $this->view->services = $services;
|
$this->view->objects = $this->view->services = $services;
|
||||||
$this->view->problems = $this->getProblems($services);
|
$this->view->problems = $this->getProblems($services);
|
||||||
@ -263,28 +283,37 @@ class Monitoring_MultiController extends ActionController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch all requests from the 'detail' parameter.
|
* "Flips" the structure of the objects created by _getAllParams
|
||||||
*
|
*
|
||||||
* @return array An array of request that contain
|
* Regularly, _getAllParams would return queries like <b>host[0]=value1&service[0]=value2</b> as
|
||||||
* the filter arguments as properties.
|
* two entirely separate arrays. Instead, we want it as one single array, containing one single object
|
||||||
|
* for each index, containing all of its members as keys.
|
||||||
|
*
|
||||||
|
* @return array An array of all query parameters (See example above)
|
||||||
|
* <b>
|
||||||
|
* array( <br />
|
||||||
|
* 0 => array(host => value1, service => value2), <br />
|
||||||
|
* ... <br />
|
||||||
|
* )
|
||||||
|
* </b>
|
||||||
*/
|
*/
|
||||||
private function getDetailQueries()
|
private function getAllParamsAsArray()
|
||||||
{
|
{
|
||||||
$details = $this->_getAllParams();
|
$details = $this->_getAllParams();
|
||||||
$objects = array();
|
$queries = array();
|
||||||
|
|
||||||
foreach ($details as $property => $values) {
|
foreach ($details as $property => $values) {
|
||||||
if (!is_array($values)) {
|
if (!is_array($values)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
foreach ($values as $index => $value) {
|
foreach ($values as $index => $value) {
|
||||||
if (!array_key_exists($index, $objects)) {
|
if (!array_key_exists($index, $queries)) {
|
||||||
$objects[$index] = array();
|
$queries[$index] = array();
|
||||||
}
|
}
|
||||||
$objects[$index][$property] = $value;
|
$queries[$index][$property] = $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $objects;
|
return $queries;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,12 +19,17 @@ if ($hosts->count() === 0) {
|
|||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<table data-base-target="_next" class="action multiselect">
|
<table
|
||||||
|
data-base-target="_next"
|
||||||
|
class="action multiselect"
|
||||||
|
data-icinga-multiselect-url="<?= $this->href("/monitoring/multi/host") ?>"
|
||||||
|
data-icinga-multiselect-data="host"
|
||||||
|
>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php foreach($hosts as $host):
|
<?php foreach($hosts as $host):
|
||||||
|
|
||||||
$hostStateName = strtolower($this->util()->getHostStateName($host->host_state));
|
$hostStateName = strtolower($this->util()->getHostStateName($host->host_state));
|
||||||
$hostLink = $this->href('monitoring/show/host', array('host' => $host->host_name));
|
$hostLink = $this->href('/monitoring/show/host', array('host' => $host->host_name));
|
||||||
|
|
||||||
$icons = array();
|
$icons = array();
|
||||||
if (!$host->host_handled && $host->host_state > 0){
|
if (!$host->host_handled && $host->host_state > 0){
|
||||||
|
@ -13,6 +13,12 @@
|
|||||||
|
|
||||||
Icinga.Events.prototype = {
|
Icinga.Events.prototype = {
|
||||||
|
|
||||||
|
keyboard: {
|
||||||
|
ctrlKey: false,
|
||||||
|
altKey: false,
|
||||||
|
shiftKey: false
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Icinga will call our initialize() function once it's ready
|
* Icinga will call our initialize() function once it's ready
|
||||||
*/
|
*/
|
||||||
@ -65,7 +71,7 @@
|
|||||||
type: 'pie',
|
type: 'pie',
|
||||||
sliceColors: ['#44bb77', '#ffaa44', '#ff5566', '#dcd'],
|
sliceColors: ['#44bb77', '#ffaa44', '#ff5566', '#dcd'],
|
||||||
width: '2em',
|
width: '2em',
|
||||||
height: '2em',
|
height: '2em'
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -90,7 +96,10 @@
|
|||||||
$(document).on('click', 'a', { self: this }, this.linkClicked);
|
$(document).on('click', 'a', { self: this }, this.linkClicked);
|
||||||
|
|
||||||
// We treat tr's with a href attribute like links
|
// We treat tr's with a href attribute like links
|
||||||
$(document).on('click', 'tr[href]', { self: this }, this.linkClicked);
|
$(document).on('click', ':not(table) tr[href]', { self: this }, this.linkClicked);
|
||||||
|
|
||||||
|
// When tables have the class 'multiselect', multiple selection is possible.
|
||||||
|
$(document).on('click', 'table tr[href]', { self: this }, this.rowSelected);
|
||||||
|
|
||||||
$(document).on('click', 'button', { self: this }, this.submitForm);
|
$(document).on('click', 'button', { self: this }, this.submitForm);
|
||||||
|
|
||||||
@ -277,6 +286,151 @@
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleExternalTarget: function($node) {
|
||||||
|
var linkTarget = $node.attr('target');
|
||||||
|
|
||||||
|
// TODO: Let remote links pass through. Right now they only work
|
||||||
|
// combined with target="_blank" or target="_self"
|
||||||
|
// window.open is used as return true; didn't work reliable
|
||||||
|
if (linkTarget === '_blank' || linkTarget === '_self') {
|
||||||
|
window.open(href, linkTarget);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle table selection.
|
||||||
|
*/
|
||||||
|
rowSelected: function(event) {
|
||||||
|
var self = event.data.self;
|
||||||
|
var icinga = self.icinga;
|
||||||
|
var $tr = $(this);
|
||||||
|
var $table = $tr.closest('table.multiselect');
|
||||||
|
var data = $table.data('icinga-multiselect-data').split(',');
|
||||||
|
var multisel = $table.hasClass('multiselect');
|
||||||
|
var url = $table.data('icinga-multiselect-url');
|
||||||
|
var $trs, $target;
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
if (icinga.events.handleExternalTarget($tr)) {
|
||||||
|
// link handled externally
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!data) {
|
||||||
|
icinga.logger.error('A table with multiselection must define the attribute "data-icinga-multiselect-data"');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!url) {
|
||||||
|
icinga.logger.error('A table with multiselection must define the attribute "data-icinga-multiselect-url"');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update selection
|
||||||
|
if (event.ctrlKey && multisel) {
|
||||||
|
// multi selection
|
||||||
|
if ($tr.hasClass('active')) {
|
||||||
|
$tr.removeClass('active');
|
||||||
|
} else {
|
||||||
|
$tr.addClass('active');
|
||||||
|
}
|
||||||
|
} else if (event.shiftKey && multisel) {
|
||||||
|
// range selection
|
||||||
|
|
||||||
|
var $rows = $table.find('tr[href]'),
|
||||||
|
from, to;
|
||||||
|
var selected = this;
|
||||||
|
|
||||||
|
// TODO: find a better place for this
|
||||||
|
$rows.find('td').attr('unselectable', 'on')
|
||||||
|
.css('user-select', 'none')
|
||||||
|
.css('-webkit-user-select', 'none')
|
||||||
|
.css('-moz-user-select', 'none')
|
||||||
|
.css('-ms-user-select', 'none');
|
||||||
|
|
||||||
|
$rows.each(function(i, el) {
|
||||||
|
if ($(el).hasClass('active') || el === selected) {
|
||||||
|
if (!from) {
|
||||||
|
from = el;
|
||||||
|
}
|
||||||
|
to = el;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var inRange = false;
|
||||||
|
$rows.each(function(i, el){
|
||||||
|
if (el === from) {
|
||||||
|
inRange = true;
|
||||||
|
}
|
||||||
|
if (inRange) {
|
||||||
|
$(el).addClass('active');
|
||||||
|
}
|
||||||
|
if (el === to) {
|
||||||
|
inRange = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// single selection
|
||||||
|
if ($tr.hasClass('active')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$table.find('tr[href].active').removeClass('active');
|
||||||
|
$tr.addClass('active');
|
||||||
|
}
|
||||||
|
$trs = $table.find('tr[href].active');
|
||||||
|
|
||||||
|
// Update url
|
||||||
|
$target = self.getLinkTargetFor($tr);
|
||||||
|
if ($trs.length > 1) {
|
||||||
|
// display multiple rows
|
||||||
|
var query = icinga.events.selectionToQuery($trs, data, icinga);
|
||||||
|
icinga.loader.loadUrl(url + '?' + query, $target);
|
||||||
|
} else if ($trs.length === 1) {
|
||||||
|
// display a single row
|
||||||
|
icinga.loader.loadUrl($tr.attr('href'), $target);
|
||||||
|
} else {
|
||||||
|
// display nothing
|
||||||
|
icinga.loader.loadUrl('#');
|
||||||
|
}
|
||||||
|
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]
|
* Someone clicked a link or tr[href]
|
||||||
*/
|
*/
|
||||||
|
@ -99,6 +99,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
console.log("$.ajax({ url = " + url + " })");
|
||||||
var req = $.ajax({
|
var req = $.ajax({
|
||||||
type : method,
|
type : method,
|
||||||
url : url,
|
url : url,
|
||||||
|
@ -104,7 +104,7 @@
|
|||||||
path : a.pathname.replace(/^([^\/])/,'/$1'),
|
path : a.pathname.replace(/^([^\/])/,'/$1'),
|
||||||
relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [,''])[1],
|
relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [,''])[1],
|
||||||
segments: a.pathname.replace(/^\//,'').split('/'),
|
segments: a.pathname.replace(/^\//,'').split('/'),
|
||||||
params : this.parseParams(a),
|
params : this.parseParams(a)
|
||||||
};
|
};
|
||||||
a = null;
|
a = null;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user