mirror of
https://github.com/Icinga/icingaweb2.git
synced 2025-07-26 23:34:08 +02:00
Merge branch 'bugfix/multiselection-not-visible-9054'
fixes #9054 fixes #9346
This commit is contained in:
commit
aecfb2eb97
@ -26,7 +26,8 @@ class JavaScript
|
|||||||
'js/icinga/behavior/sparkline.js',
|
'js/icinga/behavior/sparkline.js',
|
||||||
'js/icinga/behavior/tristate.js',
|
'js/icinga/behavior/tristate.js',
|
||||||
'js/icinga/behavior/navigation.js',
|
'js/icinga/behavior/navigation.js',
|
||||||
'js/icinga/behavior/form.js'
|
'js/icinga/behavior/form.js',
|
||||||
|
'js/icinga/behavior/actiontable.js'
|
||||||
);
|
);
|
||||||
|
|
||||||
protected static $vendorFiles = array(
|
protected static $vendorFiles = array(
|
||||||
|
@ -45,10 +45,11 @@ class Zend_View_Helper_Link extends Zend_View_Helper_Abstract
|
|||||||
* @param string $serviceLinkText Text for the service link, e.g. the service's display name
|
* @param string $serviceLinkText Text for the service link, e.g. the service's display name
|
||||||
* @param string $host Hostname
|
* @param string $host Hostname
|
||||||
* @param string $hostLinkText Text for the host link, e.g. the host's display name
|
* @param string $hostLinkText Text for the host link, e.g. the host's display name
|
||||||
|
* @param string $class An optional class to use for this link
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function service($service, $serviceLinkText, $host, $hostLinkText)
|
public function service($service, $serviceLinkText, $host, $hostLinkText, $class = null)
|
||||||
{
|
{
|
||||||
return sprintf(
|
return sprintf(
|
||||||
'%s: %s',
|
'%s: %s',
|
||||||
@ -57,11 +58,14 @@ class Zend_View_Helper_Link extends Zend_View_Helper_Abstract
|
|||||||
$serviceLinkText,
|
$serviceLinkText,
|
||||||
'monitoring/service/show',
|
'monitoring/service/show',
|
||||||
array('host' => $host, 'service' => $service),
|
array('host' => $host, 'service' => $service),
|
||||||
array('title' => sprintf(
|
array(
|
||||||
|
'title' => sprintf(
|
||||||
$this->view->translate('Show detailed information for service %s on host %s'),
|
$this->view->translate('Show detailed information for service %s on host %s'),
|
||||||
$service,
|
$service,
|
||||||
$host
|
$host
|
||||||
))
|
),
|
||||||
|
'class' => $class
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -43,11 +43,14 @@ if (count($comments) === 0) {
|
|||||||
),
|
),
|
||||||
'monitoring/comment/show',
|
'monitoring/comment/show',
|
||||||
array('comment_id' => $comment->id),
|
array('comment_id' => $comment->id),
|
||||||
array('title' => sprintf(
|
array(
|
||||||
|
'title' => sprintf(
|
||||||
$this->translate('Show detailed information for comment on %s for %s'),
|
$this->translate('Show detailed information for comment on %s for %s'),
|
||||||
$comment->service_display_name,
|
$comment->service_display_name,
|
||||||
$comment->host_display_name
|
$comment->host_display_name
|
||||||
))) ?>
|
),
|
||||||
|
'class' => 'rowaction'
|
||||||
|
)) ?>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<?= $this->icon('host', $this->translate('Host')); ?>
|
<?= $this->icon('host', $this->translate('Host')); ?>
|
||||||
|
|
||||||
|
@ -57,11 +57,14 @@ if (count($downtimes) === 0) {
|
|||||||
sprintf('%s: %s', $downtime->host_display_name, $downtime->service_display_name),
|
sprintf('%s: %s', $downtime->host_display_name, $downtime->service_display_name),
|
||||||
'monitoring/downtime/show',
|
'monitoring/downtime/show',
|
||||||
array('downtime_id' => $downtime->id),
|
array('downtime_id' => $downtime->id),
|
||||||
array('title' => sprintf(
|
array(
|
||||||
|
'title' => sprintf(
|
||||||
$this->translate('Show detailed information for downtime on %s for %s'),
|
$this->translate('Show detailed information for downtime on %s for %s'),
|
||||||
$downtime->service_display_name,
|
$downtime->service_display_name,
|
||||||
$downtime->host_display_name
|
$downtime->host_display_name
|
||||||
))) ?>
|
),
|
||||||
|
'class' => 'rowaction'
|
||||||
|
)) ?>
|
||||||
<br>
|
<br>
|
||||||
<?= $this->icon('comment', $this->translate('Comment')); ?> [<?= $this->escape($downtime->author_name) ?>] <?= $this->escape($downtime->comment) ?>
|
<?= $this->icon('comment', $this->translate('Comment')); ?> [<?= $this->escape($downtime->author_name) ?>] <?= $this->escape($downtime->comment) ?>
|
||||||
<br>
|
<br>
|
||||||
|
@ -82,7 +82,7 @@ if (count($history) === 0) {
|
|||||||
<td>
|
<td>
|
||||||
<?php if ($isService): ?>
|
<?php if ($isService): ?>
|
||||||
<?= $this->link()->service(
|
<?= $this->link()->service(
|
||||||
$event->service_description, $event->service_display_name, $event->host_name, $event->host_display_name
|
$event->service_description, $event->service_display_name, $event->host_name, $event->host_display_name, 'rowaction'
|
||||||
) ?>
|
) ?>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<?= $this->link()->host($event->host_name, $event->host_display_name) ?>
|
<?= $this->link()->host($event->host_name, $event->host_display_name) ?>
|
||||||
|
@ -60,7 +60,8 @@ if (count($hosts) === 0) {
|
|||||||
$hostLink,
|
$hostLink,
|
||||||
null,
|
null,
|
||||||
array(
|
array(
|
||||||
'title' => sprintf($this->translate('Show detailed information for host %s'), $host->host_display_name)
|
'title' => sprintf($this->translate('Show detailed information for host %s'), $host->host_display_name),
|
||||||
|
'class' => 'rowaction'
|
||||||
)
|
)
|
||||||
); ?>
|
); ?>
|
||||||
<?php if (isset($host->host_unhandled_services) && $host->host_unhandled_services > 0): ?>
|
<?php if (isset($host->host_unhandled_services) && $host->host_unhandled_services > 0): ?>
|
||||||
|
396
public/js/icinga/behavior/actiontable.js
Normal file
396
public/js/icinga/behavior/actiontable.js
Normal file
@ -0,0 +1,396 @@
|
|||||||
|
/* Icinga Web 2 | (c) 2013-2015 Icinga Development Team | GPLv2+ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icinga.Behavior.ActionTable
|
||||||
|
*
|
||||||
|
* A multi selection that distincts between the table rows using the row action URL filter
|
||||||
|
*/
|
||||||
|
(function(Icinga, $) {
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove one leading and trailing bracket and all text outside those brackets
|
||||||
|
*
|
||||||
|
* @param str {String}
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
var stripBrackets = function (str) {
|
||||||
|
return str.replace(/^[^\(]*\(/, '').replace(/\)[^\)]*$/, '');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the filter query contained in the given url filter string
|
||||||
|
*
|
||||||
|
* @param filterString {String}
|
||||||
|
*
|
||||||
|
* @returns {Array} An object containing each row filter
|
||||||
|
*/
|
||||||
|
var parseSelectionQuery = function(filterString) {
|
||||||
|
var selections = [];
|
||||||
|
$.each(stripBrackets(filterString).split('|'), function(i, row) {
|
||||||
|
var tuple = {};
|
||||||
|
$.each(stripBrackets(row).split('&'), function(i, keyValue) {
|
||||||
|
var s = keyValue.split('=');
|
||||||
|
tuple[s[0]] = decodeURIComponent(s[1]);
|
||||||
|
});
|
||||||
|
selections.push(tuple);
|
||||||
|
});
|
||||||
|
return selections;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the selection of an action table
|
||||||
|
*
|
||||||
|
* @param table {HTMLElement} The table
|
||||||
|
* @param {Icinga}
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
var Selection = function(table, icinga) {
|
||||||
|
this.$el = $(table);
|
||||||
|
this.icinga = icinga;
|
||||||
|
|
||||||
|
if (this.hasMultiselection()) {
|
||||||
|
if (! this.getMultiselectionKeys().length) {
|
||||||
|
icinga.logger.error('multiselect table has no data-icinga-multiselect-data');
|
||||||
|
}
|
||||||
|
if (! this.getMultiselectionUrl()) {
|
||||||
|
icinga.logger.error('multiselect table has no data-icinga-multiselect-url');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Selection.prototype = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all rows as jQuery selector
|
||||||
|
*
|
||||||
|
* @returns {jQuery}
|
||||||
|
*/
|
||||||
|
rows: function() {
|
||||||
|
return this.$el.find('tr');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all row action links as jQuery selector
|
||||||
|
*
|
||||||
|
* @returns {jQuery}
|
||||||
|
*/
|
||||||
|
rowActions: function() {
|
||||||
|
return this.$el.find('tr a.rowaction');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all selected rows as jQuery selector
|
||||||
|
*
|
||||||
|
* @returns {jQuery}
|
||||||
|
*/
|
||||||
|
selections: function() {
|
||||||
|
return this.$el.find('tr.active');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this selection allows selecting multiple rows
|
||||||
|
*
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
hasMultiselection: function() {
|
||||||
|
return this.$el.hasClass('multiselect');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all filter keys that are significant when applying the selection
|
||||||
|
*
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
getMultiselectionKeys: function() {
|
||||||
|
var data = this.$el.data('icinga-multiselect-data');
|
||||||
|
return (data && data.split(',')) || [];
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the target URL that is used when multi selecting rows
|
||||||
|
*
|
||||||
|
* This URL may differ from the url that is used when applying single rows
|
||||||
|
*
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
getMultiselectionUrl: function() {
|
||||||
|
return this.$el.data('icinga-multiselect-url');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read all filter data from the given row
|
||||||
|
*
|
||||||
|
* @param row {jQuery} The row element
|
||||||
|
*
|
||||||
|
* @returns {Object} An object containing all filter data in this row as key-value pairs
|
||||||
|
*/
|
||||||
|
getRowData: function(row) {
|
||||||
|
var params = this.icinga.utils.parseUrl(row.attr('href')).params;
|
||||||
|
var tuple = {};
|
||||||
|
var keys = this.getMultiselectionKeys();
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
var key = keys[i];
|
||||||
|
if (params[key]) {
|
||||||
|
tuple[key] = params[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tuple;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deselect all selected rows
|
||||||
|
*/
|
||||||
|
clear: function() {
|
||||||
|
this.selections().removeClass('active');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add all rows that match the given filter to the selection
|
||||||
|
*
|
||||||
|
* @param filter {jQuery|Object} Either an object containing filter variables or the actual row to select
|
||||||
|
*/
|
||||||
|
select: function(filter) {
|
||||||
|
if (filter instanceof jQuery) {
|
||||||
|
filter.addClass('active');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var self = this;
|
||||||
|
var url = this.getMultiselectionUrl();
|
||||||
|
this.rowActions()
|
||||||
|
.filter(
|
||||||
|
function (i, el) {
|
||||||
|
var params = self.getRowData($(el));
|
||||||
|
if (self.icinga.utils.objectKeys(params).length !== self.icinga.utils.objectKeys(filter).length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var equal = true;
|
||||||
|
$.each(params, function(key, value) {
|
||||||
|
if (filter[key] !== value) {
|
||||||
|
equal = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return equal;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.closest('tr')
|
||||||
|
.addClass('active');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle the selection of the row between on and off
|
||||||
|
*
|
||||||
|
* @param row {jQuery} The row to toggle
|
||||||
|
*/
|
||||||
|
toggle: function(row) {
|
||||||
|
row.toggleClass('active');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new selection range to the closest table, using the selected row as
|
||||||
|
* range target.
|
||||||
|
*
|
||||||
|
* @param row {jQuery} The target of the selected range.
|
||||||
|
*
|
||||||
|
* @returns {boolean} If the selection was changed.
|
||||||
|
*/
|
||||||
|
range: function(row) {
|
||||||
|
var from, to;
|
||||||
|
var selected = row.first().get(0);
|
||||||
|
this.rows().each(function(i, el) {
|
||||||
|
if ($(el).hasClass('active') || el === selected) {
|
||||||
|
if (!from) {
|
||||||
|
from = el;
|
||||||
|
}
|
||||||
|
to = el;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var inRange = false;
|
||||||
|
this.rows().each(function(i, el) {
|
||||||
|
if (el === from) {
|
||||||
|
inRange = true;
|
||||||
|
}
|
||||||
|
if (inRange) {
|
||||||
|
$(el).addClass('active');
|
||||||
|
}
|
||||||
|
if (el === to) {
|
||||||
|
inRange = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select rows that target the given url
|
||||||
|
*
|
||||||
|
* @param url {String} The target url
|
||||||
|
*/
|
||||||
|
selectUrl: function(url) {
|
||||||
|
this.rows().filter('[href="' + url + '"]').addClass('active');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert all currently selected rows into an url query string
|
||||||
|
*
|
||||||
|
* @returns {String} The filter string
|
||||||
|
*/
|
||||||
|
toQuery: function() {
|
||||||
|
var self = this;
|
||||||
|
var selections = this.selections();
|
||||||
|
var queries = [];
|
||||||
|
if (selections.length === 1) {
|
||||||
|
return $(selections[0]).attr('href');
|
||||||
|
} else if (selections.length > 1 && self.hasMultiselection()) {
|
||||||
|
selections.each(function (i, el) {
|
||||||
|
var parts = [];
|
||||||
|
$.each(self.getRowData($(el)), function(key, value) {
|
||||||
|
parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
|
||||||
|
});
|
||||||
|
queries.push('(' + parts.join('&') + ')');
|
||||||
|
});
|
||||||
|
return self.getMultiselectionUrl() + '?(' + queries.join('|') + ')';
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the displayed active columns using the current page location
|
||||||
|
*/
|
||||||
|
refresh: function() {
|
||||||
|
this.clear();
|
||||||
|
var hash = this.icinga.utils.parseUrl(window.location.href).hash;
|
||||||
|
if (this.hasMultiselection()) {
|
||||||
|
var query = parseSelectionQuery(hash);
|
||||||
|
if (query.length > 1 && this.getMultiselectionUrl() === this.icinga.utils.parseUrl(hash.substr(1)).path) {
|
||||||
|
// select all rows with matching filters
|
||||||
|
var self = this;
|
||||||
|
$.each(query, function(i, selection) {
|
||||||
|
self.select(selection);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (query.length > 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.selectUrl(hash.substr(1));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Icinga.Behaviors = Icinga.Behaviors || {};
|
||||||
|
|
||||||
|
var ActionTable = function (icinga) {
|
||||||
|
Icinga.EventListener.call(this, icinga);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The hash that is currently being loaded
|
||||||
|
*
|
||||||
|
* @var String
|
||||||
|
*/
|
||||||
|
this.loadingHash = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If currently loading
|
||||||
|
*
|
||||||
|
* @var Boolean
|
||||||
|
*/
|
||||||
|
this.loading = false;
|
||||||
|
|
||||||
|
this.on('rendered', this.onRendered, this);
|
||||||
|
this.on('click', 'table.action tr[href]', this.onRowClicked, this);
|
||||||
|
};
|
||||||
|
ActionTable.prototype = new Icinga.EventListener();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all active tables in this table, or in the context as jQuery selector
|
||||||
|
*
|
||||||
|
* @param context {HTMLElement}
|
||||||
|
* @returns {jQuery}
|
||||||
|
*/
|
||||||
|
ActionTable.prototype.tables = function(context) {
|
||||||
|
if (context) {
|
||||||
|
return $(context).find('table.action');
|
||||||
|
}
|
||||||
|
return $('table.action');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle clicks on table rows and update selection and history
|
||||||
|
*/
|
||||||
|
ActionTable.prototype.onRowClicked = function (event) {
|
||||||
|
var self = event.data.self;
|
||||||
|
var $tr = $(event.target).closest('tr');
|
||||||
|
var table = new Selection($tr.closest('table.action')[0], self.icinga);
|
||||||
|
|
||||||
|
// allow form actions in table rows to pass through
|
||||||
|
if ($(event.target).closest('form').length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// update selection
|
||||||
|
if (table.hasMultiselection()) {
|
||||||
|
if (event.ctrlKey || event.metaKey) {
|
||||||
|
// add to selection
|
||||||
|
table.toggle($tr);
|
||||||
|
} else if (event.shiftKey) {
|
||||||
|
// range selection
|
||||||
|
table.range($tr);
|
||||||
|
} else {
|
||||||
|
// single selection
|
||||||
|
table.clear();
|
||||||
|
table.select($tr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
table.clear();
|
||||||
|
table.select($tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update history
|
||||||
|
var url = self.icinga.utils.parseUrl(window.location.href.split('#')[0]);
|
||||||
|
var count = table.selections().length;
|
||||||
|
var state = url.path + url.query;
|
||||||
|
if (count > 0) {
|
||||||
|
var query = table.toQuery();
|
||||||
|
self.icinga.loader.loadUrl(query, self.icinga.events.getLinkTargetFor($tr));
|
||||||
|
state += '#!' + query;
|
||||||
|
} else {
|
||||||
|
if (self.icinga.events.getLinkTargetFor($tr).attr('id') === 'col2') {
|
||||||
|
self.icinga.ui.layout1col();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.icinga.history.pushUrl(state);
|
||||||
|
|
||||||
|
// re draw all table selections
|
||||||
|
self.tables().each(function () {
|
||||||
|
new Selection(this, self.icinga).refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
// update selection info
|
||||||
|
$('.selection-info-count').text(table.selections().size());
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that
|
||||||
|
*/
|
||||||
|
ActionTable.prototype.onRendered = function(evt) {
|
||||||
|
var container = evt.target;
|
||||||
|
var self = evt.data.self;
|
||||||
|
|
||||||
|
// draw all selections
|
||||||
|
self.tables().each(function(i, el) {
|
||||||
|
new Selection(el, self.icinga).refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
// update displayed selection count
|
||||||
|
var table = new Selection(self.tables(container).first());
|
||||||
|
$(container).find('.selection-info-count').text(table.selections().size());
|
||||||
|
};
|
||||||
|
|
||||||
|
Icinga.Behaviors.ActionTable = ActionTable;
|
||||||
|
|
||||||
|
}) (Icinga, jQuery);
|
@ -117,9 +117,6 @@
|
|||||||
$(document).on('click', 'a', { self: this }, this.linkClicked);
|
$(document).on('click', 'a', { self: this }, this.linkClicked);
|
||||||
$(document).on('click', 'tr[href]', { self: this }, this.linkClicked);
|
$(document).on('click', 'tr[href]', { self: this }, this.linkClicked);
|
||||||
|
|
||||||
// Select a table row
|
|
||||||
$(document).on('click', 'table.multiselect tr[href]', { self: this }, this.rowSelected);
|
|
||||||
|
|
||||||
// We catch all form submit events
|
// We catch all form submit events
|
||||||
$(document).on('submit', 'form', { self: this }, this.submitForm);
|
$(document).on('submit', 'form', { self: this }, this.submitForm);
|
||||||
|
|
||||||
@ -303,74 +300,6 @@
|
|||||||
return false;
|
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 = self.icinga.ui.getSelectionKeys($table);
|
|
||||||
var url = $table.data('icinga-multiselect-url');
|
|
||||||
|
|
||||||
if ($(event.target).closest('form').length) {
|
|
||||||
// allow form actions in table rows to pass through
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
icinga.logger.error('multiselect table has no data-icinga-multiselect-data');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!url) {
|
|
||||||
icinga.logger.error('multiselect table has no data-icinga-multiselect-url');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update selection
|
|
||||||
if (event.ctrlKey || event.metaKey) {
|
|
||||||
icinga.ui.toogleTableRowSelection($tr);
|
|
||||||
// multi selection
|
|
||||||
} else if (event.shiftKey) {
|
|
||||||
// range selection
|
|
||||||
icinga.ui.addTableRowRangeSelection($tr);
|
|
||||||
} else {
|
|
||||||
// single selection
|
|
||||||
icinga.ui.setTableRowSelection($tr);
|
|
||||||
}
|
|
||||||
// focus only the current table.
|
|
||||||
icinga.ui.focusTable($table[0]);
|
|
||||||
|
|
||||||
var $target = self.getLinkTargetFor($tr);
|
|
||||||
|
|
||||||
var $trs = $table.find('tr[href].active');
|
|
||||||
if ($trs.length > 1) {
|
|
||||||
var selectionData = icinga.ui.getSelectionSetData($trs, data);
|
|
||||||
var query = icinga.ui.selectionDataToQuery(selectionData);
|
|
||||||
icinga.loader.loadUrl(url + '?' + query, $target);
|
|
||||||
icinga.ui.storeSelectionData(selectionData);
|
|
||||||
icinga.ui.provideSelectionCount();
|
|
||||||
} else if ($trs.length === 1) {
|
|
||||||
// display a single row
|
|
||||||
$tr = $trs.first();
|
|
||||||
icinga.loader.loadUrl($tr.attr('href'), $target);
|
|
||||||
icinga.ui.storeSelectionData($tr.attr('href'));
|
|
||||||
icinga.ui.provideSelectionCount();
|
|
||||||
} else {
|
|
||||||
// display nothing
|
|
||||||
if ($target.attr('id') === 'col2') {
|
|
||||||
icinga.ui.layout1col();
|
|
||||||
}
|
|
||||||
icinga.ui.storeSelectionData(null);
|
|
||||||
icinga.ui.provideSelectionCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle anchor, i.e. focus the element which is referenced by the anchor
|
* Handle anchor, i.e. focus the element which is referenced by the anchor
|
||||||
*
|
*
|
||||||
@ -406,16 +335,16 @@
|
|||||||
// Special checks for link clicks in multiselect rows
|
// Special checks for link clicks in multiselect rows
|
||||||
if (! $a.is('tr[href]') && $a.closest('tr[href]').length > 0 && $a.closest('table.multiselect').length > 0) {
|
if (! $a.is('tr[href]') && $a.closest('tr[href]').length > 0 && $a.closest('table.multiselect').length > 0) {
|
||||||
|
|
||||||
// Forward clicks to ANY link with special key pressed to rowSelected
|
// ignoray clicks to ANY link with special key pressed
|
||||||
if (event.ctrlKey || event.metaKey || event.shiftKey)
|
if (event.ctrlKey || event.metaKey || event.shiftKey)
|
||||||
{
|
{
|
||||||
return self.rowSelected.call($a.closest('tr[href]'), event);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forward inner links matching the row URL to rowSelected
|
// ignore inner links matching the row URL
|
||||||
if ($a.attr('href') === $a.closest('tr[href]').attr('href'))
|
if ($a.attr('href') === $a.closest('tr[href]').attr('href'))
|
||||||
{
|
{
|
||||||
return self.rowSelected.call($a.closest('tr[href]'), event);
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -477,8 +406,6 @@
|
|||||||
icinga.ui.layout1col();
|
icinga.ui.layout1col();
|
||||||
}
|
}
|
||||||
$('table tr[href].active').removeClass('active');
|
$('table tr[href].active').removeClass('active');
|
||||||
icinga.ui.storeSelectionData(null);
|
|
||||||
icinga.ui.loadSelectionData();
|
|
||||||
icinga.history.pushCurrentState();
|
icinga.history.pushCurrentState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -574,8 +501,6 @@
|
|||||||
$(window).off('beforeunload', this.onUnload);
|
$(window).off('beforeunload', this.onUnload);
|
||||||
$(document).off('scroll', '.container', this.onContainerScroll);
|
$(document).off('scroll', '.container', this.onContainerScroll);
|
||||||
$(document).off('click', 'a', this.linkClicked);
|
$(document).off('click', 'a', this.linkClicked);
|
||||||
$(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('submit', 'form', this.submitForm);
|
||||||
$(document).off('change', 'form select.autosubmit', this.submitForm);
|
$(document).off('change', 'form select.autosubmit', this.submitForm);
|
||||||
$(document).off('change', 'form input.autosubmit', this.submitForm);
|
$(document).off('change', 'form input.autosubmit', this.submitForm);
|
||||||
|
@ -504,42 +504,6 @@
|
|||||||
this.icinga.ui.fixDebugVisibility().triggerWindowResize();
|
this.icinga.ui.fixDebugVisibility().triggerWindowResize();
|
||||||
}
|
}
|
||||||
self.cacheLoadedIcons(req.$target);
|
self.cacheLoadedIcons(req.$target);
|
||||||
|
|
||||||
if (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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -9,13 +9,6 @@
|
|||||||
|
|
||||||
'use strict';
|
'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) {
|
Icinga.UI = function (icinga) {
|
||||||
|
|
||||||
this.icinga = icinga;
|
this.icinga = icinga;
|
||||||
@ -38,8 +31,7 @@
|
|||||||
this.fadeNotificationsAway();
|
this.fadeNotificationsAway();
|
||||||
},
|
},
|
||||||
|
|
||||||
fadeNotificationsAway: function()
|
fadeNotificationsAway: function() {
|
||||||
{
|
|
||||||
var icinga = this.icinga;
|
var icinga = this.icinga;
|
||||||
$('#notifications li')
|
$('#notifications li')
|
||||||
.not('.fading-out')
|
.not('.fading-out')
|
||||||
@ -298,226 +290,6 @@
|
|||||||
return $('#main > .container').length;
|
return $('#main > .container').length;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the given table-row to the selection of the closest
|
|
||||||
* table and deselect all other rows 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');
|
|
||||||
$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;
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
|
||||||
this.icinga.logger.error('Something went wrong, we should never multiselect just one row');
|
|
||||||
} else {
|
|
||||||
$.each(selectionData, function(i, el){
|
|
||||||
var parts = []
|
|
||||||
$.each(el, function(key, value) {
|
|
||||||
parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
|
|
||||||
});
|
|
||||||
queries.push('(' + parts.join('&') + ')');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
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() {
|
|
||||||
this.provideSelectionCount();
|
|
||||||
return selectionData;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the selections row count hint info
|
|
||||||
*/
|
|
||||||
provideSelectionCount: function() {
|
|
||||||
var $count = $('.selection-info-count');
|
|
||||||
|
|
||||||
if (typeof selectionData === 'undefined' || selectionData === null) {
|
|
||||||
$count.text(0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof selectionData === 'string') {
|
|
||||||
$count.text(1);
|
|
||||||
} else if (selectionData.length > 1) {
|
|
||||||
$count.text(selectionData.length);
|
|
||||||
} else {
|
|
||||||
$count.text(0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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');
|
|
||||||
var n = $(table).closest('div.container').attr('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;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign a unique ID to each .container without such
|
* Assign a unique ID to each .container without such
|
||||||
*
|
*
|
||||||
|
@ -293,6 +293,14 @@
|
|||||||
return $element[0];
|
return $element[0];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
objectKeys: Object.keys || function (obj) {
|
||||||
|
var keys = [];
|
||||||
|
$.each(obj, function (key) {
|
||||||
|
keys.push(key);
|
||||||
|
});
|
||||||
|
return keys;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cleanup
|
* Cleanup
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user