You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
800 lines
22 KiB
800 lines
22 KiB
1 year ago
|
/*
|
||
|
** Zabbix
|
||
|
** Copyright (C) 2001-2023 Zabbix SIA
|
||
|
**
|
||
|
** This program is free software; you can redistribute it and/or modify
|
||
|
** it under the terms of the GNU General Public License as published by
|
||
|
** the Free Software Foundation; either version 2 of the License, or
|
||
|
** (at your option) any later version.
|
||
|
**
|
||
|
** This program is distributed in the hope that it will be useful,
|
||
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
** GNU General Public License for more details.
|
||
|
**
|
||
|
** You should have received a copy of the GNU General Public License
|
||
|
** along with this program; if not, write to the Free Software
|
||
|
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||
|
**/
|
||
|
|
||
|
|
||
|
const TABFILTER_EVENT_URLSET = 'urlset.tabfilter';
|
||
|
const TABFILTER_EVENT_UPDATE = 'update.tabfilter';
|
||
|
const TABFILTER_EVENT_NEWITEM = 'newitem.tabfilter';
|
||
|
|
||
|
class CTabFilter extends CBaseComponent {
|
||
|
|
||
|
constructor(target, options) {
|
||
|
super(target);
|
||
|
this._options = options;
|
||
|
// Array of CTabFilterItem objects.
|
||
|
this._items = [];
|
||
|
this._active_item = null;
|
||
|
this._filters_footer = null;
|
||
|
// NodeList of available templates (<script> DOM elements).
|
||
|
this._templates = {};
|
||
|
this._fetch = {};
|
||
|
this._idx_namespace = options.idx;
|
||
|
this._timeselector = null;
|
||
|
this._csrf_token = this._options.csrf_token;
|
||
|
|
||
|
this.init(options);
|
||
|
this.registerEvents();
|
||
|
this.initItemUnsavedState(this._active_item, this._active_item._data);
|
||
|
|
||
|
if (this._timeselector instanceof CTabFilterItem) {
|
||
|
this._timeselector._data = options.timeselector;
|
||
|
this.updateTimeselector(this._active_item, this._timeselector._data.disabled);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
init(options) {
|
||
|
let item, index = 0;
|
||
|
|
||
|
this._filters_footer = this._target.querySelector('.form-buttons');
|
||
|
|
||
|
if (options.expanded) {
|
||
|
options.data[options.selected].expanded = true;
|
||
|
}
|
||
|
|
||
|
for (const template of this._target.querySelectorAll('[data-template]')) {
|
||
|
this._templates[template.getAttribute('data-template')] = template;
|
||
|
}
|
||
|
|
||
|
for (const title of this._target.querySelectorAll('nav [data-target]')) {
|
||
|
item = this.create(title, options.data[index] || {});
|
||
|
|
||
|
if (index > 0) {
|
||
|
item.initUnsavedIndicator();
|
||
|
}
|
||
|
|
||
|
if (options.selected == index) {
|
||
|
this.setSelectedItem(item);
|
||
|
|
||
|
if (options.expanded) {
|
||
|
item.setExpanded();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
index++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Ensures item label is visible in tab filter navigation.
|
||
|
*
|
||
|
* @param {CTabfilterItem} item Filter item object.
|
||
|
*/
|
||
|
scrollIntoView(item) {
|
||
|
let scrollable_parent = item._target.closest('.ui-sortable-container').parentNode;
|
||
|
|
||
|
scrollable_parent.scrollLeft = item._target.parentNode.offsetLeft - item._target.parentNode.clientWidth;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render filter with profiles stored data to hidden container to get source url for unsaved state comparison.
|
||
|
*
|
||
|
* @param {CTabfilterItem} item Selected filter object.
|
||
|
* @param {object} filter_data Selected filter object filter data.
|
||
|
*/
|
||
|
initItemUnsavedState(item, filter_data) {
|
||
|
let filter_src = {...{tab_view: filter_data.tab_view}, ...filter_data.filter_src},
|
||
|
target = item._target.parentNode.cloneNode(true),
|
||
|
src_item;
|
||
|
|
||
|
filter_src.uniqid = filter_data.uniqid + '__clone';
|
||
|
filter_src.filter_configurable = filter_data.filter_configurable;
|
||
|
target.setAttribute('data-target', target.getAttribute('data-target') + '__clone');
|
||
|
src_item = this.create(target, filter_src);
|
||
|
this._items.pop();
|
||
|
|
||
|
src_item.renderContentTemplate();
|
||
|
item._src_url = src_item._src_url;
|
||
|
src_item.delete();
|
||
|
item.updateUnsavedState();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Delete item from items collection.
|
||
|
*/
|
||
|
delete(item) {
|
||
|
let index = this._items.indexOf(item);
|
||
|
|
||
|
if (index != -1) {
|
||
|
this.setSelectedItem(this._items[index - 1]);
|
||
|
|
||
|
if (item._expanded) {
|
||
|
this._active_item.setExpanded();
|
||
|
}
|
||
|
|
||
|
item.delete();
|
||
|
delete this._items[index];
|
||
|
this._items.splice(index, 1);
|
||
|
this._items.forEach((item, index) => {
|
||
|
item._index = index;
|
||
|
});
|
||
|
this._active_item.setBrowserLocation(this._active_item.getFilterParams());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Create new CTabFilterItem object with it container if it does not exists and append to _items array.
|
||
|
*
|
||
|
* @param {HTMLElement} title HTML node element of tab label.
|
||
|
* @param {object} data Filter item dynamic data for template.
|
||
|
*
|
||
|
* @return {CTabFilterItem}
|
||
|
*/
|
||
|
create(title, data) {
|
||
|
let item,
|
||
|
containers = this._target.querySelector('.tabfilter-tabs-container'),
|
||
|
container = containers.querySelector('#' + title.getAttribute('data-target'));
|
||
|
|
||
|
if (!container) {
|
||
|
container = document.createElement('div');
|
||
|
container.setAttribute('id', title.getAttribute('data-target'));
|
||
|
container.classList.add('display-none');
|
||
|
containers.appendChild(container);
|
||
|
}
|
||
|
|
||
|
item = new CTabFilterItem(title.querySelector('a'), {
|
||
|
parent: this,
|
||
|
idx_namespace: this._idx_namespace,
|
||
|
index: this._items.length,
|
||
|
expanded: data.expanded || false,
|
||
|
container: container,
|
||
|
data: data,
|
||
|
template: this._templates[data.tab_view] || null,
|
||
|
support_custom_time: this._options.support_custom_time
|
||
|
});
|
||
|
|
||
|
this._items.push(item);
|
||
|
|
||
|
if (title.getAttribute('data-target') === 'tabfilter_timeselector') {
|
||
|
this._timeselector = item;
|
||
|
}
|
||
|
|
||
|
return item;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Fire event TABFILTERITEM_EVENT_COLLAPSE on every expanded tab except passed one.
|
||
|
*
|
||
|
* @param {CTabFilterItem} except Tab item object.
|
||
|
*/
|
||
|
collapseAllItemsExcept(except) {
|
||
|
for (const item of this._items) {
|
||
|
if (item !== except && item._expanded) {
|
||
|
item.fire(TABFILTERITEM_EVENT_COLLAPSE);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Update timeselector tab and timeselector buttons accessibility according passed item.
|
||
|
*
|
||
|
* @param {CTabFilterItem} item Tab item object.
|
||
|
* @param {bool} disable Additional status to determine should the timeselector to be disabled or not.
|
||
|
*/
|
||
|
updateTimeselector(item, disable) {
|
||
|
if (!this._timeselector) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
let disabled = disable || (!this._options.support_custom_time || item.hasCustomTime()),
|
||
|
buttons = {
|
||
|
decrement_button: this._target.querySelector('button.js-btn-time-left'),
|
||
|
increment_button: this._target.querySelector('button.js-btn-time-right'),
|
||
|
zoomout_button: this._target.querySelector('button.btn-time-zoomout')
|
||
|
};
|
||
|
|
||
|
this._timeselector.setDisabled(disabled);
|
||
|
this._timeselector._target.setAttribute('tabindex', disabled ? -1 : 0);
|
||
|
|
||
|
if (disabled || !this._timeselector._data.can_decrement) {
|
||
|
buttons.decrement_button.setAttribute('disabled', 'disabled');
|
||
|
}
|
||
|
else {
|
||
|
buttons.decrement_button.removeAttribute('disabled');
|
||
|
}
|
||
|
|
||
|
if (disabled || !this._timeselector._data.can_increment) {
|
||
|
buttons.increment_button.setAttribute('disabled', 'disabled');
|
||
|
}
|
||
|
else {
|
||
|
buttons.increment_button.removeAttribute('disabled');
|
||
|
}
|
||
|
|
||
|
if (disabled || !this._timeselector._data.can_zoomout) {
|
||
|
buttons.zoomout_button.setAttribute('disabled', 'disabled');
|
||
|
}
|
||
|
else {
|
||
|
buttons.zoomout_button.removeAttribute('disabled');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Updates filter values in user profile. Aborts previous unfinished update of property.
|
||
|
*
|
||
|
* @param {string} property Filter property to be updated: 'selected', 'expanded', 'properties', 'taborder'.
|
||
|
* @param {object} body Key value pair of data to be passed to profile.update action.
|
||
|
*
|
||
|
* @return {Promise}
|
||
|
*/
|
||
|
profileUpdate(property, body) {
|
||
|
let url = new Curl('zabbix.php'),
|
||
|
signal = null;
|
||
|
|
||
|
url.setArgument('action', 'tabfilter.profile.update');
|
||
|
this.fire(TABFILTER_EVENT_UPDATE, {filter_property: property});
|
||
|
|
||
|
if (this._fetch[property] && ('abort' in this._fetch[property]) && !this._fetch[property].aborted) {
|
||
|
this._fetch[property].abort();
|
||
|
}
|
||
|
|
||
|
body.idx = this._idx_namespace + '.' + property;
|
||
|
body._csrf_token = this._csrf_token;
|
||
|
|
||
|
if (property !== 'properties') {
|
||
|
this._fetch[property] = new AbortController();
|
||
|
signal = this._fetch[property].signal;
|
||
|
}
|
||
|
|
||
|
return fetch(url.getUrl(), {
|
||
|
method: 'POST',
|
||
|
signal: signal,
|
||
|
body: new URLSearchParams(body)
|
||
|
})
|
||
|
.then(() => {
|
||
|
this._fetch[property] = null;
|
||
|
})
|
||
|
.catch(() => {
|
||
|
// User aborted a request.
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Update all tab filter counters values.
|
||
|
*
|
||
|
* @param {array} counters Array of counters to be set.
|
||
|
*/
|
||
|
updateCounters(counters) {
|
||
|
counters.forEach((value, index) => {
|
||
|
let item = this._items[index];
|
||
|
|
||
|
if (item) {
|
||
|
item.updateCounter(value);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
if (this._active_item !== this._timeselector) {
|
||
|
this.scrollIntoView(this._active_item);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set item object as current selected item, also ensures that only one selected item with filters form exists.
|
||
|
*
|
||
|
* @param {CTabFilterItem} item Item object to be set as selected item.
|
||
|
*/
|
||
|
setSelectedItem(item) {
|
||
|
this._active_item = item;
|
||
|
this._active_item.unsetExpandedSubfilters();
|
||
|
item.setSelected();
|
||
|
|
||
|
if (item !== this._timeselector) {
|
||
|
item._target.setAttribute('tabindex', 0);
|
||
|
this.scrollIntoView(item);
|
||
|
item.setBrowserLocationToApplyUrl();
|
||
|
}
|
||
|
|
||
|
for (const _item of this._items) {
|
||
|
if (_item !== this._active_item && this._timeselector !== this._active_item) {
|
||
|
_item.removeSelected();
|
||
|
|
||
|
if (_item !== this._timeselector) {
|
||
|
_item._target.setAttribute('tabindex', -1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get first selected item. Return null if no item is selected.
|
||
|
*
|
||
|
* @return {CTabFilterItem}
|
||
|
*/
|
||
|
getSelectedItem() {
|
||
|
for (const item of this._items) {
|
||
|
if (item.isSelected()) {
|
||
|
return item;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Enable subfilter option by key and value.
|
||
|
*/
|
||
|
setSubfilter(key, value) {
|
||
|
this._active_item.setSubfilter(key, value);
|
||
|
this._active_item.updateUnsavedState();
|
||
|
this._active_item.updateApplyUrl();
|
||
|
this._active_item.setBrowserLocationToApplyUrl();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Disable subfilter option by key and value.
|
||
|
*/
|
||
|
unsetSubfilter(key, value) {
|
||
|
this._active_item.unsetSubfilter(key, value);
|
||
|
this._active_item.updateUnsavedState();
|
||
|
this._active_item.updateApplyUrl();
|
||
|
this._active_item.setBrowserLocationToApplyUrl();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set expanded subfilter name.
|
||
|
*/
|
||
|
setExpandedSubfilters(name) {
|
||
|
return this._active_item.setExpandedSubfilters(name);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Retrieve expanded subfilter names.
|
||
|
*
|
||
|
* @returns {array}
|
||
|
*/
|
||
|
getExpandedSubfilters() {
|
||
|
return this._active_item.getExpandedSubfilters();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Register tab filter events, called once during initialization.
|
||
|
*/
|
||
|
registerEvents() {
|
||
|
this._events = {
|
||
|
/**
|
||
|
* Event handler on tab content expand.
|
||
|
*/
|
||
|
select: (ev) => {
|
||
|
let item = ev.detail.target,
|
||
|
expand = (this._active_item._expanded
|
||
|
|| (item === this._timeselector && this._active_item !== this._timeselector)
|
||
|
|| (item !== this._timeselector && this._active_item === this._timeselector)
|
||
|
);
|
||
|
|
||
|
if (item !== this._timeselector) {
|
||
|
if (item.isSelected()) {
|
||
|
this.profileUpdate('expanded', {
|
||
|
value_int: item._expanded ? 0 : 1
|
||
|
}).then(() => {
|
||
|
this._options.expanded = +item._expanded;
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
item.setFocused();
|
||
|
item.initUnsavedState();
|
||
|
this.profileUpdate('selected', {
|
||
|
value_int: item._index
|
||
|
}).then(() => {
|
||
|
this._options.selected = item._index;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
this.scrollIntoView(item);
|
||
|
}
|
||
|
|
||
|
if (item !== this._active_item) {
|
||
|
this.setSelectedItem(item);
|
||
|
this.collapseAllItemsExcept(item);
|
||
|
|
||
|
if (expand) {
|
||
|
item.setFocused();
|
||
|
item.fire(TABFILTERITEM_EVENT_EXPAND);
|
||
|
}
|
||
|
}
|
||
|
else if (!item._expanded) {
|
||
|
item.fire(TABFILTERITEM_EVENT_EXPAND);
|
||
|
}
|
||
|
else {
|
||
|
item.fire(TABFILTERITEM_EVENT_COLLAPSE);
|
||
|
}
|
||
|
|
||
|
if (this._timeselector instanceof CTabFilterItem && item !== this._timeselector
|
||
|
&& this._timeselector._expanded) {
|
||
|
this._timeselector.removeExpanded();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
expand: (ev) => {
|
||
|
let item = ev.detail.target;
|
||
|
|
||
|
if (item !== this._timeselector) {
|
||
|
this._filters_footer.classList.remove('display-none');
|
||
|
}
|
||
|
else {
|
||
|
this._filters_footer.classList.add('display-none');
|
||
|
}
|
||
|
|
||
|
item.setExpanded();
|
||
|
|
||
|
const tabfilter = this._target.querySelector('.tabfilter-content-container');
|
||
|
tabfilter.classList.remove('tabfilter-collapsed', 'display-none');
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Event handler on tab content collapse.
|
||
|
*/
|
||
|
collapse: (ev) => {
|
||
|
let item = ev.detail.target;
|
||
|
|
||
|
if (item !== this._timeselector) {
|
||
|
item.updateUnsavedState();
|
||
|
}
|
||
|
|
||
|
item.removeExpanded();
|
||
|
const tabfilter = this._target.querySelector('.tabfilter-content-container');
|
||
|
if (tabfilter.querySelector('.tabfilter-subfilter')) {
|
||
|
tabfilter.classList.add('tabfilter-collapsed');
|
||
|
}
|
||
|
else {
|
||
|
tabfilter.classList.add('tabfilter-collapsed', 'display-none');
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* UI sortable update event handler. Updates tab sorting in profile.
|
||
|
*/
|
||
|
tabSortChanged: (ev, ui) => {
|
||
|
// Update order of this._items array.
|
||
|
let from,
|
||
|
to,
|
||
|
item_moved;
|
||
|
const target = ui.item[0].querySelector('[data-target] .tabfilter-item-link');
|
||
|
|
||
|
this._items.forEach((item, index) => from = (item._target === target) ? index : from);
|
||
|
this._target.querySelectorAll('nav [data-target] .tabfilter-item-link')
|
||
|
.forEach((elm, index) => to = (elm === target) ? index : to);
|
||
|
|
||
|
item_moved = this._items[from];
|
||
|
this._items.splice(from, 1);
|
||
|
this._items.splice(to, 0, item_moved);
|
||
|
|
||
|
// Tab order changed, update changes via ajax.
|
||
|
let value_str = this._items.map((item) => item._index).join(',');
|
||
|
|
||
|
this.profileUpdate('taborder', {
|
||
|
value_str: value_str
|
||
|
})
|
||
|
.then(() => {
|
||
|
this._items.forEach((item, index) => {
|
||
|
item._index = index;
|
||
|
});
|
||
|
});
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Event handler for 'Delete' button.
|
||
|
*
|
||
|
* @param {object} ev.detail.idx2 Index of deleted tab.
|
||
|
*/
|
||
|
deleteFilterTab: (ev) => {
|
||
|
this.delete(this._items[ev.detail.idx2]);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Event handler for 'Save as' button and on filter modal close.
|
||
|
*/
|
||
|
updateActiveFilterTab: (ev) => {
|
||
|
var item = this.getSelectedItem(),
|
||
|
params;
|
||
|
|
||
|
if (ev.detail.create == '1') {
|
||
|
item = this.create(item._target.parentNode.cloneNode(true), {});
|
||
|
}
|
||
|
|
||
|
item.update(ev.detail);
|
||
|
|
||
|
if (ev.detail.create == '1') {
|
||
|
// Allow to tab filter initialization code modify values of new created filter.
|
||
|
this.fire(TABFILTER_EVENT_NEWITEM, {item: item});
|
||
|
params = item.getFilterParams();
|
||
|
|
||
|
// Popup were created by 'Save as' button, reload page for simplicity.
|
||
|
this.profileUpdate('properties', {
|
||
|
'idx2': ev.detail.idx2,
|
||
|
'value_str': params.toString()
|
||
|
})
|
||
|
.then(() => {
|
||
|
item.setBrowserLocation(params);
|
||
|
window.location.reload(true);
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
params = item.getFilterParams();
|
||
|
this.setSelectedItem(item);
|
||
|
this.fire(TABFILTER_EVENT_UPDATE, {filter_property: 'properties'});
|
||
|
|
||
|
if (this._timeselector instanceof CTabFilterItem && this._timeselector._expanded
|
||
|
&& params.get('filter_custom_time') == 1) {
|
||
|
this._timeselector.fire(TABFILTERITEM_EVENT_COLLAPSE);
|
||
|
|
||
|
if (this._options.expanded) {
|
||
|
item.fire(TABFILTERITEM_EVENT_EXPAND);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Action on 'chevron left' button press. Select previous active tab filter.
|
||
|
*/
|
||
|
selectPrevTab: () => {
|
||
|
let index = this._items.indexOf(this._active_item);
|
||
|
|
||
|
if (index > 0) {
|
||
|
this._items[index - 1].fire(TABFILTERITEM_EVENT_SELECT);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Action on 'chevron right' button press. Select next active tab filter.
|
||
|
*/
|
||
|
selectNextTab: () => {
|
||
|
let index = this._items.indexOf(this._active_item);
|
||
|
|
||
|
if (index > -1 && index < this._items.length - 1 && this._items[index + 1] !== this._timeselector) {
|
||
|
this._items[index + 1].fire(TABFILTERITEM_EVENT_SELECT);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Action on 'chevron down' button press. Creates dropdown with list of existing tabs.
|
||
|
*/
|
||
|
toggleTabsList: (ev) => {
|
||
|
let dropdown_items = [],
|
||
|
dropdown = [{
|
||
|
items: [{
|
||
|
label: this._items[0]._target.getAttribute('aria-label'),
|
||
|
clickCallback: () => {
|
||
|
if (this._active_item !== this._items[0]) {
|
||
|
this._items[0].fire(TABFILTERITEM_EVENT_SELECT);
|
||
|
// Set selected item focus after popup menu will focus it opener element (used for ESC).
|
||
|
setTimeout(() => this._items[0].setFocused(), 0);
|
||
|
}
|
||
|
}
|
||
|
}]
|
||
|
}],
|
||
|
items = this._timeselector ? this._items.slice(1, -1) : this._items.slice(1);
|
||
|
|
||
|
if (items.length) {
|
||
|
for (const item of items) {
|
||
|
dropdown_items.push({
|
||
|
label: item._data.filter_name,
|
||
|
dataAttributes: (item._data.filter_show_counter && !item._unsaved)
|
||
|
? {'data-counter': item.getCounter()} : [],
|
||
|
clickCallback: () => {
|
||
|
if (this._active_item !== item) {
|
||
|
item.fire(TABFILTERITEM_EVENT_SELECT);
|
||
|
// Set selected item focus after popup menu will focus it opener element (used for ESC).
|
||
|
setTimeout(() => item.setFocused(), 0);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
dropdown.push({items: dropdown_items});
|
||
|
}
|
||
|
|
||
|
$(this._target).menuPopup(dropdown, new jQuery.Event(ev), {
|
||
|
position: {
|
||
|
of: ev.target,
|
||
|
my: 'left bottom',
|
||
|
at: 'left top'
|
||
|
}
|
||
|
});
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Action on 'Update' button press.
|
||
|
*/
|
||
|
buttonUpdateAction: () => {
|
||
|
var params = this._active_item.getFilterParams(false);
|
||
|
|
||
|
this.profileUpdate('properties', {
|
||
|
idx2: this._active_item._index,
|
||
|
value_str: params.toString()
|
||
|
})
|
||
|
.then(() => {
|
||
|
this._active_item.updateApplyUrl(false);
|
||
|
this._active_item.setBrowserLocation(params);
|
||
|
this._active_item.resetUnsavedState();
|
||
|
});
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Action on 'Save as' button press, open properties popup.
|
||
|
*/
|
||
|
buttonSaveAsAction: (ev) => {
|
||
|
this._active_item.openPropertiesDialog({
|
||
|
create: 1,
|
||
|
idx2: this._items.length,
|
||
|
support_custom_time: this._options.support_custom_time
|
||
|
}, ev.target);
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Action on 'Apply' button press.
|
||
|
*/
|
||
|
buttonApplyAction: () => {
|
||
|
this._active_item.unsetExpandedSubfilters();
|
||
|
this._active_item.emptySubfilter();
|
||
|
this._active_item.updateUnsavedState(false);
|
||
|
this._active_item.updateApplyUrl(false);
|
||
|
this._active_item.setBrowserLocationToApplyUrl();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Action on 'Reset' button press.
|
||
|
*/
|
||
|
buttonResetAction: () => {
|
||
|
let current_url = new Curl(),
|
||
|
url = new Curl('zabbix.php');
|
||
|
|
||
|
url.setArgument('action', current_url.getArgument('action'));
|
||
|
url.setArgument('filter_reset', 1);
|
||
|
window.location.href = url.getUrl();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Trigger TABFILTERITEM_EVENT_ACTION on active item passing clicked button name as action parameter.
|
||
|
*/
|
||
|
buttonActionNotify: (ev) => {
|
||
|
if (ev.target instanceof HTMLButtonElement && this._active_item instanceof CTabFilterItem) {
|
||
|
this._active_item.fire(TABFILTERITEM_EVENT_ACTION, {action: ev.target.getAttribute('name')});
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Keydown handler for keyboard navigation support.
|
||
|
*/
|
||
|
keydown: (ev) => {
|
||
|
if (ev.key !== 'ArrowLeft' && ev.key !== 'ArrowRight') {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
let path = ev.path || (ev.composedPath && ev.composedPath()),
|
||
|
focused_item = this._active_item,
|
||
|
index;
|
||
|
|
||
|
if (path && path.indexOf(this._target.querySelector('nav')) > -1) {
|
||
|
for (const item of this._items) {
|
||
|
if (item._target.parentNode.classList.contains(TABFILTERITEM_STYLE_FOCUSED)) {
|
||
|
focused_item = item;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
index = focused_item._index + ((ev.key === 'ArrowRight') ? 1 : -1);
|
||
|
|
||
|
if (this._items[index] instanceof CTabFilterItem && this._items[index] !== this._timeselector) {
|
||
|
this._items[index].setFocused();
|
||
|
}
|
||
|
|
||
|
cancelEvent(ev);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Scroll horizontally with mouse wheel handler for sortable items container.
|
||
|
*/
|
||
|
mouseWheelHandler: (container, ev) => {
|
||
|
if ((ev.deltaY < 0 && container.scrollLeft > 0)
|
||
|
|| (ev.deltaY > 0 && container.scrollLeft < container.scrollWidth - container.clientWidth)) {
|
||
|
container.scrollBy({left: ev.deltaY});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (const item of this._items) {
|
||
|
item
|
||
|
.on(TABFILTERITEM_EVENT_SELECT, this._events.select)
|
||
|
.on(TABFILTERITEM_EVENT_EXPAND, this._events.expand)
|
||
|
.on(TABFILTERITEM_EVENT_COLLAPSE, this._events.collapse)
|
||
|
.on(TABFILTERITEM_EVENT_URLSET, () => this.fire(TABFILTER_EVENT_URLSET));
|
||
|
}
|
||
|
|
||
|
$('.ui-sortable-container', this._target).sortable({
|
||
|
items: '.tabfilter-item-label:not(:first-child)',
|
||
|
update: this._events.tabSortChanged,
|
||
|
stop: (_, ui) => {
|
||
|
const $item = ui.item;
|
||
|
|
||
|
ui.item[0].classList.remove(TABFILTERITEM_STYLE_FOCUSED);
|
||
|
|
||
|
/**
|
||
|
* Remove inline style position, left and top that stay after sortable.
|
||
|
* This styles broken tabs layout.
|
||
|
*/
|
||
|
if ($item.css('position') === 'relative') {
|
||
|
$item.css({
|
||
|
'position': '',
|
||
|
'left': '',
|
||
|
'top': ''
|
||
|
});
|
||
|
}
|
||
|
},
|
||
|
axis: 'x',
|
||
|
containment: 'parent',
|
||
|
helper : 'clone'
|
||
|
});
|
||
|
|
||
|
const container = this._target.querySelector('.ui-sortable-container').parentNode;
|
||
|
|
||
|
try {
|
||
|
addEventListener('test', null, {get passive() {}});
|
||
|
container.addEventListener('wheel', ev => this._events.mouseWheelHandler(container, ev), {passive: true});
|
||
|
}
|
||
|
catch(e) {
|
||
|
container.addEventListener('wheel', ev => this._events.mouseWheelHandler(container, ev));
|
||
|
}
|
||
|
|
||
|
for (const action of this._target.querySelectorAll('nav [data-action]')) {
|
||
|
action.addEventListener('click', this._events[action.getAttribute('data-action')]);
|
||
|
}
|
||
|
|
||
|
this._filters_footer.querySelector('[name="filter_update"]')
|
||
|
.addEventListener('click', this._events.buttonUpdateAction);
|
||
|
this._filters_footer.querySelector('[name="filter_new"]')
|
||
|
.addEventListener('click', this._events.buttonSaveAsAction);
|
||
|
this._filters_footer.querySelector('[name="filter_apply"]')
|
||
|
.addEventListener('click', () => {
|
||
|
if (this._active_item._index == 0) {
|
||
|
this._events.buttonUpdateAction();
|
||
|
}
|
||
|
else {
|
||
|
this._events.buttonApplyAction();
|
||
|
}
|
||
|
});
|
||
|
this._filters_footer.querySelector('[name="filter_reset"]')
|
||
|
.addEventListener('click', this._events.buttonResetAction);
|
||
|
this._filters_footer.addEventListener('click', this._events.buttonActionNotify);
|
||
|
|
||
|
this.on('keydown', this._events.keydown);
|
||
|
this.on(TABFILTERITEM_EVENT_UPDATE, this._events.updateActiveFilterTab);
|
||
|
this.on(TABFILTERITEM_EVENT_DELETE, this._events.deleteFilterTab);
|
||
|
this.on('submit', (ev) => {
|
||
|
ev.preventDefault();
|
||
|
this._filters_footer.querySelector('[name="filter_apply"]').dispatchEvent(new CustomEvent('click'));
|
||
|
});
|
||
|
|
||
|
// Timeselector uses jQuery object as pub sub.
|
||
|
$.subscribe('timeselector.rangeupdate', (e, data) => {
|
||
|
Object.assign(this._timeselector._data, data);
|
||
|
this.updateTimeselector(this._active_item, false);
|
||
|
});
|
||
|
}
|
||
|
}
|