From 6d303f1c42f2da396148423ba020c42f8d847ff6 Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Wed, 2 Apr 2014 18:23:01 +0200 Subject: [PATCH 1/8] 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 --- .../controllers/MultiController.php | 121 +++++++++----- .../views/scripts/list/hosts.phtml | 9 +- public/js/icinga/events.js | 158 +++++++++++++++++- public/js/icinga/loader.js | 1 + public/js/icinga/utils.js | 2 +- 5 files changed, 240 insertions(+), 51 deletions(-) diff --git a/modules/monitoring/application/controllers/MultiController.php b/modules/monitoring/application/controllers/MultiController.php index 2599912c3..5657f8552 100644 --- a/modules/monitoring/application/controllers/MultiController.php +++ b/modules/monitoring/application/controllers/MultiController.php @@ -31,12 +31,12 @@ use \Icinga\Web\Form; use \Icinga\Web\Controller\ActionController; use \Icinga\Web\Widget\Tabextension\OutputFormat; use \Icinga\Module\Monitoring\Backend; -use \Icinga\Module\Monitoring\Object\Host; -use \Icinga\Module\Monitoring\Object\Service; +use \Icinga\Data\BaseQuery; +use \Icinga\Module\Monitoring\Backend\Ido\Query\StatusQuery; 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\Comment as CommentView; +use \Icinga\Module\Monitoring\DataView\Comment as CommentView; /** * Displays aggregations collections of multiple objects. @@ -45,7 +45,7 @@ class Monitoring_MultiController extends ActionController { public function init() { - $this->view->queries = $this->getDetailQueries(); + $this->view->queries = $this->getAllParamsAsArray(); $this->backend = Backend::createBackend($this->_getParam('backend')); $this->createTabs(); } @@ -53,10 +53,10 @@ class Monitoring_MultiController extends ActionController public function hostAction() { $filters = $this->view->queries; - $errors = array(); + $errors = array(); - // Hosts - $backendQuery = HostStatusView::fromRequest( + // Fetch Hosts + $hostQuery = HostStatusView::fromRequest( $this->_request, array( 'host_name', @@ -71,51 +71,69 @@ class Monitoring_MultiController extends ActionController ) )->getQuery(); if ($this->_getParam('host') !== '*') { - $this->applyQueryFilter($backendQuery, $filters); + $this->applyQueryFilter($hostQuery, $filters); } - $hosts = $backendQuery->fetchAll(); + $hosts = $hostQuery->fetchAll(); - // Comments - $commentQuery = CommentView::fromRequest($this->_request)->getQuery(); - $this->applyQueryFilter($commentQuery, $filters); - $comments = array_keys($this->getUniqueValues($commentQuery->fetchAll(), 'comment_id')); + // Fetch comments + $commentQuery = $this->applyQueryFilter( + CommentView::fromRequest($this->_request)->getQuery(), + $filters, + 'comment_host' + ); + $comments = array_keys($this->getUniqueValues($commentQuery->fetchAll(), 'comment_internal_id')); - $this->view->objects = $this->view->hosts = $hosts; - $this->view->problems = $this->getProblems($hosts); - $this->view->comments = isset($comments) ? $comments : $this->getComments($hosts); + // Populate view + $this->view->objects = $this->view->hosts = $hosts; + $this->view->problems = $this->getProblems($hosts); + $this->view->comments = isset($comments) ? $comments : $this->getComments($hosts); $this->view->hostnames = $this->getProperties($hosts, 'host_name'); $this->view->downtimes = $this->getDowntimes($hosts); - $this->view->errors = $errors; + $this->view->errors = $errors; + // 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_active_checks_enabled' => 'Active Checks', + 'host_obsessing' => 'Obsessing', + 'host_notifications_enabled' => 'Notifications', + 'host_event_handler_enabled' => 'Event Handler', 'host_flap_detection_enabled' => 'Flap Detection' )); $this->view->form->setAction('/icinga2-web/monitoring/multi/host'); } /** - * @param $backendQuery BaseQuery The query to apply the filter to - * @param $filter array Containing the filter expressions from the request + * Apply the query-filter received + * + * @param $backendQuery BaseQuery The query to apply the filter to + * @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 foreach ($filter as $index => $expr) { + // Every query entry must define at least the host. if (!array_key_exists('host', $expr)) { $errors[] = 'Query ' . $index . ' misses property host.'; continue; } // apply filter expressions from query - $backendQuery->orWhere('host_name', $expr['host']); + $backendQuery->orWhere($hostColumn, $expr['host']); 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)) { $unique[$value[$key]] = $value[$key]; } else { - $unique[$value->{$key}] = $value->{$key}; + $unique[$value->$key] = $value->$key; } } 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) { $problems = 0; foreach ($objects as $object) { if (property_exists($object, 'host_unhandled_service_count')) { - $problems += $object->{'host_unhandled_service_count'}; + $problems += $object->host_unhandled_service_count; } else if ( property_exists($object, 'service_handled') && !$object->service_handled && @@ -175,7 +195,7 @@ class Monitoring_MultiController extends ActionController { $objectnames = array(); foreach ($objects as $object) { - $objectnames[] = $object->{$property}; + $objectnames[] = $object->$property; } return $objectnames; } @@ -224,7 +244,7 @@ class Monitoring_MultiController extends ActionController // Comments $commentQuery = CommentView::fromRequest($this->_request)->getQuery(); $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->problems = $this->getProblems($services); @@ -236,9 +256,9 @@ class Monitoring_MultiController extends ActionController $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_active_checks_enabled' => 'Active Checks', + 'service_notifications_enabled' => 'Notifications', + 'service_event_handler_enabled' => 'Event Handler', 'service_flap_detection_enabled' => 'Flap Detection' )); $this->view->form->setAction('/icinga2-web/monitoring/multi/service'); @@ -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 - * the filter arguments as properties. + * Regularly, _getAllParams would return queries like host[0]=value1&service[0]=value2 as + * 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) + * + * array(
+ * 0 => array(host => value1, service => value2),
+ * ...
+ * ) + *
*/ - private function getDetailQueries() + private function getAllParamsAsArray() { $details = $this->_getAllParams(); - $objects = array(); + $queries = array(); foreach ($details as $property => $values) { if (!is_array($values)) { continue; } foreach ($values as $index => $value) { - if (!array_key_exists($index, $objects)) { - $objects[$index] = array(); + if (!array_key_exists($index, $queries)) { + $queries[$index] = array(); } - $objects[$index][$property] = $value; + $queries[$index][$property] = $value; } } - return $objects; + return $queries; } /** diff --git a/modules/monitoring/application/views/scripts/list/hosts.phtml b/modules/monitoring/application/views/scripts/list/hosts.phtml index c2979aa25..be7108fdc 100644 --- a/modules/monitoring/application/views/scripts/list/hosts.phtml +++ b/modules/monitoring/application/views/scripts/list/hosts.phtml @@ -19,12 +19,17 @@ if ($hosts->count() === 0) { } ?> - +
" + data-icinga-multiselect-data="host" +> 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(); if (!$host->host_handled && $host->host_state > 0){ diff --git a/public/js/icinga/events.js b/public/js/icinga/events.js index 614b85c38..dab9a4274 100644 --- a/public/js/icinga/events.js +++ b/public/js/icinga/events.js @@ -13,6 +13,12 @@ Icinga.Events.prototype = { + keyboard: { + ctrlKey: false, + altKey: false, + shiftKey: false + }, + /** * Icinga will call our initialize() function once it's ready */ @@ -65,7 +71,7 @@ type: 'pie', sliceColors: ['#44bb77', '#ffaa44', '#ff5566', '#dcd'], width: '2em', - height: '2em', + height: '2em' }); }, @@ -90,7 +96,10 @@ $(document).on('click', 'a', { self: this }, this.linkClicked); // 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); @@ -277,6 +286,151 @@ 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] */ diff --git a/public/js/icinga/loader.js b/public/js/icinga/loader.js index 04035e1e6..dc2d06667 100644 --- a/public/js/icinga/loader.js +++ b/public/js/icinga/loader.js @@ -99,6 +99,7 @@ } var self = this; + console.log("$.ajax({ url = " + url + " })"); var req = $.ajax({ type : method, url : url, diff --git a/public/js/icinga/utils.js b/public/js/icinga/utils.js index 7a756092e..bf081d016 100644 --- a/public/js/icinga/utils.js +++ b/public/js/icinga/utils.js @@ -104,7 +104,7 @@ path : a.pathname.replace(/^([^\/])/,'/$1'), relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [,''])[1], segments: a.pathname.replace(/^\//,'').split('/'), - params : this.parseParams(a), + params : this.parseParams(a) }; a = null; From 3b2bb3c4fc59fbdb07fde688c00ba83c007b1a21 Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Mon, 7 Apr 2014 13:36:25 +0200 Subject: [PATCH 2/8] Move functions to perform single, multi and range-selection into the ui module refs #5765 --- .../controllers/MultiController.php | 2 +- public/js/icinga/events.js | 48 ++--------- public/js/icinga/loader.js | 1 - public/js/icinga/ui.js | 82 +++++++++++++++++++ 4 files changed, 88 insertions(+), 45 deletions(-) diff --git a/modules/monitoring/application/controllers/MultiController.php b/modules/monitoring/application/controllers/MultiController.php index 5657f8552..63e8141bc 100644 --- a/modules/monitoring/application/controllers/MultiController.php +++ b/modules/monitoring/application/controllers/MultiController.php @@ -32,7 +32,6 @@ use \Icinga\Web\Controller\ActionController; use \Icinga\Web\Widget\Tabextension\OutputFormat; use \Icinga\Module\Monitoring\Backend; use \Icinga\Data\BaseQuery; -use \Icinga\Module\Monitoring\Backend\Ido\Query\StatusQuery; use \Icinga\Module\Monitoring\Form\Command\MultiCommandFlagForm; use \Icinga\Module\Monitoring\DataView\HostStatus as HostStatusView; use \Icinga\Module\Monitoring\DataView\ServiceStatus as ServiceStatusView; @@ -238,6 +237,7 @@ class Monitoring_MultiController extends ActionController )->getQuery(); if ($this->_getParam('service') !== '*' && $this->_getParam('host') !== '*') { $this->applyQueryFilter($backendQuery, $filters); + $this->applyQueryFilter($backendQuery, $filters); } $services = $backendQuery->fetchAll(); diff --git a/public/js/icinga/events.js b/public/js/icinga/events.js index dab9a4274..409d3e1ed 100644 --- a/public/js/icinga/events.js +++ b/public/js/icinga/events.js @@ -26,6 +26,7 @@ this.applyGlobalDefaults(); this.applyHandlers($('#layout')); this.icinga.ui.prepareContainers(); + this.icinga.ui.prepareMultiselectTables(); }, // TODO: What's this? @@ -98,7 +99,7 @@ // We treat tr's with a href attribute like links $(document).on('click', ':not(table) tr[href]', { self: this }, this.linkClicked); - // When tables have the class 'multiselect', multiple selection is possible. + // Select a table. $(document).on('click', 'table tr[href]', { self: this }, this.rowSelected); $(document).on('click', 'button', { self: this }, this.submitForm); @@ -328,53 +329,14 @@ // Update selection if (event.ctrlKey && multisel) { + icinga.ui.toogleTableRowSelection($tr); // 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; - } - }); + icinga.ui.addTableRowRangeSelection($tr); } else { // single selection - if ($tr.hasClass('active')) { - return false; - } - $table.find('tr[href].active').removeClass('active'); - $tr.addClass('active'); + icinga.ui.setTableRowSelection($tr); } $trs = $table.find('tr[href].active'); diff --git a/public/js/icinga/loader.js b/public/js/icinga/loader.js index dc2d06667..04035e1e6 100644 --- a/public/js/icinga/loader.js +++ b/public/js/icinga/loader.js @@ -99,7 +99,6 @@ } var self = this; - console.log("$.ajax({ url = " + url + " })"); var req = $.ajax({ type : method, url : url, diff --git a/public/js/icinga/ui.js b/public/js/icinga/ui.js index 27ebae7a4..43104fcd3 100644 --- a/public/js/icinga/ui.js +++ b/public/js/icinga/ui.js @@ -271,6 +271,88 @@ */ }, + /** + * Prepare all multiselectable tables for multi-selection by + * removing the regular text selection. + */ + prepareMultiselectTables: function () { + var $rows = $('table.multiselect tr[href]'); + $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'); + }, + + /** + * Add the given table-row to the selection of the closest + * table + * + * @param $tr {jQuery} The selected table row. + * @returns {boolean} If the selection was changed. + */ + 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; + }, + + /** + * Toggle the given table row to "on" when not selected, or to "off" when + * currently selected. + * + * @param $tr {jQuery} The table row. + * @returns {boolean} If the selection was changed. + */ + toogleTableRowSelection: function ($tr) { + // multi selection + if ($tr.hasClass('active')) { + $tr.removeClass('active'); + } else { + $tr.addClass('active'); + } + return true; + }, + + /** + * Add a new selection range to the closest table, using the selected row as + * range target + * + * @param $tr {jQuery} The target of the selected range. + * @returns {boolean} If the selection was changed. + */ + addTableRowRangeSelection: function ($tr) { + var $table = $tr.closest('table.multiselect'); + var $rows = $table.find('tr[href]'), + from, to; + var selected = $tr.first().get(0); + $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; + } + }); + return false; + }, + refreshDebug: function () { var size = this.getDefaultFontSize().toString(); From cb630d45add82d6e1c758f075259c333117c436b Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Wed, 9 Apr 2014 19:11:56 +0200 Subject: [PATCH 3/8] Fix issues issues with javascript multiselection refs #5765 --- .../controllers/MultiController.php | 104 +++++++++--------- .../views/scripts/list/hosts-compact.phtml | 7 +- .../views/scripts/list/services.phtml | 7 +- .../scripts/multi/components/objectlist.phtml | 7 ++ public/css/icinga/selection-toolbar.less | 0 public/js/icinga/events.js | 25 ++++- public/js/icinga/ui.js | 18 ++- 7 files changed, 109 insertions(+), 59 deletions(-) create mode 100644 modules/monitoring/application/views/scripts/multi/components/objectlist.phtml create mode 100644 public/css/icinga/selection-toolbar.less diff --git a/modules/monitoring/application/controllers/MultiController.php b/modules/monitoring/application/controllers/MultiController.php index 63e8141bc..74b2becee 100644 --- a/modules/monitoring/application/controllers/MultiController.php +++ b/modules/monitoring/application/controllers/MultiController.php @@ -102,6 +102,61 @@ class Monitoring_MultiController extends ActionController $this->view->form->setAction('/icinga2-web/monitoring/multi/host'); } + + public function serviceAction() + { + $filters = $this->view->queries; + $errors = array(); + + $backendQuery = ServiceStatusView::fromRequest( + $this->_request, + array( + 'host_name', + 'service_description', + 'service_handled', + 'service_state', + 'service_in_downtime', + + 'service_passive_checks_enabled', + 'service_notifications_enabled', + 'service_event_handler_enabled', + 'service_flap_detection_enabled', + 'service_active_checks_enabled' + ) + )->getQuery(); + if ($this->_getParam('service') !== '*' && $this->_getParam('host') !== '*') { + $this->applyQueryFilter($backendQuery, $filters); + $this->applyQueryFilter($backendQuery, $filters); + } + $services = $backendQuery->fetchAll(); + + // Comments + $commentQuery = $this->applyQueryFilter( + CommentView::fromRequest($this->_request)->getQuery(), + $filters, + 'comment_host', + 'comment_service' + ); + $comments = array_keys($this->getUniqueValues($commentQuery->fetchAll(), 'comment_internal_id')); + + $this->view->objects = $this->view->services = $services; + $this->view->problems = $this->getProblems($services); + $this->view->comments = isset($comments) ? $comments : $this->getComments($services); + $this->view->hostnames = $this->getProperties($services, 'host_name'); + $this->view->servicenames = $this->getProperties($services, 'service_description'); + $this->view->downtimes = $this->getDowntimes($services); + $this->view->errors = $errors; + + $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' + )); + $this->view->form->setAction('/icinga2-web/monitoring/multi/service'); + } + /** * Apply the query-filter received * @@ -214,55 +269,6 @@ class Monitoring_MultiController extends ActionController return $downtimes; } - public function serviceAction() - { - $filters = $this->view->queries; - $errors = array(); - - $backendQuery = ServiceStatusView::fromRequest( - $this->_request, - array( - 'host_name', - 'service_description', - 'service_handled', - 'service_state', - 'service_in_downtime', - - 'service_passive_checks_enabled', - 'service_notifications_enabled', - 'service_event_handler_enabled', - 'service_flap_detection_enabled', - 'service_active_checks_enabled' - ) - )->getQuery(); - if ($this->_getParam('service') !== '*' && $this->_getParam('host') !== '*') { - $this->applyQueryFilter($backendQuery, $filters); - $this->applyQueryFilter($backendQuery, $filters); - } - $services = $backendQuery->fetchAll(); - - // Comments - $commentQuery = CommentView::fromRequest($this->_request)->getQuery(); - $this->applyQueryFilter($commentQuery, $filters); - $comments = array_keys($this->getUniqueValues($commentQuery->fetchAll(), 'comment_internal_id')); - - $this->view->objects = $this->view->services = $services; - $this->view->problems = $this->getProblems($services); - $this->view->comments = isset($comments) ? $comments : $this->getComments($services); - $this->view->hostnames = $this->getProperties($services, 'host_name'); - $this->view->servicenames = $this->getProperties($services, 'service_description'); - $this->view->downtimes = $this->getDowntimes($services); - $this->view->errors = $errors; - - $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' - )); - $this->view->form->setAction('/icinga2-web/monitoring/multi/service'); - } /** * Handle the form to edit configuration flags. diff --git a/modules/monitoring/application/views/scripts/list/hosts-compact.phtml b/modules/monitoring/application/views/scripts/list/hosts-compact.phtml index b6ac4e15e..d06c3af32 100644 --- a/modules/monitoring/application/views/scripts/list/hosts-compact.phtml +++ b/modules/monitoring/application/views/scripts/list/hosts-compact.phtml @@ -5,7 +5,12 @@ if ($hosts->count() === 0) { return; } -?>
+?>
" + data-icinga-multiselect-data="host" +> sortControl ?>
-
+
" + data-icinga-multiselect-data="service,host" +> 1) { // display multiple rows @@ -415,6 +422,11 @@ return false; } + // ignore links inside of tables. + if ($a.closest('table tr').length > 0) { + return; + } + // Handle all other links as XHR requests event.stopPropagation(); event.preventDefault(); @@ -525,7 +537,8 @@ $(window).off('beforeunload', this.onUnload); $(document).off('scroll', '.container', this.onContainerScroll); $(document).off('click', 'a', this.linkClicked); - $(document).off('click', 'tr[href]', this.linkClicked); + $(document).off('click', 'table tr[href]', this.rowSelected); + $(document).off('click', 'table tr a', this.rowSelected); $(document).off('submit', 'form', this.submitForm); $(document).off('click', 'button', this.submitForm); $(document).off('change', 'form select.autosubmit', this.submitForm); diff --git a/public/js/icinga/ui.js b/public/js/icinga/ui.js index 43104fcd3..134c2f75b 100644 --- a/public/js/icinga/ui.js +++ b/public/js/icinga/ui.js @@ -286,7 +286,7 @@ /** * Add the given table-row to the selection of the closest - * table + * table and deselect all other rows of the closest table. * * @param $tr {jQuery} The selected table row. * @returns {boolean} If the selection was changed. @@ -320,7 +320,7 @@ /** * Add a new selection range to the closest table, using the selected row as - * range target + * range target. * * @param $tr {jQuery} The target of the selected range. * @returns {boolean} If the selection was changed. @@ -353,6 +353,20 @@ return false; }, + /** + * Focus the given table by deselecting all selections on all other tables. + * + * Focusing a table is important for environments with multiple tables like + * the dashboard. It should only be possible to select rows at one table at a time, + * when a user selects a row on a table all rows that are not child of the given table + * will be removed from the selection. + * + * @param table {htmlElement} The table to focus. + */ + focusTable: function (table) { + $('table').filter(function(){ return this !== table; }).find('tr[href]').removeClass('active'); + }, + refreshDebug: function () { var size = this.getDefaultFontSize().toString(); From 081b5072a6b3ffbd1a47ef091087db5f68b3d12e Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Wed, 9 Apr 2014 19:13:46 +0200 Subject: [PATCH 4/8] Fix layout of MultiController views refs #5765 --- .../views/helpers/FormTriStateCheckbox.php | 17 +- .../Web/Form/Element/TriStateCheckbox.php | 4 +- library/Icinga/Web/StyleSheet.php | 1 + .../controllers/MultiController.php | 1 + .../forms/Command/MultiCommandFlagForm.php | 33 ++-- .../views/helpers/SelectionToolbar.php | 7 +- .../views/scripts/list/hosts.phtml | 3 +- .../views/scripts/list/services.phtml | 1 + .../scripts/multi/components/comments.phtml | 94 ++++------- .../multi/components/configuration.phtml | 36 +++-- .../scripts/multi/components/downtimes.phtml | 78 ++++----- .../scripts/multi/components/objectlist.phtml | 42 ++++- .../scripts/multi/components/summary.phtml | 149 ++++++++---------- .../views/scripts/multi/service.phtml | 56 ++++--- public/css/icinga/selection-toolbar.less | 8 + public/js/icinga/events.js | 35 ++-- public/js/icinga/loader.js | 12 +- public/js/icinga/ui.js | 2 - 18 files changed, 290 insertions(+), 289 deletions(-) diff --git a/application/views/helpers/FormTriStateCheckbox.php b/application/views/helpers/FormTriStateCheckbox.php index 5e01ac557..726a235e5 100644 --- a/application/views/helpers/FormTriStateCheckbox.php +++ b/application/views/helpers/FormTriStateCheckbox.php @@ -44,20 +44,19 @@ class Zend_View_Helper_FormTriStateCheckbox extends Zend_View_Helper_FormElement * @param array $attribs Attributes for the element tag * * @return string The element XHTML - */ + */ public function formTriStateCheckbox($name, $value = null, $attribs = null) { $class = ""; - $xhtml = '
' - . '
' . ($value == 1 ? '{{ICON_ENABLED}}' : ($value === 'unchanged' ? '{{ICON_MIXED}}' : '{{ICON_DISABLED}}' )) . '
' - . 'On ' - . 'Off '; - + $xhtml = '
' + . '
' . ($value == 1 ? ' ' : ($value === 'unchanged' ? ' ' : ' ' )) . '
' + . 'On ' + . 'Off '; if ($value === 'unchanged') { $xhtml = $xhtml . ' Mixed '; + . $name . '" ' . 'checked "> Undefined '; }; return $xhtml . '
'; } diff --git a/library/Icinga/Web/Form/Element/TriStateCheckbox.php b/library/Icinga/Web/Form/Element/TriStateCheckbox.php index 0271bf236..cc6d935ee 100644 --- a/library/Icinga/Web/Form/Element/TriStateCheckbox.php +++ b/library/Icinga/Web/Form/Element/TriStateCheckbox.php @@ -48,9 +48,9 @@ class TriStateCheckbox extends Zend_Form_Element_Xhtml */ public $helper = 'formTriStateCheckbox'; - public function __construct($spec, $options = null) + public function __construct($name, $options = null) { - parent::__construct($spec, $options); + parent::__construct($name, $options); $this->triStateValidator = new TriStateValidator($this->patterns); $this->addValidator($this->triStateValidator); diff --git a/library/Icinga/Web/StyleSheet.php b/library/Icinga/Web/StyleSheet.php index 6114643e6..84df2a18f 100644 --- a/library/Icinga/Web/StyleSheet.php +++ b/library/Icinga/Web/StyleSheet.php @@ -19,6 +19,7 @@ class StyleSheet 'css/icinga/widgets.less', 'css/icinga/pagination.less', 'css/icinga/monitoring-colors.less', + 'css/icinga/selection-toolbar.less', 'css/icinga/login.less', ); diff --git a/modules/monitoring/application/controllers/MultiController.php b/modules/monitoring/application/controllers/MultiController.php index 74b2becee..f77b2c88c 100644 --- a/modules/monitoring/application/controllers/MultiController.php +++ b/modules/monitoring/application/controllers/MultiController.php @@ -278,6 +278,7 @@ class Monitoring_MultiController extends ActionController private function handleConfigurationForm(array $flags) { $this->view->form = $form = new MultiCommandFlagForm($flags); + $this->view->formElements = $form->buildCheckboxes(); $form->setRequest($this->_request); if ($form->isSubmittedAndValid()) { // TODO: Handle commands diff --git a/modules/monitoring/application/forms/Command/MultiCommandFlagForm.php b/modules/monitoring/application/forms/Command/MultiCommandFlagForm.php index 593e471f9..f668ca959 100644 --- a/modules/monitoring/application/forms/Command/MultiCommandFlagForm.php +++ b/modules/monitoring/application/forms/Command/MultiCommandFlagForm.php @@ -103,11 +103,6 @@ class MultiCommandFlagForm extends Form { return $changed; } - /** - * Extract the values from a set of items. - * - * @param array $items The items - */ private function valuesFromObjects($items) { $values = array(); @@ -142,6 +137,21 @@ class MultiCommandFlagForm extends Form { return array_merge($values, $old); } + public function buildCheckboxes() + { + $checkboxes = array(); + foreach ($this->flags as $flag => $description) { + $checkboxes[] = new TriStateCheckbox( + $flag, + array( + 'label' => $description, + 'required' => true + ) + ); + } + return $checkboxes; + } + /** * Create the multi flag form * @@ -150,16 +160,9 @@ class MultiCommandFlagForm extends Form { public function create() { $this->setName('form_flag_configuration'); - foreach ($this->flags as $flag => $description) { - $this->addElement(new TriStateCheckbox( - $flag, - array( - 'label' => $description, - 'required' => true - ) - )); - - $old = new Zend_Form_Element_Hidden($flag . self::OLD_VALUE_MARKER); + foreach ($this->buildCheckboxes() as $checkbox) { + $this->addElement($checkbox); + $old = new Zend_Form_Element_Hidden($checkbox->getName() . self::OLD_VALUE_MARKER); $this->addElement($old); } $this->setSubmitLabel('Save Configuration'); diff --git a/modules/monitoring/application/views/helpers/SelectionToolbar.php b/modules/monitoring/application/views/helpers/SelectionToolbar.php index 32f07325b..2230cb093 100644 --- a/modules/monitoring/application/views/helpers/SelectionToolbar.php +++ b/modules/monitoring/application/views/helpers/SelectionToolbar.php @@ -13,11 +13,8 @@ class Zend_View_Helper_SelectionToolbar extends Zend_View_Helper_Abstract public function selectionToolbar($type, $target = null) { if ($type == 'multi') { - return '
Select ' - . ' All ' - . ' None
'; - } else if ($type == 'single') { - return '
Select None
'; + return ''; } else { return ''; } diff --git a/modules/monitoring/application/views/scripts/list/hosts.phtml b/modules/monitoring/application/views/scripts/list/hosts.phtml index be7108fdc..fc4e46add 100644 --- a/modules/monitoring/application/views/scripts/list/hosts.phtml +++ b/modules/monitoring/application/views/scripts/list/hosts.phtml @@ -6,8 +6,9 @@ $helper = $this->getHelper('MonitoringState');
Sort by sortControl->render($this); ?>
- + paginationControl($hosts, null, null, array('preserve' => $this->preserve)); ?> +selectionToolbar('multi', $this->href('monitoring/multi/host',array( 'host' => '*' ))); ?>
diff --git a/modules/monitoring/application/views/scripts/list/services.phtml b/modules/monitoring/application/views/scripts/list/services.phtml index ec8e6fe14..3aa9a6bc9 100644 --- a/modules/monitoring/application/views/scripts/list/services.phtml +++ b/modules/monitoring/application/views/scripts/list/services.phtml @@ -8,6 +8,7 @@ if (!$this->compact): ?> Sort by sortControl ?>
+ paginationControl($services, null, null, array('preserve' => $this->preserve)); ?> diff --git a/modules/monitoring/application/views/scripts/multi/components/comments.phtml b/modules/monitoring/application/views/scripts/multi/components/comments.phtml index 9ac0d768b..deb1e3f54 100644 --- a/modules/monitoring/application/views/scripts/multi/components/comments.phtml +++ b/modules/monitoring/application/views/scripts/multi/components/comments.phtml @@ -1,61 +1,35 @@ -
-
+ + + + diff --git a/modules/monitoring/application/views/scripts/multi/components/configuration.phtml b/modules/monitoring/application/views/scripts/multi/components/configuration.phtml index 7dd4c0844..fe116ad4f 100644 --- a/modules/monitoring/application/views/scripts/multi/components/configuration.phtml +++ b/modules/monitoring/application/views/scripts/multi/components/configuration.phtml @@ -1,10 +1,26 @@ -
-
- Configuration -
-
-
- Change configuration for objects) ?> objects. - form->render($this); ?> -
-
+ + + +
+form->getElements() as $name => $element): + if ($element instanceof \Icinga\Web\Form\Element\TriStateCheckbox): + $element->setDecorators(array('ViewHelper')); + ?> +
+ + + + + + + + + + diff --git a/modules/monitoring/application/views/scripts/multi/components/downtimes.phtml b/modules/monitoring/application/views/scripts/multi/components/downtimes.phtml index ffbf73640..7b1bffcfc 100644 --- a/modules/monitoring/application/views/scripts/multi/components/downtimes.phtml +++ b/modules/monitoring/application/views/scripts/multi/components/downtimes.phtml @@ -1,49 +1,33 @@ -
-
+ + + + diff --git a/modules/monitoring/application/views/scripts/multi/components/objectlist.phtml b/modules/monitoring/application/views/scripts/multi/components/objectlist.phtml index ef8bb0527..3f38e298a 100644 --- a/modules/monitoring/application/views/scripts/multi/components/objectlist.phtml +++ b/modules/monitoring/application/views/scripts/multi/components/objectlist.phtml @@ -1,7 +1,35 @@ - + + + 5) { + $text = ' ' . (count($objects) - 5) . ' more ...'; + } else { + $text = ' show all ... '; + } + if ($this->is_service) { + $link = 'monitoring/list/hosts'; + $target = array( + 'host' => $this->hostquery // implode(',', $hostnames) + ); + } else { + $link = 'monitoring/list/services'; + // TODO: Show multiple targets for services + $target = array( + 'host' => $this->hostquery, + 'service' => $this->servicequery + ); + } +?> \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/multi/components/summary.phtml b/modules/monitoring/application/views/scripts/multi/components/summary.phtml index 354533c12..3fe942e9c 100644 --- a/modules/monitoring/application/views/scripts/multi/components/summary.phtml +++ b/modules/monitoring/application/views/scripts/multi/components/summary.phtml @@ -1,93 +1,68 @@ getHelper('CommandForm'); -$servicequery = isset($this->servicequery) ? $this->servicequery : ''; + /** @var Zend_View_Helper_CommandForm $cf */ + $cf = $this->getHelper('CommandForm'); + $servicequery = isset($this->servicequery) ? $this->servicequery : ''; + $objectName = $this->is_service ? 'Services' : 'Hosts'; ?> -
- - 10) { - $text = ' and ' . (count($objects) - 10) . ' more ...'; - } else { - $text = ' show all ...'; - } - if ($this->is_service) { - $link = 'monitoring/list/hosts'; - $target = array( - 'host' => implode(',', $hostnames) - ); - } else { - $link = 'monitoring/list/services'; - // TODO: Show multiple targets for services - $target = array(); - } - ?> - -
-
-
- - Recheck - - - Reschedule - -
+ + + + + -
- The selected objects have problems ?> service problems.

+
+ + + + - - Acknowledge Problems - - - Schedule Downtimes - - + + + + + \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/multi/service.phtml b/modules/monitoring/application/views/scripts/multi/service.phtml index f594dc836..751fd68b5 100644 --- a/modules/monitoring/application/views/scripts/multi/service.phtml +++ b/modules/monitoring/application/views/scripts/multi/service.phtml @@ -8,30 +8,38 @@ $this->target = array( ); ?> -tabs->render($this); ?> -
- -
- Services ( objects ) -
- - - -
-
-
- -
- render('multi/components/summary.phtml'); ?> -
+tabs; ?> +
+

Summary for services

-render('multi/components/comments.phtml'); ?> -render('multi/components/downtimes.phtml'); ?> -render('multi/components/configuration.phtml'); ?> +
+ render('multi/components/objectlist.phtml'); ?> +

icon('servicegroup.png')?> Service State

+ +
+ Comments + + There are + comments for the selected + + + icon('remove_petrol.png') ?> Remove Comments +
+ + icon('notification_disabled_petrol.png') ?> Delay Notifications +
+ + icon('acknowledgement_petrol.png') ?> Acknowledge - - - icon('notification.png') ?>"> - - - - -
-
- -
- 0) { ?> - - There are comments assigned to the selected items. - - - icon('remove.png') ?>"> - - - There are 0 comments assigned to the selected items. - -
- - Delay Notifications - -
- +
+ getLabel() ?> + + render() ?> +
render() ?>
+ Downtimes + + Selected items are currently in downtime. + + + icon('in_downtime_petrol.png') ?> Schedule Downtimes +
+ + + icon('remove_petrol.png') ?> Remove Downtimes +
+ + + icon('remove_petrol.png') ?> Remove Acknowledgements - - -
-
-
- 0) { ?> - - Selected items are currently in downtime. - - - icon('remove.png') ?> - - - 0 Selected items are currently in downtime. - -
- - - Remove Acknowledgements - -
- +
+ + + You have selected . + + + icon('refresh_petrol.png') ?> Recheck +
+ + icon('reschedule_petrol.png') ?> Reschedule + +
+ Problems + + The have problems ?> problems. + + + icon('in_downtime_petrol.png') ?> Schedule Downtimes + +
+ Unhandled + + unhandled) ?> are unhandled. + + + icon('acknowledgement_petrol.png') ?> Acknowledge + +
+ + render('multi/components/summary.phtml'); ?> + render('multi/components/comments.phtml'); ?> + render('multi/components/downtimes.phtml'); ?> + +
+ +

icon('configuration.png')?> Options

+ + + render('multi/components/configuration.phtml') ?> + +
+ + + + + + diff --git a/public/css/icinga/selection-toolbar.less b/public/css/icinga/selection-toolbar.less index e69de29bb..91d83fbc4 100644 --- a/public/css/icinga/selection-toolbar.less +++ b/public/css/icinga/selection-toolbar.less @@ -0,0 +1,8 @@ +div.selection-toolbar { + float: right; + padding-right: 20px; +} + +div.selection-toolbar a { + color: #049baf +} \ No newline at end of file diff --git a/public/js/icinga/events.js b/public/js/icinga/events.js index 570ed08fc..33fc935ad 100644 --- a/public/js/icinga/events.js +++ b/public/js/icinga/events.js @@ -26,7 +26,7 @@ this.applyGlobalDefaults(); this.applyHandlers($('#layout')); this.icinga.ui.prepareContainers(); - this.icinga.ui.prepareMultiselectTables(); + this.icinga.ui.prepareMultiselectTables($(document)); }, // TODO: What's this? @@ -306,10 +306,9 @@ var icinga = self.icinga; var $tr = $(this); var $table = $tr.closest('table.multiselect'); - var data = $table.data('icinga-multiselect-data').split(','); + var data = $table.data('icinga-multiselect-data') && $table.data('icinga-multiselect-data').split(','); var multisel = $table.hasClass('multiselect'); var url = $table.data('icinga-multiselect-url'); - var $trs, $target; // When the selection points to a link, select the closest row if ($tr.prop('tagName').toLowerCase() === 'a') { @@ -323,11 +322,11 @@ // link handled externally return false; } - if (!data) { + if (multisel && !data) { icinga.logger.error('A table with multiselection must define the attribute "data-icinga-multiselect-data"'); return; } - if (!url) { + if (multisel && !url) { icinga.logger.error('A table with multiselection must define the attribute "data-icinga-multiselect-url"'); return; } @@ -347,18 +346,22 @@ icinga.ui.focusTable($table[0]); // Update url - $trs = $table.find('tr[href].active'); - $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); + 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); + 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('#'); + } } else { - // display nothing - icinga.loader.loadUrl('#'); + icinga.loader.loadUrl($tr.attr('href'), $target); } return false; }, diff --git a/public/js/icinga/loader.js b/public/js/icinga/loader.js index 04035e1e6..d6b3103fd 100644 --- a/public/js/icinga/loader.js +++ b/public/js/icinga/loader.js @@ -393,13 +393,17 @@ * Replace SVG piecharts with jQuery-Sparkline */ $('.inlinepie', $resp).each(function(){ - var title = $(this).attr('title'), - style = $(this).attr('style'), - values = $(this).data('icinga-values'); - var html = '
' + values + '
'; + var title = $(this).attr('title'), + style = $(this).attr('style'), + values = $(this).data('icinga-values'), + html = '
' + values + '
'; $(this).replaceWith(html); }); + /** + * Make multiselection-tables not selectable. + */ + this.icinga.ui.prepareMultiselectTables($resp); /* Should we try to fiddle with responses containing full HTML? */ /* diff --git a/public/js/icinga/ui.js b/public/js/icinga/ui.js index 134c2f75b..d80affcd8 100644 --- a/public/js/icinga/ui.js +++ b/public/js/icinga/ui.js @@ -198,12 +198,10 @@ return true; } } - this.icinga.logger.error( 'Someone messed up our responsiveness hacks, html font-family is', layout ); - return false; }, From b16c6efa05f8867343228fc00e5b505c268b0e68 Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Wed, 16 Apr 2014 19:43:56 +0200 Subject: [PATCH 5/8] Add PieChart to display hosts and services, improve layout and fix flag checkboxes refs #5765 --- .../views/helpers/FormTriStateCheckbox.php | 5 +- library/Icinga/Web/Widget/Chart/InlinePie.php | 6 +- .../controllers/MultiController.php | 67 ++++++++++++++++--- .../forms/Command/MultiCommandFlagForm.php | 2 + .../scripts/multi/components/comments.phtml | 18 +++-- .../multi/components/configuration.phtml | 26 ------- .../scripts/multi/components/downtimes.phtml | 28 +++----- .../scripts/multi/components/flags.phtml | 31 +++++++++ .../scripts/multi/components/objectlist.phtml | 9 +-- .../scripts/multi/components/summary.phtml | 38 ++++++----- .../views/scripts/multi/service.phtml | 62 +++++++++++++---- public/js/icinga/events.js | 61 +++++++++++++++-- public/js/icinga/loader.js | 16 ++--- public/js/icinga/ui.js | 63 +++++++++++++++++ 14 files changed, 314 insertions(+), 118 deletions(-) delete mode 100644 modules/monitoring/application/views/scripts/multi/components/configuration.phtml create mode 100644 modules/monitoring/application/views/scripts/multi/components/flags.phtml diff --git a/application/views/helpers/FormTriStateCheckbox.php b/application/views/helpers/FormTriStateCheckbox.php index 726a235e5..c41664437 100644 --- a/application/views/helpers/FormTriStateCheckbox.php +++ b/application/views/helpers/FormTriStateCheckbox.php @@ -48,12 +48,15 @@ class Zend_View_Helper_FormTriStateCheckbox extends Zend_View_Helper_FormElement public function formTriStateCheckbox($name, $value = null, $attribs = null) { $class = ""; - $xhtml = '
' + $xhtml = '
' . '
' . ($value == 1 ? ' ' : ($value === 'unchanged' ? ' ' : ' ' )) . '
' + . 'On ' + . 'Off '; + if ($value === 'unchanged') { $xhtml = $xhtml . ' Undefined '; diff --git a/library/Icinga/Web/Widget/Chart/InlinePie.php b/library/Icinga/Web/Widget/Chart/InlinePie.php index 6c9d1196e..d38163aa6 100644 --- a/library/Icinga/Web/Widget/Chart/InlinePie.php +++ b/library/Icinga/Web/Widget/Chart/InlinePie.php @@ -49,11 +49,12 @@ class InlinePie extends AbstractWidget * @var string */ private $template =<<<'EOD' +
+
EOD; /** @@ -118,10 +119,13 @@ EOD; * The labels to be displayed in the pie-chart * * @param null $labels + * + * @return $this */ public function setLabels($labels = null) { $this->url->setParam('labels', implode(',', $labels)); + return $this; } /** diff --git a/modules/monitoring/application/controllers/MultiController.php b/modules/monitoring/application/controllers/MultiController.php index f77b2c88c..5c2da9f1c 100644 --- a/modules/monitoring/application/controllers/MultiController.php +++ b/modules/monitoring/application/controllers/MultiController.php @@ -32,6 +32,7 @@ use \Icinga\Web\Controller\ActionController; use \Icinga\Web\Widget\Tabextension\OutputFormat; use \Icinga\Module\Monitoring\Backend; use \Icinga\Data\BaseQuery; +use \Icinga\Web\Widget\Chart\InlinePie; use \Icinga\Module\Monitoring\Form\Command\MultiCommandFlagForm; use \Icinga\Module\Monitoring\DataView\HostStatus as HostStatusView; use \Icinga\Module\Monitoring\DataView\ServiceStatus as ServiceStatusView; @@ -89,6 +90,8 @@ class Monitoring_MultiController extends ActionController $this->view->hostnames = $this->getProperties($hosts, 'host_name'); $this->view->downtimes = $this->getDowntimes($hosts); $this->view->errors = $errors; + $this->view->states = $this->countStates($hosts); + $this->view->pie = $this->createPie($this->view->states); // Handle configuration changes $this->handleConfigurationForm(array( @@ -99,7 +102,6 @@ class Monitoring_MultiController extends ActionController 'host_event_handler_enabled' => 'Event Handler', 'host_flap_detection_enabled' => 'Flap Detection' )); - $this->view->form->setAction('/icinga2-web/monitoring/multi/host'); } @@ -112,11 +114,11 @@ class Monitoring_MultiController extends ActionController $this->_request, array( 'host_name', + 'host_state', 'service_description', 'service_handled', 'service_state', 'service_in_downtime', - 'service_passive_checks_enabled', 'service_notifications_enabled', 'service_event_handler_enabled', @@ -139,13 +141,17 @@ class Monitoring_MultiController extends ActionController ); $comments = array_keys($this->getUniqueValues($commentQuery->fetchAll(), 'comment_internal_id')); - $this->view->objects = $this->view->services = $services; - $this->view->problems = $this->getProblems($services); - $this->view->comments = isset($comments) ? $comments : $this->getComments($services); - $this->view->hostnames = $this->getProperties($services, 'host_name'); - $this->view->servicenames = $this->getProperties($services, 'service_description'); - $this->view->downtimes = $this->getDowntimes($services); - $this->view->errors = $errors; + $this->view->objects = $this->view->services = $services; + $this->view->problems = $this->getProblems($services); + $this->view->comments = isset($comments) ? $comments : $this->getComments($services); + $this->view->hostnames = $this->getProperties($services, 'host_name'); + $this->view->servicenames = $this->getProperties($services, 'service_description'); + $this->view->downtimes = $this->getDowntimes($services); + $this->view->service_states = $this->countStates($services); + $this->view->host_states = $this->countStates($services, 'host_name', 'host_state'); + $this->view->service_pie = $this->createPie($this->view->service_states); + $this->view->host_pie = $this->createPie($this->view->host_states); + $this->view->errors = $errors; $this->handleConfigurationForm(array( 'service_passive_checks_enabled' => 'Passive Checks', @@ -154,9 +160,9 @@ class Monitoring_MultiController extends ActionController 'service_event_handler_enabled' => 'Event Handler', 'service_flap_detection_enabled' => 'Flap Detection' )); - $this->view->form->setAction('/icinga2-web/monitoring/multi/service'); } + /** * Apply the query-filter received * @@ -236,6 +242,47 @@ class Monitoring_MultiController extends ActionController return $problems; } + private function countStates($objects, $unique = null, $state = 'service_state') + { + $known = array(); + $states = array( + '0' => 0, + '1' => 0, + '2' => 0, + '3' => 0, + '99' => 0 + ); + foreach ($objects as $object) { + if (isset($unique)) { + if (array_key_exists($object->$unique, $known)) { + continue; + } + $known[$object->$unique] = true; + } + $states[$object->$state]++; + } + return array( + 'up' => $states['0'], + 'down' => $states['1'], + 'unreachable' => $states['2'], + 'unknown' => $states['3'], + 'pending' => $states['99'] + ); + } + + private function createPie($states) + { + $chart = new InlinePie( + array_values($states), + array('#44bb77', '#FF5566', '#FF5566', '#E066FF', '#77AAFF') + ); + $chart->setLabels(array_keys($states)) + ->setHeight(100) + ->setWidth(100); + return $chart; + } + + private function getComments($objects) { $unique = array(); diff --git a/modules/monitoring/application/forms/Command/MultiCommandFlagForm.php b/modules/monitoring/application/forms/Command/MultiCommandFlagForm.php index f668ca959..3b30574ff 100644 --- a/modules/monitoring/application/forms/Command/MultiCommandFlagForm.php +++ b/modules/monitoring/application/forms/Command/MultiCommandFlagForm.php @@ -32,6 +32,7 @@ namespace Icinga\Module\Monitoring\Form\Command; use \Icinga\Web\Form\Element\TriStateCheckbox; use \Icinga\Web\Form; use \Zend_Form_Element_Hidden; +use \Zend_Form; /** * A form to edit multiple command flags of multiple commands at once. When some commands have @@ -71,6 +72,7 @@ class MultiCommandFlagForm extends Form { { $this->flags = $flags; parent::__construct(); + $this->setEnctype(Zend_Form::ENCTYPE_MULTIPART); } /** diff --git a/modules/monitoring/application/views/scripts/multi/components/comments.phtml b/modules/monitoring/application/views/scripts/multi/components/comments.phtml index deb1e3f54..e2b9b7e96 100644 --- a/modules/monitoring/application/views/scripts/multi/components/comments.phtml +++ b/modules/monitoring/application/views/scripts/multi/components/comments.phtml @@ -4,20 +4,12 @@ - Comments + Comments - - There are - comments for the selected - + > icon('remove_petrol.png') ?> Remove Comments
icon('acknowledgement_petrol.png') ?> Acknowledge + Change comments. + diff --git a/modules/monitoring/application/views/scripts/multi/components/configuration.phtml b/modules/monitoring/application/views/scripts/multi/components/configuration.phtml deleted file mode 100644 index fe116ad4f..000000000 --- a/modules/monitoring/application/views/scripts/multi/components/configuration.phtml +++ /dev/null @@ -1,26 +0,0 @@ - - - - -form->getElements() as $name => $element): - if ($element instanceof \Icinga\Web\Form\Element\TriStateCheckbox): - $element->setDecorators(array('ViewHelper')); - ?> - - - getLabel() ?> - - - render() ?> - - - - - - render() ?> - - - diff --git a/modules/monitoring/application/views/scripts/multi/components/downtimes.phtml b/modules/monitoring/application/views/scripts/multi/components/downtimes.phtml index 7b1bffcfc..1641bda14 100644 --- a/modules/monitoring/application/views/scripts/multi/components/downtimes.phtml +++ b/modules/monitoring/application/views/scripts/multi/components/downtimes.phtml @@ -4,30 +4,22 @@ - Downtimes + Downtimes - Selected items are currently in downtime. - - + + icon('remove_petrol.png') ?> Remove Downtimes +
+ > icon('in_downtime_petrol.png') ?> Schedule Downtimes -
- - - icon('remove_petrol.png') ?> Remove Downtimes -
- - - icon('remove_petrol.png') ?> Remove Acknowledgements + + Change + downtimes. + diff --git a/modules/monitoring/application/views/scripts/multi/components/flags.phtml b/modules/monitoring/application/views/scripts/multi/components/flags.phtml new file mode 100644 index 000000000..c7fba5d97 --- /dev/null +++ b/modules/monitoring/application/views/scripts/multi/components/flags.phtml @@ -0,0 +1,31 @@ + + + + form->getElements() as $name => $element): + if ($element instanceof \Icinga\Web\Form\Element\TriStateCheckbox): + $element->setDecorators(array('ViewHelper')); + ?> + + + + + + + + + +
+ + + render() ?> +
render() ?>
+ \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/multi/components/objectlist.phtml b/modules/monitoring/application/views/scripts/multi/components/objectlist.phtml index 3f38e298a..a8621954f 100644 --- a/modules/monitoring/application/views/scripts/multi/components/objectlist.phtml +++ b/modules/monitoring/application/views/scripts/multi/components/objectlist.phtml @@ -6,23 +6,24 @@ 'monitoring/show/service', array('host' => $object->host_name, 'service' => $object->service_description) ); + echo '">' . $object->host_name . ' ' . $object->service_description . ''; } else { echo $this->href( 'monitoring/show/host', array('host' => $object->host_name) ); - }?>" > - + echo '">' . $object->host_name . ''; + }?>, 5) { $text = ' ' . (count($objects) - 5) . ' more ...'; } else { $text = ' show all ... '; } - if ($this->is_service) { + if (!$this->is_service) { $link = 'monitoring/list/hosts'; $target = array( - 'host' => $this->hostquery // implode(',', $hostnames) + 'host' => $this->hostquery ); } else { $link = 'monitoring/list/services'; diff --git a/modules/monitoring/application/views/scripts/multi/components/summary.phtml b/modules/monitoring/application/views/scripts/multi/components/summary.phtml index 3fe942e9c..e3ba1cb2a 100644 --- a/modules/monitoring/application/views/scripts/multi/components/summary.phtml +++ b/modules/monitoring/application/views/scripts/multi/components/summary.phtml @@ -7,11 +7,8 @@ - + - - You have selected . - href('monitoring/command/reschedulenextcheck', $this->target) ?>"> icon('reschedule_petrol.png') ?> Reschedule - +
+ + + + Perform actions on . - + - Problems + problems ?> Problems - - The have problems ?> problems. - icon('in_downtime_petrol.png') ?> Schedule Downtimes + + Handle problems ?> problems on objects) ?> . + - - - Unhandled + unhandled) ?> Unhandled - - unhandled) ?> are unhandled. - + > icon('acknowledgement_petrol.png') ?> Acknowledge +
+ + icon('remove_petrol.png') ?> Remove Acknowledgements + + \ No newline at end of file diff --git a/modules/monitoring/application/views/scripts/multi/service.phtml b/modules/monitoring/application/views/scripts/multi/service.phtml index 751fd68b5..c0e6e32e2 100644 --- a/modules/monitoring/application/views/scripts/multi/service.phtml +++ b/modules/monitoring/application/views/scripts/multi/service.phtml @@ -8,33 +8,67 @@ $this->target = array( ); ?> -tabs; ?> -
-

Summary for services

+ tabs; ?>
+

Summary for services

+ render('multi/components/objectlist.phtml'); ?> -

icon('servicegroup.png')?> Service State

+ + + + + + + + + + + + + + +
+

Services

+
+

Hosts

+
+ service_pie->render(); ?> + + $count) { + if ($count > 0) { + echo ucfirst($state) . ': ' . $count . '
'; + } + } + ?> +
+ host_pie->render(); ?> + + $count) { + if ($count > 0) { + echo ucfirst($state) . ': ' . $count . '
'; + } + } + ?> +
+ +

icon('servicegroup.png')?> Service Actions

- render('multi/components/summary.phtml'); ?> - render('multi/components/comments.phtml'); ?> - render('multi/components/downtimes.phtml'); ?> + render('multi/components/summary.phtml'); ?> + render('multi/components/comments.phtml'); ?> + render('multi/components/downtimes.phtml'); ?>
-

icon('configuration.png')?> Options

- - - render('multi/components/configuration.phtml') ?> - -
+ render('multi/components/flags.phtml') ?>
- 0 + // 0 => unchanged + // unchanged => 1 + value = value === '1' ? '0' : (value === '0' ? 'unchanged' : '1'); + } else { + // 1 => 0 + // 0 => 1 + value = value === '1' ? '0' : '1'; + } + $tristate.data('icinga-value', value); + + // also set form value + $tristate.prop('value', value); + + var $changed = $tristate.parent().find('.tristate-status').first(); + if (old != value) { + $changed.text('changed'); + } else { + $changed.empty(); + } + $tristate.find('.tristate-status').text(value); + self.icinga.ui.updateTriState(value.toString(), $tristate); + }, + /** * */ diff --git a/public/js/icinga/loader.js b/public/js/icinga/loader.js index d6b3103fd..d083216bd 100644 --- a/public/js/icinga/loader.js +++ b/public/js/icinga/loader.js @@ -247,6 +247,8 @@ if (! req.autorefresh) { // 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) { @@ -273,6 +275,7 @@ }); } else { // TODO: next container url + // Get first container url? active = $('[href].active', req.$target).attr('href'); } @@ -387,18 +390,7 @@ this.icinga.history.pushCurrentState(); } } - - - /* - * Replace SVG piecharts with jQuery-Sparkline - */ - $('.inlinepie', $resp).each(function(){ - var title = $(this).attr('title'), - style = $(this).attr('style'), - values = $(this).data('icinga-values'), - html = '
' + values + '
'; - $(this).replaceWith(html); - }); + this.icinga.ui.initializeTriStates($resp); /** * Make multiselection-tables not selectable. diff --git a/public/js/icinga/ui.js b/public/js/icinga/ui.js index d80affcd8..9bbf1620f 100644 --- a/public/js/icinga/ui.js +++ b/public/js/icinga/ui.js @@ -463,6 +463,69 @@ return $calc.width() / 1000; }, + /** + * Initialize all TriStateCheckboxes in the given html + */ + initializeTriStates: function ($html) { + var self = this; + $('div.tristate', $html).each(function(index, item) { + var target = item; + var $target = $(target); + var value = $target.find('input:checked').first().val(); + var triState = value === 'unchanged' ? true : false; + var name = $('input', target).first().attr('name'); + var old = value; + + var getStateDescription = function(value) { + if (value === 'unchanged') { + return '(mixed values)'; + } + return ''; + }; + + $target.empty(); + $target.parent().parent() + .find('label') + .append('  '); + $target.append( + '' + + '
' + getStateDescription(value) + '
' + ); + }); + }, + + /** + * Set the value of the given TriStateCheckbox + * + * @param value {String} The value to set, can be '1', '0' and 'unchanged' + * @param $checkbox {jQuery} The checkbox + */ + updateTriState: function(value, $checkbox) + { + console.log($checkbox); + switch (value) { + case ('1'): + console.log('checked true; indeterminate: false'); + $checkbox.prop('checked', true).prop('indeterminate', false); + break; + case ('0'): + console.log('checked false; indeterminate: false'); + $checkbox.prop('checked', false).prop('indeterminate', false); + break; + case ('unchanged'): + console.log('checked false; indeterminate: true'); + $checkbox.prop('checked', false).prop('indeterminate', true); + break; + } + }, + initializeControls: function (parent) { var self = this; From b791883fa86244f2e57853ba8722107e38b1ab40 Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Wed, 23 Apr 2014 11:35:16 +0200 Subject: [PATCH 6/8] Fix configuration checkboxes and colors in piecharts refs #5765 --- .../controllers/MultiController.php | 43 +++++----- .../views/helpers/MonitoringState.php | 21 ++++- .../scripts/multi/components/summary.phtml | 2 +- .../views/scripts/multi/host.phtml | 78 ++++++++++++------- public/js/icinga/events.js | 30 +++---- public/js/icinga/ui.js | 57 ++++++-------- 6 files changed, 131 insertions(+), 100 deletions(-) diff --git a/modules/monitoring/application/controllers/MultiController.php b/modules/monitoring/application/controllers/MultiController.php index 5c2da9f1c..de4fefd0a 100644 --- a/modules/monitoring/application/controllers/MultiController.php +++ b/modules/monitoring/application/controllers/MultiController.php @@ -64,6 +64,7 @@ class Monitoring_MultiController extends ActionController 'host_unhandled_service_count', 'host_passive_checks_enabled', 'host_obsessing', + 'host_state', 'host_notifications_enabled', 'host_event_handler_enabled', 'host_flap_detection_enabled', @@ -90,8 +91,8 @@ class Monitoring_MultiController extends ActionController $this->view->hostnames = $this->getProperties($hosts, 'host_name'); $this->view->downtimes = $this->getDowntimes($hosts); $this->view->errors = $errors; - $this->view->states = $this->countStates($hosts); - $this->view->pie = $this->createPie($this->view->states); + $this->view->states = $this->countStates($hosts, 'host', 'host_name'); + $this->view->pie = $this->createPie($this->view->states, $this->view->getHelper('MonitoringState')->getHostStateColors()); // Handle configuration changes $this->handleConfigurationForm(array( @@ -147,10 +148,10 @@ class Monitoring_MultiController extends ActionController $this->view->hostnames = $this->getProperties($services, 'host_name'); $this->view->servicenames = $this->getProperties($services, 'service_description'); $this->view->downtimes = $this->getDowntimes($services); - $this->view->service_states = $this->countStates($services); - $this->view->host_states = $this->countStates($services, 'host_name', 'host_state'); - $this->view->service_pie = $this->createPie($this->view->service_states); - $this->view->host_pie = $this->createPie($this->view->host_states); + $this->view->service_states = $this->countStates($services, 'service'); + $this->view->host_states = $this->countStates($services, 'host', 'host_name'); + $this->view->service_pie = $this->createPie($this->view->service_states, $this->view->getHelper('MonitoringState')->getServiceStateColors()); + $this->view->host_pie = $this->createPie($this->view->host_states, $this->view->getHelper('MonitoringState')->getHostStateColors()); $this->view->errors = $errors; $this->handleConfigurationForm(array( @@ -242,16 +243,14 @@ class Monitoring_MultiController extends ActionController return $problems; } - private function countStates($objects, $unique = null, $state = 'service_state') + private function countStates($objects, $type = 'host', $unique = null) { - $known = array(); - $states = array( - '0' => 0, - '1' => 0, - '2' => 0, - '3' => 0, - '99' => 0 - ); + $known = array(); + if ($type === 'host') { + $states = array_fill_keys($this->view->getHelper('MonitoringState')->getHostStateNames(), 0); + } else { + $states = array_fill_keys($this->view->getHelper('MonitoringState')->getServiceStateNames(), 0); + } foreach ($objects as $object) { if (isset($unique)) { if (array_key_exists($object->$unique, $known)) { @@ -259,22 +258,16 @@ class Monitoring_MultiController extends ActionController } $known[$object->$unique] = true; } - $states[$object->$state]++; + $states[$this->view->monitoringState($object, $type)]++; } - return array( - 'up' => $states['0'], - 'down' => $states['1'], - 'unreachable' => $states['2'], - 'unknown' => $states['3'], - 'pending' => $states['99'] - ); + return $states; } - private function createPie($states) + private function createPie($states, $colors) { $chart = new InlinePie( array_values($states), - array('#44bb77', '#FF5566', '#FF5566', '#E066FF', '#77AAFF') + $colors ); $chart->setLabels(array_keys($states)) ->setHeight(100) diff --git a/modules/monitoring/application/views/helpers/MonitoringState.php b/modules/monitoring/application/views/helpers/MonitoringState.php index dff80a205..2051975b7 100644 --- a/modules/monitoring/application/views/helpers/MonitoringState.php +++ b/modules/monitoring/application/views/helpers/MonitoringState.php @@ -7,7 +7,6 @@ class Zend_View_Helper_MonitoringState extends Zend_View_Helper_Abstract public function monitoringState($object, $type = 'service') { - if ($type === 'service') { return $this->servicestates[$object->service_state]; } elseif ($type === 'host') { @@ -15,6 +14,26 @@ class Zend_View_Helper_MonitoringState extends Zend_View_Helper_Abstract } } + public function getServiceStateColors() + { + return array('#44bb77', '#FFCC66', '#FF5566', '#E066FF', '#77AAFF'); + } + + public function getHostStateColors() + { + return array('#44bb77', '#FF5566', '#E066FF', '#77AAFF'); + } + + public function getServiceStateNames() + { + return array_values($this->servicestates); + } + + public function getHostStateNames() + { + return array_values($this->hoststates); + } + public function getStateFlags($object, $type = 'service') { $state_classes = array(); diff --git a/modules/monitoring/application/views/scripts/multi/components/summary.phtml b/modules/monitoring/application/views/scripts/multi/components/summary.phtml index e3ba1cb2a..fb761cce2 100644 --- a/modules/monitoring/application/views/scripts/multi/components/summary.phtml +++ b/modules/monitoring/application/views/scripts/multi/components/summary.phtml @@ -15,7 +15,7 @@ 'monitoring/command/reschedulenextcheck', array( 'host' => $this->target['host'], - 'service' => $this->target['service'], + 'service' => array_key_exists('service', $this->target) ? $this->target['service'] : null, 'checktime' => time(), 'forcecheck' => '1' ) diff --git a/modules/monitoring/application/views/scripts/multi/host.phtml b/modules/monitoring/application/views/scripts/multi/host.phtml index 165ece8ef..e473ba05a 100644 --- a/modules/monitoring/application/views/scripts/multi/host.phtml +++ b/modules/monitoring/application/views/scripts/multi/host.phtml @@ -1,33 +1,59 @@ is_service = false; $this->hostquery = implode($this->hostnames, ','); -$this->target = array( - 'host' => $this->hostquery -); +$this->target = array('host' => $this->hostquery); ?> -tabs->render($this); ?> -
- -
- -
- render('multi/components/summary.phtml'); ?> -
+
+ tabs; ?>
-render('multi/components/comments.phtml'); ?> -render('multi/components/downtimes.phtml'); ?> -render('multi/components/configuration.phtml'); ?> +
+

Summary for hosts

+ + render('multi/components/objectlist.phtml'); ?> + + + + + + + + + + +
+

Hosts

+
+ pie->render(); ?> + + $count) { + if ($count > 0) { + echo ucfirst($state) . ': ' . $count . '
'; + } + } + ?> +
+ +

icon('hostgroup.png')?> Host Actions

+ + + + render('multi/components/summary.phtml'); ?> + render('multi/components/comments.phtml'); ?> + render('multi/components/downtimes.phtml'); ?> + +
+ + render('multi/components/flags.phtml') ?> +
+ + + + diff --git a/public/js/icinga/events.js b/public/js/icinga/events.js index df0fc86be..fcce23e4a 100644 --- a/public/js/icinga/events.js +++ b/public/js/icinga/events.js @@ -135,7 +135,7 @@ $(document).on('click', '.tree .handle', { self: this }, this.treeNodeToggle); // Toggle all triStateButtons - $(document).on('click', '.tristate input.tristate', { self: this}, this.clickTriState); + $(document).on('click', 'div.tristate .tristate-dummy', { self: this }, this.clickTriState); // TBD: a global autocompletion handler // $(document).on('keyup', 'form.auto input', this.formChangeDelayed); @@ -253,12 +253,13 @@ clickTriState: function (event) { var $tristate = $(this); - var old = $tristate.data('icinga-old'); var triState = parseInt($tristate.data('icinga-tristate'), 10); - var value = $tristate.data('icinga-value'); - var self = event.data.self; - // update value + // load current values + var old = $tristate.data('icinga-old'); + var value = $tristate.parent().find('input:radio:checked').first().prop('checked', false).val(); + + // calculate the new value if (triState) { // 1 => 0 // 0 => unchanged @@ -269,19 +270,17 @@ // 0 => 1 value = value === '1' ? '0' : '1'; } - $tristate.data('icinga-value', value); + // update form value + $tristate.parent().find('input:radio[value="' + value + '"]').prop('checked', true); + // update dummy - // also set form value - $tristate.prop('value', value); - - var $changed = $tristate.parent().find('.tristate-status').first(); - if (old != value) { - $changed.text('changed'); + console.log(value + ' === ' + old + ' ?'); + if (value !== old) { + $tristate.parent().find('b.tristate-changed').css('visibility', 'visible'); } else { - $changed.empty(); + $tristate.parent().find('b.tristate-changed').hide(); } - $tristate.find('.tristate-status').text(value); - self.icinga.ui.updateTriState(value.toString(), $tristate); + self.icinga.ui.setTriState(value.toString(), $tristate); }, /** @@ -600,6 +599,7 @@ $(document).off('mouseleave', '.historycolorgrid td', this.historycolorgidUnhover); $(document).off('mouseenter', 'li.dropdown', this.dropdownHover); $(document).off('mouseleave', 'li.dropdown', this.dropdownLeave); + $(document).off('click', 'div.tristate .tristate-dummy', this.clickTriState); }, destroy: function() { diff --git a/public/js/icinga/ui.js b/public/js/icinga/ui.js index 9bbf1620f..6d8366b53 100644 --- a/public/js/icinga/ui.js +++ b/public/js/icinga/ui.js @@ -469,35 +469,32 @@ initializeTriStates: function ($html) { var self = this; $('div.tristate', $html).each(function(index, item) { - var target = item; - var $target = $(target); - var value = $target.find('input:checked').first().val(); - var triState = value === 'unchanged' ? true : false; - var name = $('input', target).first().attr('name'); - var old = value; + var $target = $(item); - var getStateDescription = function(value) { - if (value === 'unchanged') { - return '(mixed values)'; - } - return ''; - }; - - $target.empty(); - $target.parent().parent() - .find('label') - .append('  '); + // hide input boxess and remove text nodes + $target.find("input").hide(); + $target.contents().filter(function() { return this.nodeType == 3; }).remove(); + + // has three states? + var triState = $target.find('input[value="unchanged"]').size() > 0 ? 1 : 0; + + // fetch current value from radiobuttons + var value = $target.find('input:checked').first().val(); + $target.append( - '' + - '
' + getStateDescription(value) + '
' - ); + ' ' + ); + if (triState) { + // TODO: find a better way to activate indeterminate checkboxes after load. + $target.append( + '' + ); + } }); }, @@ -507,20 +504,16 @@ * @param value {String} The value to set, can be '1', '0' and 'unchanged' * @param $checkbox {jQuery} The checkbox */ - updateTriState: function(value, $checkbox) + setTriState: function(value, $checkbox) { - console.log($checkbox); switch (value) { case ('1'): - console.log('checked true; indeterminate: false'); $checkbox.prop('checked', true).prop('indeterminate', false); break; case ('0'): - console.log('checked false; indeterminate: false'); $checkbox.prop('checked', false).prop('indeterminate', false); break; case ('unchanged'): - console.log('checked false; indeterminate: true'); $checkbox.prop('checked', false).prop('indeterminate', true); break; } From c641988233261772548011b83b0672db1490895c Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Wed, 23 Apr 2014 19:41:07 +0200 Subject: [PATCH 7/8] Preserve multiselection during refresh refs #5765 --- .../controllers/ListController.php | 2 + .../controllers/MultiController.php | 65 +++++---- .../views/helpers/SelectionToolbar.php | 2 +- .../views/scripts/list/hosts.phtml | 2 +- .../views/scripts/list/notifications.phtml | 3 +- .../views/scripts/list/services.phtml | 5 +- .../scripts/multi/components/comments.phtml | 6 +- .../scripts/multi/components/downtimes.phtml | 6 +- .../scripts/multi/components/objectlist.phtml | 10 +- public/js/icinga/events.js | 66 +++------ public/js/icinga/loader.js | 36 ++++- public/js/icinga/ui.js | 133 +++++++++++++++++- 12 files changed, 238 insertions(+), 98 deletions(-) diff --git a/modules/monitoring/application/controllers/ListController.php b/modules/monitoring/application/controllers/ListController.php index 89b84873f..5e26d3654 100644 --- a/modules/monitoring/application/controllers/ListController.php +++ b/modules/monitoring/application/controllers/ListController.php @@ -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); diff --git a/modules/monitoring/application/controllers/MultiController.php b/modules/monitoring/application/controllers/MultiController.php index de4fefd0a..00dcb7ed2 100644 --- a/modules/monitoring/application/controllers/MultiController.php +++ b/modules/monitoring/application/controllers/MultiController.php @@ -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; } diff --git a/modules/monitoring/application/views/helpers/SelectionToolbar.php b/modules/monitoring/application/views/helpers/SelectionToolbar.php index 2230cb093..dccb9fa27 100644 --- a/modules/monitoring/application/views/helpers/SelectionToolbar.php +++ b/modules/monitoring/application/views/helpers/SelectionToolbar.php @@ -14,7 +14,7 @@ class Zend_View_Helper_SelectionToolbar extends Zend_View_Helper_Abstract { if ($type == 'multi') { return ''; + . ' Show All
'; } else { return ''; } diff --git a/modules/monitoring/application/views/scripts/list/hosts.phtml b/modules/monitoring/application/views/scripts/list/hosts.phtml index fc4e46add..29a5597a6 100644 --- a/modules/monitoring/application/views/scripts/list/hosts.phtml +++ b/modules/monitoring/application/views/scripts/list/hosts.phtml @@ -8,7 +8,7 @@ $helper = $this->getHelper('MonitoringState');
paginationControl($hosts, null, null, array('preserve' => $this->preserve)); ?> -selectionToolbar('multi', $this->href('monitoring/multi/host',array( 'host' => '*' ))); ?> +selectionToolbar('multi', $this->href('monitoring/multi/host', $query)); ?>
diff --git a/modules/monitoring/application/views/scripts/list/notifications.phtml b/modules/monitoring/application/views/scripts/list/notifications.phtml index 03dad8d97..97b56a650 100644 --- a/modules/monitoring/application/views/scripts/list/notifications.phtml +++ b/modules/monitoring/application/views/scripts/list/notifications.phtml @@ -2,7 +2,8 @@ tabs ?>
Sort by sortControl->render($this) ?> - + +selectionToolbar('single') ?>
paginationControl($notifications, null, null, array('preserve' => $this->preserve)) ?>
diff --git a/modules/monitoring/application/views/scripts/list/services.phtml b/modules/monitoring/application/views/scripts/list/services.phtml index 3aa9a6bc9..111d3336a 100644 --- a/modules/monitoring/application/views/scripts/list/services.phtml +++ b/modules/monitoring/application/views/scripts/list/services.phtml @@ -9,7 +9,10 @@ if (!$this->compact): ?> Sort by sortControl ?> -paginationControl($services, null, null, array('preserve' => $this->preserve)); ?> +paginationControl($services, null, null, array('preserve' => $this->preserve));?> + + +selectionToolbar('multi', $this->href('monitoring/multi/service', $query)); ?>
diff --git a/modules/monitoring/application/views/scripts/multi/components/comments.phtml b/modules/monitoring/application/views/scripts/multi/components/comments.phtml index e2b9b7e96..390fdb8cb 100644 --- a/modules/monitoring/application/views/scripts/multi/components/comments.phtml +++ b/modules/monitoring/application/views/scripts/multi/components/comments.phtml @@ -24,10 +24,10 @@ icon('acknowledgement_petrol.png') ?> Acknowledge - Change href( 'monitoring/list/comments', - array('comment_id' => implode(',', $this->comments)) + array('comment_internal_id' => implode(',', $this->comments)) ); - ?>"> comments. + ?>"> comments . diff --git a/modules/monitoring/application/views/scripts/multi/components/downtimes.phtml b/modules/monitoring/application/views/scripts/multi/components/downtimes.phtml index 1641bda14..66ea016ec 100644 --- a/modules/monitoring/application/views/scripts/multi/components/downtimes.phtml +++ b/modules/monitoring/application/views/scripts/multi/components/downtimes.phtml @@ -18,8 +18,8 @@ - Change - downtimes. + Change downtimes + . diff --git a/modules/monitoring/application/views/scripts/multi/components/objectlist.phtml b/modules/monitoring/application/views/scripts/multi/components/objectlist.phtml index a8621954f..21bdf3f3b 100644 --- a/modules/monitoring/application/views/scripts/multi/components/objectlist.phtml +++ b/modules/monitoring/application/views/scripts/multi/components/objectlist.phtml @@ -1,6 +1,6 @@ - is_service) { echo $this->href( 'monitoring/show/service', @@ -16,10 +16,9 @@ }?>, 5) { - $text = ' ' . (count($objects) - 5) . ' more ...'; - } else { - $text = ' show all ... '; + echo ' ... ' . (count($objects) - 5) . ' more ... '; } + 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 ); } -?> \ No newline at end of file +?> list all \ No newline at end of file diff --git a/public/js/icinga/events.js b/public/js/icinga/events.js index fcce23e4a..27fb89e21 100644 --- a/public/js/icinga/events.js +++ b/public/js/icinga/events.js @@ -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); diff --git a/public/js/icinga/loader.js b/public/js/icinga/loader.js index d083216bd..f525c2437 100644 --- a/public/js/icinga/loader.js +++ b/public/js/icinga/loader.js @@ -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'); }, diff --git a/public/js/icinga/ui.js b/public/js/icinga/ui.js index 6d8366b53..23a47677a 100644 --- a/public/js/icinga/ui.js +++ b/public/js/icinga/ui.js @@ -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; From 661dbea74045884fa6c25186dc471d31ed4bed4a Mon Sep 17 00:00:00 2001 From: Matthias Jentsch Date: Mon, 28 Apr 2014 09:46:35 +0200 Subject: [PATCH 8/8] Fix query that finds the nearest container of a table row and stores it. --- public/js/icinga/events.js | 2 +- public/js/icinga/ui.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/js/icinga/events.js b/public/js/icinga/events.js index 27fb89e21..d78229a16 100644 --- a/public/js/icinga/events.js +++ b/public/js/icinga/events.js @@ -382,7 +382,7 @@ } // update selection - if (event.ctrlKey && multisel) { + if ((event.ctrlKey || event.metaKey) && multisel) { icinga.ui.toogleTableRowSelection($tr); // multi selection } else if (event.shiftKey && multisel) { diff --git a/public/js/icinga/ui.js b/public/js/icinga/ui.js index 23a47677a..e71310d62 100644 --- a/public/js/icinga/ui.js +++ b/public/js/icinga/ui.js @@ -477,7 +477,7 @@ */ focusTable: function (table) { $('table').filter(function(){ return this !== table; }).find('tr[href]').removeClass('active'); - var n = $(table).closest('.container').data('icinga-url'); + var n = $(table).closest('div.container').attr('data-icinga-url'); focusedTableDataUrl = n; },