diff options
| author | Petri Hienonen <petri.hienonen@gmail.com> | 2025-09-28 21:23:14 +0300 |
|---|---|---|
| committer | Petri Hienonen <petri.hienonen@gmail.com> | 2025-09-28 21:23:14 +0300 |
| commit | df83a2861130d4e07b0a720a3452f10cbc4bc84b (patch) | |
| tree | 901b84a33714d7b7057c294e0c1c91399cb6d3bf | |
| parent | 01fa523251680e9708c0301ee09f83f472f03e06 (diff) | |
| download | network-df83a2861130d4e07b0a720a3452f10cbc4bc84b.tar.zst | |
Complex
| -rw-r--r-- | static/interface-renderer.js | 150 | ||||
| -rw-r--r-- | static/utils.js | 37 |
2 files changed, 168 insertions, 19 deletions
diff --git a/static/interface-renderer.js b/static/interface-renderer.js index 5145cf3..d3478be 100644 --- a/static/interface-renderer.js +++ b/static/interface-renderer.js @@ -1,6 +1,7 @@ /* jshint esversion: 2024, module: true */ import { Utils } from "./utils.js"; +import { InterfaceState } from "./enums.js"; /** * Static Interface Renderer for displaying network interfaces @@ -27,7 +28,7 @@ class InterfaceRenderer { const isActive = iface === currentInterface; return ` - <button class="interface-tab ${isActive ? "active" : ""}" + <button class="interface-tab ${isActive ? "active" : ""}" data-interface="${Utils.sanitizeHTML(iface.Name)}"> ${Utils.sanitizeHTML(iface.Name)} <span class="interface-state ${stateClass}">${Utils.sanitizeHTML(stateText)}</span> @@ -96,7 +97,10 @@ class InterfaceRenderer { "DNS", InterfaceRenderer.renderDNSServerList(iface.DNS), ), - InterfaceRenderer.renderDetailRow("NTP", iface.NTP), + InterfaceRenderer.renderDetailRow( + "NTP", + InterfaceRenderer.renderNTPServerList(iface.NTP), + ), InterfaceRenderer.renderDetailRow( "Activation Policy", iface.ActivationPolicy, @@ -126,7 +130,7 @@ class InterfaceRenderer { * @returns {string} HTML string */ static renderDetailRow(label, value, valueClass = "") { - if (!value) return ""; + if (!value && value !== 0 && value !== false) return ""; const abbreviations = { MTU: "Maximum Transmission Unit", @@ -145,15 +149,74 @@ class InterfaceRenderer { ? `<abbr title="${abbreviations[label]}">${label}</abbr>` : label; + // Handle different value types + let displayValue; + if (typeof value === "string") { + displayValue = Utils.sanitizeHTML(value); + } else if (typeof value === "object") { + // Handle object rendering + displayValue = InterfaceRenderer.renderObjectValue(value); + } else { + displayValue = Utils.sanitizeHTML(String(value)); + } + return ` <div class="detail-row"> <span class="detail-label">${abbrLabel}:</span> - <span class="detail-value ${valueClass}">${Utils.sanitizeHTML(value)}</span> + <span class="detail-value ${valueClass}">${displayValue}</span> </div> `; } /** + * Render object value for display + * @static + * @param {Object} obj - Object to render + * @returns {string} HTML string + */ + static renderObjectValue(obj) { + if (obj === null || obj === undefined) return ""; + + // Handle array of objects + if (Array.isArray(obj)) { + return obj + .map((item) => InterfaceRenderer.renderObjectValue(item)) + .join(", "); + } + + // Handle plain object + if (typeof obj === "object") { + // Check if it's an IP address object + if (obj.Address && Array.isArray(obj.Address)) { + return Utils.ipFromArray(obj); + } + + // Check if it's a simple key-value object we can stringify + try { + const simpleObj = {}; + for (const [key, value] of Object.entries(obj)) { + if ( + value !== null && + value !== undefined && + typeof value !== "object" + ) { + simpleObj[key] = value; + } + } + if (Object.keys(simpleObj).length > 0) { + return Utils.sanitizeHTML(JSON.stringify(simpleObj)); + } + } catch (e) { + // Fall through to default handling + } + + return Utils.sanitizeHTML(String(obj)); + } + + return Utils.sanitizeHTML(String(obj)); + } + + /** * Render address list * @static * @param {Array} addresses - Array of addresses @@ -162,14 +225,15 @@ class InterfaceRenderer { static renderAddressList(addresses) { if (!addresses?.length) return ""; - return addresses + const addressItems = addresses .map((addr) => { const ip = Utils.ipFromArray(addr); - return ip - ? `<div class="address-item">${Utils.sanitizeHTML(ip)}</div>` - : ""; + return ip ? Utils.sanitizeHTML(ip) : ""; }) - .join(""); + .filter((ip) => ip) + .join(", "); + + return addressItems || ""; } /** @@ -181,14 +245,47 @@ class InterfaceRenderer { static renderDNSServerList(dnsServers) { if (!dnsServers?.length) return ""; - return dnsServers + const dnsItems = dnsServers .map((dns) => { const server = Utils.ipFromArray(dns.Address ?? dns); - return server - ? `<div class="dns-item">${Utils.sanitizeHTML(server)}</div>` - : ""; + return server ? Utils.sanitizeHTML(server) : ""; }) - .join(""); + .filter((server) => server) + .join(", "); + + return dnsItems || ""; + } + + /** + * Render NTP server list + * @static + * @param {Array} ntpServers - Array of NTP servers + * @returns {string} Formatted NTP servers + */ + static renderNTPServerList(ntpServers) { + if (!ntpServers?.length) return ""; + + const ntpItems = ntpServers + .map((ntp) => { + if (typeof ntp === "string") { + return Utils.sanitizeHTML(ntp); + } else if (ntp && typeof ntp === "object") { + // Handle NTP server object + if (ntp.Address && Array.isArray(ntp.Address)) { + return Utils.ipFromArray(ntp); + } else if (ntp.Server) { + return Utils.sanitizeHTML(ntp.Server); + } else { + // Try to extract any stringifiable value + return InterfaceRenderer.renderObjectValue(ntp); + } + } + return ""; + }) + .filter((server) => server) + .join(", "); + + return ntpItems || ""; } /** @@ -200,13 +297,28 @@ class InterfaceRenderer { static renderDHCPLeases(leases) { if (!leases?.length) return ""; - return leases + const leaseItems = leases .map((lease) => { - const ip = lease.IP ?? lease; - const to = lease.To ?? lease.MAC ?? ""; - return `<div class="lease-item">${Utils.sanitizeHTML(ip)} (to ${Utils.sanitizeHTML(to)})</div>`; + if (typeof lease === "string") { + return Utils.sanitizeHTML(lease); + } else if (lease && typeof lease === "object") { + const ip = lease.IP ?? lease.Address ?? lease; + const to = lease.To ?? lease.MAC ?? lease.ClientIdentifier ?? ""; + + if (ip && to) { + return `${Utils.sanitizeHTML(String(ip))} (to ${Utils.sanitizeHTML(String(to))})`; + } else if (ip) { + return Utils.sanitizeHTML(String(ip)); + } else { + return InterfaceRenderer.renderObjectValue(lease); + } + } + return ""; }) - .join(""); + .filter((lease) => lease) + .join(", "); + + return leaseItems || ""; } } diff --git a/static/utils.js b/static/utils.js index 32b10be..931653f 100644 --- a/static/utils.js +++ b/static/utils.js @@ -168,6 +168,43 @@ class Utils { timeout = setTimeout(later, wait); }; } + /** + * Safely convert any value to display string + * @static + * @param {*} value - Any value + * @returns {string} Safe display string + */ + static safeToString(value) { + if (value === null || value === undefined) return ""; + if (typeof value === "string") return value; + if (typeof value === "number" || typeof value === "boolean") + return String(value); + if (Array.isArray(value)) { + return value.map((item) => Utils.safeToString(item)).join(", "); + } + if (typeof value === "object") { + // Handle IP address objects + if (value.Address && Array.isArray(value.Address)) { + return Utils.ipFromArray(value); + } + // Try to stringify simple objects + try { + const simpleObj = {}; + for (const [key, val] of Object.entries(value)) { + if (val !== null && val !== undefined && typeof val !== "object") { + simpleObj[key] = val; + } + } + if (Object.keys(simpleObj).length > 0) { + return JSON.stringify(simpleObj); + } + } catch (e) { + // Fall through + } + return String(value); + } + return String(value); + } } export { Utils }; |
