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.
zabbix/ui/js/hostinterfacemanager.js

529 lines
16 KiB

/*
** 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.
**/
class HostInterfaceManager {
static INTERFACE_TYPE_AGENT = 1;
static INTERFACE_TYPE_SNMP = 2;
static INTERFACE_TYPE_IPMI = 3;
static INTERFACE_TYPE_JMX = 4;
static SNMP_V1 = 1;
static SNMP_V2C = 2;
static SNMP_V3 = 3;
static SNMP_BULK_ENABLED = 1;
static INTERFACE_SECONDARY = 0;
static INTERFACE_PRIMARY = 1;
static INTERFACE_USE_IP = 1;
static ITEM_SNMPV3_SECURITYLEVEL_NOAUTHNOPRIV = 0;
static ITEM_SNMPV3_SECURITYLEVEL_AUTHNOPRIV = 1;
static ITEM_SNMPV3_SECURITYLEVEL_AUTHPRIV = 2;
static ITEM_SNMPV3_AUTHPROTOCOL_MD5 = 0;
static ITEM_SNMPV3_PRIVPROTOCOL_DES = 0;
static ZBX_STYLE_HOST_INTERFACE_BTN_MAIN_INTERFACE = 'interface-btn-main-interface';
static ZBX_STYLE_HOST_INTERFACE_BTN_REMOVE = 'interface-btn-remove';
static ZBX_STYLE_HOST_INTERFACE_BTN_TOGGLE = 'interface-btn-toggle';
static ZBX_STYLE_HOST_INTERFACE_CELL_DETAILS = 'interface-cell-details';
static ZBX_STYLE_HOST_INTERFACE_CELL_USEIP = 'interface-cell-useip';
static ZBX_STYLE_HOST_INTERFACE_CONTAINER = 'interface-container';
static ZBX_STYLE_HOST_INTERFACE_CONTAINER_HEADER = 'interface-container-header';
static ZBX_STYLE_HOST_INTERFACE_INPUT_EXPAND = 'interface-input-expand';
static ZBX_STYLE_HOST_INTERFACE_ROW = 'interface-row';
static ZBX_STYLE_HOST_NO_INTERFACE = 'no-interface';
static ZBX_STYLE_LIST_ACCORDION_ITEM = 'list-accordion-item';
static ZBX_STYLE_LIST_ACCORDION_ITEM_OPENED = 'list-accordion-item-opened';
constructor(data, host_interface_row_tmpl) {
// Constants.
this.TEMPLATE = new Template(host_interface_row_tmpl);
this.DEFAULT_PORTS = {
agent: 10050,
snmp: 161,
jmx: 12345,
ipmi: 623
};
this.CONTAINER_IDS = {
[HostInterfaceManager.INTERFACE_TYPE_AGENT]: '#agentInterfaces',
[HostInterfaceManager.INTERFACE_TYPE_SNMP]: '#SNMPInterfaces',
[HostInterfaceManager.INTERFACE_TYPE_JMX]: '#JMXInterfaces',
[HostInterfaceManager.INTERFACE_TYPE_IPMI]: '#IPMIInterfaces'
};
this.INTERFACE_TYPES = {
'agent': HostInterfaceManager.INTERFACE_TYPE_AGENT,
'snmp': HostInterfaceManager.INTERFACE_TYPE_SNMP,
'jmx': HostInterfaceManager.INTERFACE_TYPE_JMX,
'ipmi': HostInterfaceManager.INTERFACE_TYPE_IPMI
};
this.INTERFACE_NAMES = {
[HostInterfaceManager.INTERFACE_TYPE_AGENT]: t('Agent'),
[HostInterfaceManager.INTERFACE_TYPE_SNMP]: t('SNMP'),
[HostInterfaceManager.INTERFACE_TYPE_JMX]: t('JMX'),
[HostInterfaceManager.INTERFACE_TYPE_IPMI]: t('IPMI')
};
this.allow_empty_message = true;
this.$noInterfacesMsg = jQuery('<div>', {
html: t('No interfaces are defined.'),
class: HostInterfaceManager.ZBX_STYLE_HOST_NO_INTERFACE
})
.insertAfter(jQuery('.' + HostInterfaceManager.ZBX_STYLE_HOST_INTERFACE_CONTAINER_HEADER));
this.interfaces = {};
this.data = data;
}
/**
* Setter for interface store.
*
* @param object new_data
*/
set data(new_data) {
if (typeof new_data !== 'object') {
throw new Error('Incorrect data.');
}
Object
.entries(new_data)
.forEach(([_, value]) => {
if (!('interfaceid' in value)) {
value.interfaceid = this.generateId();
}
this.interfaces[value.interfaceid] = value;
});
}
/**
* Getter for interface store.
*/
get data() {
return this.interfaces;
}
setAllowEmptyMessage(value) {
this.allow_empty_message = value;
}
setSnmpFields(elem, iface) {
if (iface.type != HostInterfaceManager.INTERFACE_TYPE_SNMP) {
return elem
.querySelector('.' + HostInterfaceManager.ZBX_STYLE_HOST_INTERFACE_CELL_DETAILS)
.remove();
}
elem
.querySelector(`#interfaces_${iface.interfaceid}_details_version`)
.value = iface.details.version;
if (iface.details.securitylevel !== undefined) {
elem
.querySelector(`#interfaces_${iface.interfaceid}_details_securitylevel`)
.value = iface.details.securitylevel;
}
if (iface.details.privprotocol !== undefined) {
elem.querySelector(`[name="interfaces[${iface.interfaceid}][details][privprotocol]"]`).value
= iface.details.privprotocol;
}
if (iface.details.authprotocol !== undefined) {
elem.querySelector(`[name="interfaces[${iface.interfaceid}][details][authprotocol]"]`).value
= iface.details.authprotocol;
}
if (iface.details.bulk == HostInterfaceManager.SNMP_BULK_ENABLED) {
elem
.querySelector(`#interfaces_${iface.interfaceid}_details_bulk`)
.checked = true;
}
new CViewSwitcher(`interfaces_${iface.interfaceid}_details_version`, 'change',
{
[HostInterfaceManager.SNMP_V1]: [
`snmp_community_label_${iface.interfaceid}`,
`snmp_community_field_${iface.interfaceid}`
],
[HostInterfaceManager.SNMP_V2C]: [
`snmp_community_label_${iface.interfaceid}`,
`snmp_community_field_${iface.interfaceid}`,
`snmp_repetition_count_label_${iface.interfaceid}`,
`snmp_repetition_count_field_${iface.interfaceid}`
],
[HostInterfaceManager.SNMP_V3]: [
`snmpv3_contextname_label_${iface.interfaceid}`,
`snmpv3_contextname_field_${iface.interfaceid}`,
`snmpv3_securityname_label_${iface.interfaceid}`,
`snmpv3_securityname_field_${iface.interfaceid}`,
`snmpv3_securitylevel_label_${iface.interfaceid}`,
`snmpv3_securitylevel_field_${iface.interfaceid}`,
`snmpv3_authprotocol_label_${iface.interfaceid}`,
`snmpv3_authprotocol_field_${iface.interfaceid}`,
`snmpv3_authpassphrase_label_${iface.interfaceid}`,
`snmpv3_authpassphrase_field_${iface.interfaceid}`,
`snmpv3_privprotocol_label_${iface.interfaceid}`,
`snmpv3_privprotocol_field_${iface.interfaceid}`,
`snmpv3_privpassphrase_label_${iface.interfaceid}`,
`snmpv3_privpassphrase_field_${iface.interfaceid}`,
`snmp_repetition_count_label_${iface.interfaceid}`,
`snmp_repetition_count_field_${iface.interfaceid}`
]
}
);
jQuery(`#interfaces_${iface.interfaceid}_details_version`).on('change', (e) => {
jQuery(`#interfaces_${iface.interfaceid}_details_securitylevel`).off('change');
if (e.target.value == HostInterfaceManager.SNMP_V3) {
new CViewSwitcher(`interfaces_${iface.interfaceid}_details_securitylevel`, 'change',
{
[HostInterfaceManager.ITEM_SNMPV3_SECURITYLEVEL_NOAUTHNOPRIV]: [],
[HostInterfaceManager.ITEM_SNMPV3_SECURITYLEVEL_AUTHNOPRIV]: [
`snmpv3_authprotocol_label_${iface.interfaceid}`,
`snmpv3_authprotocol_field_${iface.interfaceid}`,
`snmpv3_authpassphrase_label_${iface.interfaceid}`,
`snmpv3_authpassphrase_field_${iface.interfaceid}`
],
[HostInterfaceManager.ITEM_SNMPV3_SECURITYLEVEL_AUTHPRIV]: [
`snmpv3_authprotocol_label_${iface.interfaceid}`,
`snmpv3_authprotocol_field_${iface.interfaceid}`,
`snmpv3_authpassphrase_label_${iface.interfaceid}`,
`snmpv3_authpassphrase_field_${iface.interfaceid}`,
`snmpv3_privprotocol_label_${iface.interfaceid}`,
`snmpv3_privprotocol_field_${iface.interfaceid}`,
`snmpv3_privpassphrase_label_${iface.interfaceid}`,
`snmpv3_privpassphrase_field_${iface.interfaceid}`
]
}
);
}
}).trigger('change');
}
generateId() {
const keys = Object.keys(this.data).map(Number);
const max_key = Math.max(0, ...keys);
return max_key+1;
}
getNewData(type) {
return {
interfaceid: this.generateId(),
isNew: true,
useip: 1,
type: this.INTERFACE_TYPES[type],
type_name: this.INTERFACE_NAMES[this.INTERFACE_TYPES[type]],
port: this.DEFAULT_PORTS[type],
ip: '127.0.0.1',
main: '0',
details: {
version: HostInterfaceManager.SNMP_V2C,
community: '{$SNMP_COMMUNITY}',
max_repetitions: 10,
bulk: HostInterfaceManager.SNMP_BULK_ENABLED,
securitylevel: HostInterfaceManager.ITEM_SNMPV3_SECURITYLEVEL_NOAUTHNOPRIV,
authprotocol: HostInterfaceManager.ITEM_SNMPV3_AUTHPROTOCOL_MD5,
privprotocol: HostInterfaceManager.ITEM_SNMPV3_PRIVPROTOCOL_DES
}
};
}
getInterfaces() {
let types = {
[HostInterfaceManager.INTERFACE_TYPE_AGENT]: {main: null, all: []},
[HostInterfaceManager.INTERFACE_TYPE_SNMP]: {main: null, all: []},
[HostInterfaceManager.INTERFACE_TYPE_JMX]: {main: null, all: []},
[HostInterfaceManager.INTERFACE_TYPE_IPMI]: {main: null, all: []}
};
Object
.entries(this.data)
.forEach(([_, value]) => {
types[value.type].all.push(value.interfaceid);
if (value.main == HostInterfaceManager.INTERFACE_PRIMARY) {
if (types[value.type].main !== null) {
throw new Error('Multiple default interfaces for same type.');
}
types[value.type].main = value.interfaceid;
}
});
return types;
}
renderRow(iface) {
const container = document.querySelector(this.CONTAINER_IDS[iface.type]);
const disabled = (typeof iface.items !== 'undefined' && iface.items > 0);
iface.type_name = this.INTERFACE_NAMES[iface.type];
/*
* New line break css selector :empty. Trim used to avoid this.
* Template added with new line. Because template <script> tag it contain for code readability.
*/
container.insertAdjacentHTML('beforeend', this.TEMPLATE.evaluate({iface: iface}).trim());
const elem = document.getElementById(`interface_row_${iface.interfaceid}`);
// Select proper use ip radio element.
elem
.querySelector(`#interfaces_${iface.interfaceid}_useip_${iface.useip}`)
.checked = true;
if (disabled) {
elem
.querySelector('.' + HostInterfaceManager.ZBX_STYLE_HOST_INTERFACE_BTN_REMOVE)
.disabled = true;
}
this.setSnmpFields(elem, iface);
// Set onclick actions.
elem
.querySelector('.' + HostInterfaceManager.ZBX_STYLE_HOST_INTERFACE_BTN_REMOVE)
.addEventListener('click', () => this.removeById(iface.interfaceid));
elem
.querySelector('.' + HostInterfaceManager.ZBX_STYLE_HOST_INTERFACE_BTN_MAIN_INTERFACE)
.addEventListener('click', () => this.setMainInterfaceById(iface.interfaceid));
[...elem.querySelectorAll('.' + HostInterfaceManager.ZBX_STYLE_HOST_INTERFACE_CELL_USEIP + ' input')].map(
(el) => el.addEventListener('click', (event) => this.setUseIp(elem, event.currentTarget.value))
);
return true;
}
removeById(id) {
const elem = document.getElementById(`interface_row_${id}`);
if (!elem) {
return false;
}
elem.remove();
delete this.data[id];
this.resetMainInterfaces();
this.renderLayout();
return true;
}
createRowByTypeName(type) {
const new_data = this.getNewData(type);
let data = {};
data[new_data.interfaceid] = new_data;
this.data = data;
this.renderRow(new_data);
this.resetMainInterfaces();
this.renderLayout();
if (new_data.type == HostInterfaceManager.INTERFACE_TYPE_SNMP) {
const elem = document.getElementById(`interface_row_${new_data.interfaceid}`);
const index = [...elem.parentElement.children].indexOf(elem)
jQuery(this.CONTAINER_IDS[HostInterfaceManager.INTERFACE_TYPE_SNMP])
.zbx_vertical_accordion('expandNth', index);
}
return true;
}
resetMainInterfaces() {
const interfaces = this.getInterfaces();
for (let type in interfaces) {
if (!interfaces.hasOwnProperty(type)) {
continue;
}
let type_interfaces = interfaces[type];
if (!type_interfaces.main && type_interfaces.all.length) {
for (let i = 0; i < type_interfaces.all.length; i++) {
if (this.data[type_interfaces.all[i]].main == HostInterfaceManager.INTERFACE_PRIMARY) {
interfaces[type].main = this.data[type_interfaces.all[i]].interfaceid;
}
}
if (!type_interfaces.main) {
type_interfaces.main = type_interfaces.all[0];
this.data[type_interfaces.main].main = HostInterfaceManager.INTERFACE_PRIMARY;
}
}
}
for (let type in interfaces) {
if (interfaces.hasOwnProperty(type)) {
let type_interfaces = interfaces[type];
if (type_interfaces.main) {
document
.getElementById(`interface_main_${type_interfaces.main}`)
.checked = true;
}
}
}
return true;
}
setMainInterfaceById(id) {
const interfaces = this.getInterfaces();
const type = this.data[id].type;
const old = interfaces[type].main;
if (id != old) {
this.data[id].main = HostInterfaceManager.INTERFACE_PRIMARY;
this.data[old].main = HostInterfaceManager.INTERFACE_SECONDARY;
}
return true;
}
setUseIp(elem, use_ip) {
const interfaceid = elem.dataset.interfaceid;
this.data[interfaceid].useip = use_ip;
[...elem.querySelectorAll('input[name$="[ip]"], input[name$="[dns]"]')].map((el) =>
el.removeAttribute('aria-required')
);
elem
.querySelector((use_ip == HostInterfaceManager.INTERFACE_USE_IP) ? '[name$="[ip]"]' : '[name$="[dns]"]')
.setAttribute('aria-required', true);
return true;
}
addAgent() {
this.createRowByTypeName('agent');
}
addSnmp() {
this.createRowByTypeName('snmp');
}
addJmx() {
this.createRowByTypeName('jmx');
}
addIpmi() {
this.createRowByTypeName('ipmi');
}
render() {
for (let i in this.data) {
if (this.data.hasOwnProperty(i)) {
this.renderRow(this.data[i]);
}
}
this.resetMainInterfaces();
this.renderLayout();
// Add accordion functionality to SNMP interfaces.
jQuery(this.CONTAINER_IDS[HostInterfaceManager.INTERFACE_TYPE_SNMP])
.zbx_vertical_accordion({handler: '.' + HostInterfaceManager.ZBX_STYLE_HOST_INTERFACE_BTN_TOGGLE});
// Add event to expand SNMP interface accordion if focused or clicked on inputs.
jQuery(this.CONTAINER_IDS[HostInterfaceManager.INTERFACE_TYPE_SNMP])
.on("focus", "." + HostInterfaceManager.ZBX_STYLE_LIST_ACCORDION_ITEM + ":not(." + HostInterfaceManager.ZBX_STYLE_LIST_ACCORDION_ITEM_OPENED + ") ." + HostInterfaceManager.ZBX_STYLE_HOST_INTERFACE_INPUT_EXPAND, (event) => {
const index = jQuery(event.currentTarget)
.closest('.' + HostInterfaceManager.ZBX_STYLE_LIST_ACCORDION_ITEM)
.index();
jQuery(this.CONTAINER_IDS[HostInterfaceManager.INTERFACE_TYPE_SNMP])
.zbx_vertical_accordion("expandNth", index);
});
return true;
}
renderLayout() {
if (Object.keys(this.data).length > 0) {
jQuery('.' + HostInterfaceManager.ZBX_STYLE_HOST_INTERFACE_CONTAINER).show();
this.$noInterfacesMsg.hide();
}
else {
jQuery('.' + HostInterfaceManager.ZBX_STYLE_HOST_INTERFACE_CONTAINER).hide();
this.$noInterfacesMsg.toggle(this.allow_empty_message);
}
}
/**
* Converts form field to readonly.
*
* @param {Element} el Native JavaScript element for form field.
*/
setReadonly(el) {
const tag_name = el.tagName;
if (tag_name === 'INPUT') {
const type = el.getAttribute('type');
switch (type) {
case 'text':
el.readOnly = true;
break;
case 'radio':
case 'checkbox':
const {checked, name, value} = el;
el.disabled = true;
if (checked) {
const input = document.createElement('input');
input.type = 'hidden';
input.name = name;
input.value = value;
el.insertAdjacentElement('beforebegin', input);
}
break;
}
}
else if (tag_name === 'Z-SELECT') {
el.readOnly = true;
}
}
makeReadonly() {
[...document.querySelectorAll('.' + HostInterfaceManager.ZBX_STYLE_HOST_INTERFACE_ROW)].forEach((row) => {
[...row.querySelectorAll('input, z-select')].map((el) => {
this.setReadonly(el);
});
[...row.querySelectorAll('.' + HostInterfaceManager.ZBX_STYLE_HOST_INTERFACE_BTN_REMOVE)]
.map((el) => el.remove());
});
return true;
}
}