diff options
Diffstat (limited to 'static/systemd-network.js')
| -rw-r--r-- | static/systemd-network.js | 416 |
1 files changed, 244 insertions, 172 deletions
diff --git a/static/systemd-network.js b/static/systemd-network.js index 69020f1..16c079c 100644 --- a/static/systemd-network.js +++ b/static/systemd-network.js @@ -1,21 +1,23 @@ /* jshint esversion: 2024, module: true */ import { - BooleanYesNo, - ClientIdentifier, - DHCPMode, - DNSSECOptions, - DuplexMode, + SectionName, FieldType, + DHCPMode, IPForward, IPv6PrivacyExtensions, LLMNROptions, MulticastDNS, - PortType, + DNSSECOptions, + UseDomains, + ClientIdentifier, RouteScope, RouteType, - UseDomains, WakeOnLAN, + PortType, + DuplexMode, + SLAACOptions, + IPv6SendRAOptions, } from "./network-types.js"; /** @@ -99,6 +101,17 @@ class BaseField { return true; } + // Handle boolean conversion for yes/no strings + if (this.#type === FieldType.BOOLEAN && typeof this.#value === "string") { + const normalized = this.#value.toLowerCase(); + return ( + normalized === "yes" || + normalized === "no" || + normalized === "true" || + normalized === "false" + ); + } + if (this.#options.pattern && typeof this.#value === "string") { return this.#options.pattern.test(this.#value); } @@ -115,7 +128,60 @@ class BaseField { * @returns {string} */ toString() { - return this.#value !== null ? String(this.#value) : ""; + if (this.#value === null || this.#value === undefined) { + return ""; + } + + // Handle boolean conversion + if (this.#type === FieldType.BOOLEAN) { + if (typeof this.#value === "boolean") { + return this.#value ? "yes" : "no"; + } + if (typeof this.#value === "string") { + const normalized = this.#value.toLowerCase(); + if (normalized === "true" || normalized === "false") { + return normalized === "true" ? "yes" : "no"; + } + return normalized; // already yes/no + } + } + + return String(this.#value); + } + + /** + * Get boolean value + * @returns {boolean|null} + */ + getBooleanValue() { + if (this.#type !== FieldType.BOOLEAN) { + return null; + } + + if (this.#value === null || this.#value === undefined) { + return null; + } + + if (typeof this.#value === "boolean") { + return this.#value; + } + + if (typeof this.#value === "string") { + const normalized = this.#value.toLowerCase(); + return normalized === "yes" || normalized === "true"; + } + + return Boolean(this.#value); + } + + /** + * Set boolean value + * @param {boolean} boolValue + */ + setBooleanValue(boolValue) { + if (this.#type === FieldType.BOOLEAN) { + this.#value = boolValue; + } } /** @@ -166,6 +232,16 @@ class IPv6Address extends BaseField { } /** + * Boolean type with yes/no values + * @class BooleanField + */ +class BooleanField extends BaseField { + constructor(value = null) { + super(value, FieldType.BOOLEAN, "Boolean (yes/no)"); + } +} + +/** * Port type * @class Port */ @@ -250,7 +326,7 @@ class LinkSection { this.Duplex = new BaseField(null, FieldType.STRING, "Duplex mode", { enum: Object.values(DuplexMode), }); - this.AutoNegotiation = new BooleanYesNo(); + this.AutoNegotiation = new BooleanField(); this.WakeOnLan = new BaseField(null, FieldType.STRING, "Wake-on-LAN", { enum: Object.values(WakeOnLAN), }); @@ -262,8 +338,8 @@ class LinkSection { FieldType.STRINGS, "Advertised features", ); - this.RxFlowControl = new BooleanYesNo(); - this.TxFlowControl = new BooleanYesNo(); + this.RxFlowControl = new BooleanField(); + this.TxFlowControl = new BooleanField(); } } @@ -281,7 +357,7 @@ class NetworkSection { this.DHCP = new BaseField(null, FieldType.DHCP_MODE, "DHCP client", { enum: Object.values(DHCPMode), }); - this.DHCPServer = new BooleanYesNo(); + this.DHCPServer = new BooleanField(); this.DNS = new BaseField(null, FieldType.IP_ADDRESSES, "DNS servers"); this.NTP = new BaseField(null, FieldType.IP_ADDRESSES, "NTP servers"); this.IPForward = new BaseField( @@ -300,7 +376,7 @@ class NetworkSection { enum: Object.values(IPv6PrivacyExtensions), }, ); - this.IPv6AcceptRA = new BooleanYesNo(); + this.IPv6AcceptRA = new BooleanField(); this.LLMNR = new BaseField(null, FieldType.LLMNR, "LLMNR support", { enum: Object.values(LLMNROptions), }); @@ -311,15 +387,16 @@ class NetworkSection { enum: Object.values(DNSSECOptions), }); this.Domains = new BaseField(null, FieldType.STRINGS, "DNS search domains"); - this.ConfigureWithoutCarrier = new BooleanYesNo(); - this.IgnoreCarrierLoss = new BooleanYesNo(); + this.ConfigureWithoutCarrier = new BooleanField(); + this.IgnoreCarrierLoss = new BooleanField(); this.KeepConfiguration = new BaseField( null, FieldType.NUMBER, "Keep configuration time in seconds", ); - this.LLDP = new BooleanYesNo(); - this.EmitLLDP = new BooleanYesNo(); + this.LLDP = new BooleanField(); + this.EmitLLDP = new BooleanField(); + this.IPv6SendRA = new BooleanField(); } } @@ -329,10 +406,10 @@ class NetworkSection { */ class DHCPSection { constructor() { - this.UseDNS = new BooleanYesNo(); - this.UseNTP = new BooleanYesNo(); - this.UseMTU = new BooleanYesNo(); - this.UseHostname = new BooleanYesNo(); + this.UseDNS = new BooleanField(); + this.UseNTP = new BooleanField(); + this.UseMTU = new BooleanField(); + this.UseHostname = new BooleanField(); this.UseDomains = new BaseField( null, FieldType.USE_DOMAINS, @@ -354,8 +431,8 @@ class DHCPSection { FieldType.NUMBER, "Route metric for DHCP routes", ); - this.UseRoutes = new BooleanYesNo(); - this.SendRelease = new BooleanYesNo(); + this.UseRoutes = new BooleanField(); + this.SendRelease = new BooleanField(); } } @@ -373,10 +450,10 @@ class DHCPv4Section { enum: Object.values(ClientIdentifier), }, ); - this.UseDNS = new BooleanYesNo(); - this.UseNTP = new BooleanYesNo(); - this.UseMTU = new BooleanYesNo(); - this.UseHostname = new BooleanYesNo(); + this.UseDNS = new BooleanField(); + this.UseNTP = new BooleanField(); + this.UseMTU = new BooleanField(); + this.UseHostname = new BooleanField(); this.UseDomains = new BaseField( null, FieldType.USE_DOMAINS, @@ -385,7 +462,7 @@ class DHCPv4Section { enum: Object.values(UseDomains), }, ); - this.SendRelease = new BooleanYesNo(); + this.SendRelease = new BooleanField(); } } @@ -395,9 +472,9 @@ class DHCPv4Section { */ class DHCPv6Section { constructor() { - this.UseDNS = new BooleanYesNo(); - this.UseNTP = new BooleanYesNo(); - this.UseHostname = new BooleanYesNo(); + this.UseDNS = new BooleanField(); + this.UseNTP = new BooleanField(); + this.UseHostname = new BooleanField(); this.UseDomains = new BaseField( null, FieldType.USE_DOMAINS, @@ -406,8 +483,8 @@ class DHCPv6Section { enum: Object.values(UseDomains), }, ); - this.WithoutRA = new BooleanYesNo(); - this.UseAddress = new BooleanYesNo(); + this.WithoutRA = new BooleanField(); + this.UseAddress = new BooleanField(); } } @@ -417,7 +494,7 @@ class DHCPv6Section { */ class IPv6AcceptRASection { constructor() { - this.UseDNS = new BooleanYesNo(); + this.UseDNS = new BooleanField(); this.UseDomains = new BaseField( null, FieldType.USE_DOMAINS, @@ -426,9 +503,9 @@ class IPv6AcceptRASection { enum: Object.values(UseDomains), }, ); - this.UseAutonomousPrefix = new BooleanYesNo(); - this.UseOnLinkPrefix = new BooleanYesNo(); - this.UseRoutePrefix = new BooleanYesNo(); + this.UseAutonomousPrefix = new BooleanField(); + this.UseOnLinkPrefix = new BooleanField(); + this.UseRoutePrefix = new BooleanField(); this.RouteMetric = new BaseField( null, FieldType.NUMBER, @@ -438,12 +515,54 @@ class IPv6AcceptRASection { } /** + * [IPv6SendRA] section configuration + * @class IPv6SendRASection + */ +class IPv6SendRASection { + constructor() { + this.RouterLifetimeSec = new BaseField( + null, + FieldType.NUMBER, + "Router lifetime in seconds", + ); + this.EmitDNS = new BooleanField(); + this.DNS = new BaseField( + null, + FieldType.IP_ADDRESSES, + "DNS servers to advertise", + ); + this.EmitDomains = new BooleanField(); + this.Domains = new BaseField( + null, + FieldType.STRINGS, + "Domains to advertise", + ); + this.EmitNTP = new BooleanField(); + this.NTP = new BaseField( + null, + FieldType.IP_ADDRESSES, + "NTP servers to advertise", + ); + this.RouterPreference = new BaseField( + null, + FieldType.STRING, + "Router preference", + { + enum: ["high", "medium", "low"], + }, + ); + this.Managed = new BooleanField(); + this.OtherInformation = new BooleanField(); + } +} + +/** * [SLAAC] section configuration * @class SLAACSection */ class SLAACSection { constructor() { - this.UseDNS = new BooleanYesNo(); + this.UseDNS = new BooleanField(); this.UseDomains = new BaseField( null, FieldType.USE_DOMAINS, @@ -452,17 +571,17 @@ class SLAACSection { enum: Object.values(UseDomains), }, ); - this.UseAddress = new BooleanYesNo(); + this.UseAddress = new BooleanField(); this.RouteMetric = new BaseField( null, FieldType.NUMBER, "Route metric for SLAAC routes", ); - this.Critical = new BooleanYesNo(); - this.PreferTemporaryAddress = new BooleanYesNo(); - this.UseAutonomousPrefix = new BooleanYesNo(); - this.UseOnLinkPrefix = new BooleanYesNo(); - this.UseRoutePrefix = new BooleanYesNo(); + this.Critical = new BooleanField(); + this.PreferTemporaryAddress = new BooleanField(); + this.UseAutonomousPrefix = new BooleanField(); + this.UseOnLinkPrefix = new BooleanField(); + this.UseRoutePrefix = new BooleanField(); } } @@ -497,7 +616,7 @@ class AddressSection { class RouteSection { constructor() { this.Gateway = new BaseField(null, FieldType.IP_ADDRESS, "Gateway address"); - this.GatewayOnLink = new BooleanYesNo(); + this.GatewayOnLink = new BooleanField(); this.Destination = new BaseField( null, FieldType.IP_PREFIX, @@ -543,6 +662,7 @@ class NetworkConfiguration { #dhcpv4; #dhcpv6; #ipv6AcceptRA; + #ipv6SendRA; #slaac; #address; #route; @@ -555,87 +675,44 @@ class NetworkConfiguration { this.#dhcpv4 = new DHCPv4Section(); this.#dhcpv6 = new DHCPv6Section(); this.#ipv6AcceptRA = new IPv6AcceptRASection(); + this.#ipv6SendRA = new IPv6SendRASection(); this.#slaac = new SLAACSection(); this.#address = []; this.#route = []; } - /** - * Get Match section - * @returns {MatchSection} - */ + // ... getter methods for all sections ... + get Match() { return this.#match; } - - /** - * Get Link section - * @returns {LinkSection} - */ get Link() { return this.#link; } - - /** - * Get Network section - * @returns {NetworkSection} - */ get Network() { return this.#network; } - - /** - * Get DHCP section - * @returns {DHCPSection} - */ get DHCP() { return this.#dhcp; } - - /** - * Get DHCPv4 section - * @returns {DHCPv4Section} - */ get DHCPv4() { return this.#dhcpv4; } - - /** - * Get DHCPv6 section - * @returns {DHCPv6Section} - */ get DHCPv6() { return this.#dhcpv6; } - - /** - * Get IPv6AcceptRA section - * @returns {IPv6AcceptRASection} - */ get IPv6AcceptRA() { return this.#ipv6AcceptRA; } - - /** - * Get SLAAC section - * @returns {SLAACSection} - */ + get IPv6SendRA() { + return this.#ipv6SendRA; + } get SLAAC() { return this.#slaac; } - - /** - * Get Address sections - * @returns {Array<AddressSection>} - */ get Address() { return [...this.#address]; } - - /** - * Get Route sections - * @returns {Array<RouteSection>} - */ get Route() { return [...this.#route]; } @@ -653,18 +730,13 @@ class NetworkConfiguration { DHCPv4: this.#getSectionSchema(this.#dhcpv4), DHCPv6: this.#getSectionSchema(this.#dhcpv6), IPv6AcceptRA: this.#getSectionSchema(this.#ipv6AcceptRA), + IPv6SendRA: this.#getSectionSchema(this.#ipv6SendRA), SLAAC: this.#getSectionSchema(this.#slaac), Address: this.#getArraySectionSchema(AddressSection, "Address"), Route: this.#getArraySectionSchema(RouteSection, "Route"), }; } - /** - * Get schema for a single section - * @private - * @param {Object} section - Section instance - * @returns {Object} Section schema - */ #getSectionSchema(section) { const schema = {}; for (const [key, field] of Object.entries(section)) { @@ -678,13 +750,6 @@ class NetworkConfiguration { return schema; } - /** - * Get schema for array sections (Address, Route) - * @private - * @param {Class} SectionClass - Section class - * @param {string} sectionName - Section name - * @returns {Object} Array section schema - */ #getArraySectionSchema(SectionClass, sectionName) { const template = new SectionClass(); return { @@ -713,15 +778,16 @@ class NetworkConfiguration { if (!trimmed || trimmed.startsWith("#")) continue; // Section header - const sectionMatch = trimmed.match(/^\[(\w+)\]$/); + const sectionMatch = trimmed.match(/^\[(\w+)\]$/i); if (sectionMatch) { - currentSection = sectionMatch[1].toLowerCase(); + const sectionName = sectionMatch[1].toLowerCase(); + currentSection = config.#getSectionEnum(sectionName); // Start new array sections - if (currentSection === "address") { + if (currentSection === SectionName.ADDRESS) { currentAddress = new AddressSection(); config.#address.push(currentAddress); - } else if (currentSection === "route") { + } else if (currentSection === SectionName.ROUTE) { currentRoute = new RouteSection(); config.#route.push(currentRoute); } @@ -729,7 +795,7 @@ class NetworkConfiguration { } // Key-value pair - const kvMatch = trimmed.match(/^(\w+)\s*=\s*(.+)$/); + const kvMatch = trimmed.match(/^(\w+)\s*=\s*(.+)$/i); if (kvMatch && currentSection) { const key = kvMatch[1]; const value = kvMatch[2]; @@ -748,9 +814,33 @@ class NetworkConfiguration { } /** + * Get section enum from string + * @private + * @param {string} sectionName - Section name as string + * @returns {Symbol} Section enum + */ + #getSectionEnum(sectionName) { + const sectionMap = { + match: SectionName.MATCH, + link: SectionName.LINK, + network: SectionName.NETWORK, + dhcp: SectionName.DHCP, + dhcpv4: SectionName.DHCPV4, + dhcpv6: SectionName.DHCPV6, + ipv6acceptra: SectionName.IPV6_ACCEPT_RA, + ipv6sendra: SectionName.IPV6_SEND_RA, + slaac: SectionName.SLAAC, + address: SectionName.ADDRESS, + route: SectionName.ROUTE, + }; + + return sectionMap[sectionName] || null; + } + + /** * Set configuration value * @private - * @param {string} section - Section name + * @param {Symbol} section - Section enum * @param {string} key - Key name * @param {string} value - Value * @param {AddressSection} currentAddress - Current address section @@ -758,36 +848,39 @@ class NetworkConfiguration { */ #setValue(section, key, value, currentAddress, currentRoute) { switch (section) { - case "match": + case SectionName.MATCH: this.#setMatchValue(key, value); break; - case "link": + case SectionName.LINK: this.#setLinkValue(key, value); break; - case "network": + case SectionName.NETWORK: this.#setNetworkValue(key, value); break; - case "dhcp": + case SectionName.DHCP: this.#setDHCPValue(key, value); break; - case "dhcpv4": + case SectionName.DHCPV4: this.#setDHCPv4Value(key, value); break; - case "dhcpv6": + case SectionName.DHCPV6: this.#setDHCPv6Value(key, value); break; - case "ipv6acceptra": + case SectionName.IPV6_ACCEPT_RA: this.#setIPv6AcceptRAValue(key, value); break; - case "slaac": + case SectionName.IPV6_SEND_RA: + this.#setIPv6SendRAValue(key, value); + break; + case SectionName.SLAAC: this.#setSLAACValue(key, value); break; - case "address": + case SectionName.ADDRESS: if (currentAddress) { this.#setAddressValue(currentAddress, key, value); } break; - case "route": + case SectionName.ROUTE: if (currentRoute) { this.#setRouteValue(currentRoute, key, value); } @@ -837,6 +930,12 @@ class NetworkConfiguration { } } + #setIPv6SendRAValue(key, value) { + if (this.#ipv6SendRA[key] !== undefined) { + this.#ipv6SendRA[key].value = value; + } + } + #setSLAACValue(key, value) { if (this.#slaac[key] !== undefined) { this.#slaac[key].value = value; @@ -862,53 +961,25 @@ class NetworkConfiguration { toSystemdConfiguration() { const sections = []; - // [Match] section - if (this.#hasSectionValues(this.#match)) { - sections.push("[Match]"); - sections.push(...this.#formatSection(this.#match)); - } - - // [Link] section - if (this.#hasSectionValues(this.#link)) { - sections.push("[Link]"); - sections.push(...this.#formatSection(this.#link)); - } - - // [Network] section - if (this.#hasSectionValues(this.#network)) { - sections.push("[Network]"); - sections.push(...this.#formatSection(this.#network)); - } - - // [DHCP] section - if (this.#hasSectionValues(this.#dhcp)) { - sections.push("[DHCP]"); - sections.push(...this.#formatSection(this.#dhcp)); - } - - // [DHCPv4] section - if (this.#hasSectionValues(this.#dhcpv4)) { - sections.push("[DHCPv4]"); - sections.push(...this.#formatSection(this.#dhcpv4)); - } - - // [DHCPv6] section - if (this.#hasSectionValues(this.#dhcpv6)) { - sections.push("[DHCPv6]"); - sections.push(...this.#formatSection(this.#dhcpv6)); - } - - // [IPv6AcceptRA] section - if (this.#hasSectionValues(this.#ipv6AcceptRA)) { - sections.push("[IPv6AcceptRA]"); - sections.push(...this.#formatSection(this.#ipv6AcceptRA)); - } - - // [SLAAC] section - if (this.#hasSectionValues(this.#slaac)) { - sections.push("[SLAAC]"); - sections.push(...this.#formatSection(this.#slaac)); - } + // Add all sections in order + const sectionOrder = [ + { name: "Match", section: this.#match }, + { name: "Link", section: this.#link }, + { name: "Network", section: this.#network }, + { name: "DHCP", section: this.#dhcp }, + { name: "DHCPv4", section: this.#dhcpv4 }, + { name: "DHCPv6", section: this.#dhcpv6 }, + { name: "IPv6AcceptRA", section: this.#ipv6AcceptRA }, + { name: "IPv6SendRA", section: this.#ipv6SendRA }, + { name: "SLAAC", section: this.#slaac }, + ]; + + sectionOrder.forEach(({ name, section }) => { + if (this.#hasSectionValues(section)) { + sections.push(`[${name}]`); + sections.push(...this.#formatSection(section)); + } + }); // [Address] sections this.#address.forEach((addr) => { @@ -926,7 +997,7 @@ class NetworkConfiguration { } }); - return sections.length > 0 ? `${sections.join("\n")}\n` : ""; + return sections.length > 0 ? sections.join("\n") + "\n" : ""; } #hasSectionValues(section) { @@ -950,7 +1021,7 @@ export { MACAddress, IPv4Address, IPv6Address, - BooleanYesNo, + BooleanField, Port, MTU, MatchSection, @@ -960,6 +1031,7 @@ export { DHCPv4Section, DHCPv6Section, IPv6AcceptRASection, + IPv6SendRASection, SLAACSection, AddressSection, RouteSection, |
