/* 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 };