From b0c76dcc159ead3d67314da3a71d60bad9385991 Mon Sep 17 00:00:00 2001 From: Petri Hienonen Date: Sun, 28 Sep 2025 13:41:46 +0300 Subject: Split the system --- static/interface-renderer.js | 250 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 static/interface-renderer.js (limited to 'static/interface-renderer.js') diff --git a/static/interface-renderer.js b/static/interface-renderer.js new file mode 100644 index 0000000..93364c8 --- /dev/null +++ b/static/interface-renderer.js @@ -0,0 +1,250 @@ +/* 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 }; -- cgit v1.2.3-70-g09d2