/* jshint esversion: 2024, module: true */
/**
* Interface Renderer for displaying network interfaces
* @class InterfaceRenderer
*/
class InterfaceRenderer {
/**
* @param {Object} elements - DOM elements
* @param {Object} state - Application state
*/
constructor(elements, state) {
this.elements = elements;
this.state = state;
}
/**
* Render interface tabs
* @method renderInterfaceTabs
* @param {Array} interfaces - Array of interface objects
*/
renderInterfaceTabs(interfaces) {
if (!interfaces.length) {
this.elements.outputs.ifaceTabs.innerHTML = '
No network interfaces found
';
this.elements.outputs.ifaceDetails.innerHTML = '';
return;
}
const tabsHTML = interfaces.map(iface => `
`).join('');
this.elements.outputs.ifaceTabs.innerHTML = `${tabsHTML}
`;
// Add event listeners to tabs
this.elements.outputs.ifaceTabs.querySelectorAll('.interface-tab').forEach(tab => {
tab.addEventListener('click', (event) => {
const ifaceName = event.currentTarget.dataset.interface;
const iface = interfaces.find(i => i.Name === ifaceName);
if (iface) {
this.showInterfaceDetails(iface);
}
});
});
}
/**
* Show detailed interface information with abbreviations
* @method showInterfaceDetails
* @param {Object} iface - Interface object
*/
showInterfaceDetails(iface) {
this.state.currentInterface = iface;
// Update active tab
this.elements.outputs.ifaceTabs.querySelectorAll('.interface-tab').forEach(tab => {
tab.classList.toggle('active', tab.dataset.interface === iface.Name);
});
const detailsHTML = `
${this.renderDetailRow('Link File', iface.LinkFile)}
${this.renderDetailRow('Network File', iface.NetworkFile)}
${this.renderDetailRow('State', iface.State, this.getStateClass(iface))}
${this.renderDetailRow('Online State', iface.OnlineState)}
${this.renderDetailRow('Type', iface.Type)}
${this.renderDetailRow('Path', iface.Path)}
${this.renderDetailRow('Driver', iface.Driver)}
${this.renderDetailRow('Vendor', iface.Vendor)}
${this.renderDetailRow('Model', iface.Model)}
${this.renderDetailRow('Hardware Address', this.arrayToMac(iface.HardwareAddress))}
${this.renderDetailRow('MTU', iface.MTU ? `${iface.MTU} (min: ${iface.MTUMin ?? '?'}, max: ${iface.MTUMax ?? '?'})` : '')}
${this.renderDetailRow('QDisc', iface.QDisc)}
${this.renderDetailRow('IPv6 Address Generation Mode', iface.IPv6AddressGenerationMode)}
${this.renderDetailRow('Number of Queues (Tx/Rx)', iface.Queues ? `${iface.Queues.Tx ?? '?'}/${iface.Queues.Rx ?? '?'}` : '')}
${this.renderDetailRow('Auto negotiation', iface.AutoNegotiation ? 'yes' : 'no')}
${this.renderDetailRow('Speed', iface.Speed)}
${this.renderDetailRow('Duplex', iface.Duplex)}
${this.renderDetailRow('Port', iface.Port)}
${this.renderDetailRow('Address', this.renderAddressList(iface.Addresses))}
${this.renderDetailRow('DNS', this.renderDNSServerList(iface.DNS))}
${this.renderDetailRow('NTP', iface.NTP)}
${this.renderDetailRow('Activation Policy', iface.ActivationPolicy)}
${this.renderDetailRow('Required For Online', iface.RequiredForOnline ? 'yes' : 'no')}
${this.renderDetailRow('Connected To', iface.ConnectedTo)}
${this.renderDetailRow('Offered DHCP leases', this.renderDHCPLeases(iface.DHCPLeases))}
`;
this.elements.outputs.ifaceDetails.innerHTML = detailsHTML;
}
/**
* Render a detail row with abbreviations
* @method renderDetailRow
* @param {string} label - Row label
* @param {string} value - Row value
* @param {string} [valueClass] - CSS class for value
* @returns {string} HTML string
*/
renderDetailRow(label, value, valueClass = '') {
if (!value) return '';
// Add abbreviations for common networking terms
const abbreviations = {
'MTU': 'Maximum Transmission Unit',
'QDisc': 'Queueing Discipline',
'Tx': 'Transmit',
'Rx': 'Receive',
'DNS': 'Domain Name System',
'NTP': 'Network Time Protocol',
'DHCP': 'Dynamic Host Configuration Protocol',
'MAC': 'Media Access Control',
'IP': 'Internet Protocol',
'IPv6': 'Internet Protocol version 6'
};
const abbrLabel = Object.keys(abbreviations).includes(label)
? `${label}`
: label;
return `
${abbrLabel}:
${value}
`;
}
/**
* Render address list
* @method renderAddressList
* @param {Array} addresses - Array of addresses
* @returns {string} Formatted addresses
*/
renderAddressList(addresses) {
if (!addresses?.length) return '';
return addresses.map(addr => {
const ip = this.ipFromArray(addr);
return ip ? `${ip}
` : '';
}).join('');
}
/**
* Render DNS server list
* @method renderDNSServerList
* @param {Array} dnsServers - Array of DNS servers
* @returns {string} Formatted DNS servers
*/
renderDNSServerList(dnsServers) {
if (!dnsServers?.length) return '';
return dnsServers.map(dns => {
const server = this.ipFromArray(dns.Address ?? dns);
return server ? `${server}
` : '';
}).join('');
}
/**
* Render DHCP leases
* @method renderDHCPLeases
* @param {Array} leases - Array of DHCP leases
* @returns {string} Formatted leases
*/
renderDHCPLeases(leases) {
if (!leases?.length) return '';
return leases.map(lease => {
const ip = lease.IP ?? lease;
const to = lease.To ?? lease.MAC ?? '';
return `${ip} (to ${to})
`;
}).join('');
}
/**
* Get CSS class for interface state
* @method getStateClass
* @param {Object} iface - Interface object
* @returns {string} CSS class
*/
getStateClass(iface) {
const state = iface.OperationalState ?? iface.AdministrativeState ?? iface.State ?? '';
return state.toLowerCase().includes('up') ||
state.toLowerCase().includes('routable') ||
state.toLowerCase().includes('configured') ? 'state-up' : 'state-down';
}
/**
* Get display text for interface state
* @method getStateText
* @param {Object} iface - Interface object
* @returns {string} State text
*/
getStateText(iface) {
return iface.OperationalState ?? iface.AdministrativeState ?? iface.State ?? 'unknown';
}
/**
* Convert byte array to MAC address
* @method arrayToMac
* @param {Array} bytes - Byte array
* @returns {string} MAC address
*/
arrayToMac(bytes) {
if (!Array.isArray(bytes)) return '';
return bytes.map(byte => byte.toString(16).padStart(2, '0')).join(':');
}
/**
* Convert byte array to IP address
* @method ipFromArray
* @param {Array|Object} obj - IP data
* @returns {string} IP address
*/
ipFromArray(obj) {
let bytes = null;
if (Array.isArray(obj)) {
bytes = obj;
} else if (obj?.Address && Array.isArray(obj.Address)) {
bytes = obj.Address;
} else {
return '';
}
// IPv4
if (bytes.length === 4) {
return bytes.join('.');
}
// IPv6
if (bytes.length === 16) {
const parts = [];
for (let i = 0; i < 16; i += 2) {
parts.push(((bytes[i] << 8) | bytes[i + 1]).toString(16));
}
return parts.join(':').replace(/(^|:)0+/g, '$1').replace(/:{3,}/, '::');
}
return '';
}
}
export { InterfaceRenderer };