diff options
| author | Petri Hienonen <petri.hienonen@gmail.com> | 2025-09-28 13:30:59 +0300 |
|---|---|---|
| committer | Petri Hienonen <petri.hienonen@gmail.com> | 2025-09-28 13:30:59 +0300 |
| commit | a1862888a7818ae9663b02dda48d25ef5f2ab6a6 (patch) | |
| tree | 8f6ff541135d369cc6f58839a4534baf723e4abe /static/systemd-network.js | |
| parent | 2bf20fe39cd70b25f89ccd3b40d06895f25a0833 (diff) | |
| download | network-a1862888a7818ae9663b02dda48d25ef5f2ab6a6.tar.zst | |
Iteration 2
Diffstat (limited to '')
| -rw-r--r-- | static/systemd-network.js | 564 |
1 files changed, 564 insertions, 0 deletions
diff --git a/static/systemd-network.js b/static/systemd-network.js new file mode 100644 index 0000000..8c66a6d --- /dev/null +++ b/static/systemd-network.js @@ -0,0 +1,564 @@ +/* jshint esversion: 2024, module: true */ + +/** + * Systemd Network Configuration Parser + * Based on systemd.network(5) documentation + * @module SystemdNetwork + */ + +/** + * MAC Address type + * @class MACAddress + */ +class MACAddress { + /** + * @param {string} value - MAC address value + * @param {string} description - Description + */ + constructor(value = '', description = 'Hardware address') { + this.value = value; + this.type = 'mac-address'; + this.description = description; + this.pattern = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/; + } + + /** + * Validate MAC address format + * @returns {boolean} + */ + validate() { + return this.pattern.test(this.value); + } + + toString() { + return this.value; + } +} + +/** + * IPv4 Address type + * @class IPv4Address + */ +class IPv4Address { + /** + * @param {string} value - IPv4 address + * @param {string} description - Description + */ + constructor(value = '', description = 'IPv4 address') { + this.value = value; + this.type = 'ipv4-address'; + this.description = description; + this.pattern = /^(\d{1,3}\.){3}\d{1,3}$/; + } + + validate() { + return this.pattern.test(this.value); + } + + toString() { + return this.value; + } +} + +/** + * IPv6 Address type + * @class IPv6Address + */ +class IPv6Address { + /** + * @param {string} value - IPv6 address + * @param {string} description - Description + */ + constructor(value = '', description = 'IPv6 address') { + this.value = value; + this.type = 'ipv6-address'; + this.description = description; + this.pattern = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/; + } + + validate() { + return this.pattern.test(this.value); + } + + toString() { + return this.value; + } +} + +/** + * [Match] section configuration + * @class MatchSection + */ +class MatchSection { + constructor() { + /** @type {MACAddress[]} */ + this.MACAddress = []; + /** @type {string[]} */ + this.OriginalName = []; + /** @type {string[]} */ + this.Path = []; + /** @type {string[]} */ + this.Driver = []; + /** @type {string[]} */ + this.Type = []; + /** @type {string[]} */ + this.Name = []; + /** @type {string} */ + this.Property = ''; + /** @type {string} */ + this.Host = ''; + /** @type {string} */ + this.Virtualization = ''; + /** @type {string} */ + this.KernelCommandLine = ''; + /** @type {string} */ + this.Architecture = ''; + } +} + +/** + * [Link] section configuration + * @class LinkSection + */ +class LinkSection { + constructor() { + /** @type {string} */ + this.MACAddress = ''; + /** @type {string} */ + this.MTUBytes = ''; + /** @type {number} */ + this.BitsPerSecond = 0; + /** @type {string} */ + this.Duplex = ''; + /** @type {string} */ + this.AutoNegotiation = ''; + /** @type {string} */ + this.WakeOnLan = ''; + /** @type {string} */ + this.Port = ''; + /** @type {string} */ + this.Advertise = ''; + /** @type {string} */ + this.RxFlowControl = ''; + /** @type {string} */ + this.TxFlowControl = ''; + } +} + +/** + * [Network] section configuration + * @class NetworkSection + */ +class NetworkSection { + constructor() { + /** @type {string} */ + this.Description = ''; + /** @type {string[]} */ + this.DHCP = []; // 'yes', 'no', 'ipv4', 'ipv6' + /** @type {boolean} */ + this.DHCPServer = false; + /** @type {string[]} */ + this.DNS = []; + /** @type {string[]} */ + this.NTP = []; + /** @type {string[]} */ + this.IPForward = []; // 'yes', 'no', 'ipv4', 'ipv6' + /** @type {string} */ + this.IPv6PrivacyExtensions = ''; // 'yes', 'no', 'prefer-public' + /** @type {string} */ + this.IPv6AcceptRA = ''; // 'yes', 'no' + /** @type {string} */ + this.LLMNR = ''; // 'yes', 'no', 'resolve' + /** @type {string} */ + this.MulticastDNS = ''; // 'yes', 'no', 'resolve' + /** @type {string} */ + this.DNSSEC = ''; // 'yes', 'no', 'allow-downgrade' + /** @type {string[]} */ + this.Domains = []; + /** @type {string} */ + this.ConfigureWithoutCarrier = ''; // 'yes', 'no' + /** @type {string} */ + this.IgnoreCarrierLoss = ''; // 'yes', 'no' + /** @type {number} */ + this.KeepConfiguration = 0; // seconds + } +} + +/** + * [DHCP] section configuration + * @class DHCPSection + */ +class DHCPSection { + constructor() { + /** @type {string} */ + this.UseDNS = ''; // 'yes', 'no' + /** @type {string} */ + this.UseNTP = ''; // 'yes', 'no' + /** @type {string} */ + this.UseMTU = ''; // 'yes', 'no' + /** @type {string} */ + this.UseHostname = ''; // 'yes', 'no' + /** @type {string} */ + this.UseDomains = ''; // 'yes', 'no', 'route' + /** @type {string} */ + this.ClientIdentifier = ''; // 'mac', 'duid' + /** @type {string} */ + this.RouteMetric = ''; + } +} + +/** + * [Address] section configuration + * @class AddressSection + */ +class AddressSection { + constructor() { + /** @type {string} */ + this.Address = ''; // IP address with prefix + /** @type {string} */ + this.Peer = ''; // Peer address + /** @type {string} */ + this.Broadcast = ''; // Broadcast address + /** @type {string} */ + this.Label = ''; // Address label + /** @type {number} */ + this.Scope = 0; // Address scope + /** @type {string} */ + this.Flags = ''; // Address flags + } +} + +/** + * [Route] section configuration + * @class RouteSection + */ +class RouteSection { + constructor() { + /** @type {string} */ + this.Gateway = ''; // Gateway address + /** @type {string} */ + this.GatewayOnLink = ''; // 'yes', 'no' + /** @type {string} */ + this.Destination = ''; // Destination prefix + /** @type {string} */ + this.Source = ''; // Source address + /** @type {string} */ + this.PreferredSource = ''; // Preferred source address + /** @type {number} */ + this.Metric = 1024; // Route metric + /** @type {string} */ + this.Scope = ''; // 'global', 'link', 'host' + /** @type {string} */ + this.Type = ''; // 'unicast', 'local', 'broadcast', etc. + } +} + +/** + * Complete network configuration + * @class NetworkConfiguration + */ +class NetworkConfiguration { + constructor() { + /** @type {MatchSection} */ + this.Match = new MatchSection(); + /** @type {LinkSection} */ + this.Link = new LinkSection(); + /** @type {NetworkSection} */ + this.Network = new NetworkSection(); + /** @type {DHCPSection} */ + this.DHCP = new DHCPSection(); + /** @type {AddressSection[]} */ + this.Address = []; + /** @type {RouteSection[]} */ + this.Route = []; + } + + /** + * Parse systemd network configuration from text + * @param {string} configText - Configuration file content + * @returns {NetworkConfiguration} + */ + static fromSystemdConfiguration(configText) { + const config = new NetworkConfiguration(); + const lines = configText.split('\n'); + let currentSection = null; + + for (const line of lines) { + const trimmed = line.trim(); + + // Skip empty lines and comments + if (!trimmed || trimmed.startsWith('#')) continue; + + // Section header + const sectionMatch = trimmed.match(/^\[(\w+)\]$/); + if (sectionMatch) { + currentSection = sectionMatch[1].toLowerCase(); + continue; + } + + // Key-value pair + const kvMatch = trimmed.match(/^(\w+)\s*=\s*(.+)$/); + if (kvMatch && currentSection) { + const key = kvMatch[1]; + const value = kvMatch[2]; + + config._setValue(currentSection, key, value); + } + } + + return config; + } + + /** + * Set configuration value + * @private + * @param {string} section - Section name + * @param {string} key - Key name + * @param {string} value - Value + */ + _setValue(section, key, value) { + switch (section) { + case 'match': + this._setMatchValue(key, value); + break; + case 'link': + this._setLinkValue(key, value); + break; + case 'network': + this._setNetworkValue(key, value); + break; + case 'dhcp': + this._setDHCPValue(key, value); + break; + case 'address': + // Handle multiple address sections + if (!this.Address.length) this.Address.push(new AddressSection()); + this._setAddressValue(this.Address[this.Address.length - 1], key, value); + break; + case 'route': + // Handle multiple route sections + if (!this.Route.length) this.Route.push(new RouteSection()); + this._setRouteValue(this.Route[this.Route.length - 1], key, value); + break; + } + } + + _setMatchValue(key, value) { + const match = this.Match; + switch (key) { + case 'MACAddress': + match.MACAddress = value.split(' '); + break; + case 'Name': + match.Name = value.split(' '); + break; + case 'Driver': + match.Driver = value.split(' '); + break; + case 'Path': + match.Path = value.split(' '); + break; + case 'Type': + match.Type = value.split(' '); + break; + default: + match[key] = value; + } + } + + _setLinkValue(key, value) { + this.Link[key] = value; + } + + _setNetworkValue(key, value) { + const network = this.Network; + switch (key) { + case 'DNS': + case 'NTP': + case 'Domains': + case 'DHCP': + case 'IPForward': + network[key] = value.split(' '); + break; + default: + network[key] = value; + } + } + + _setDHCPValue(key, value) { + this.DHCP[key] = value; + } + + _setAddressValue(address, key, value) { + address[key] = value; + } + + _setRouteValue(route, key, value) { + route[key] = value; + } + + /** + * Convert to systemd network configuration format + * @returns {string} + */ + toSystemdConfiguration() { + const sections = []; + + // [Match] section + if (this._hasMatchValues()) { + sections.push('[Match]'); + sections.push(...this._formatMatchSection()); + } + + // [Link] section + if (this._hasLinkValues()) { + sections.push('[Link]'); + sections.push(...this._formatLinkSection()); + } + + // [Network] section + if (this._hasNetworkValues()) { + sections.push('[Network]'); + sections.push(...this._formatNetworkSection()); + } + + // [DHCP] section + if (this._hasDHCPValues()) { + sections.push('[DHCP]'); + sections.push(...this._formatDHCPSection()); + } + + // [Address] sections + this.Address.forEach((addr, index) => { + if (this._hasAddressValues(addr)) { + sections.push('[Address]'); + sections.push(...this._formatAddressSection(addr)); + } + }); + + // [Route] sections + this.Route.forEach((route, index) => { + if (this._hasRouteValues(route)) { + sections.push('[Route]'); + sections.push(...this._formatRouteSection(route)); + } + }); + + return sections.join('\n') + '\n'; + } + + // Helper methods to check if sections have values + _hasMatchValues() { + return Object.values(this.Match).some(val => + val && (Array.isArray(val) ? val.length > 0 : val !== '') + ); + } + + _hasLinkValues() { + return Object.values(this.Link).some(val => val && val !== ''); + } + + _hasNetworkValues() { + return Object.values(this.Network).some(val => + val && (Array.isArray(val) ? val.length > 0 : val !== '') + ); + } + + _hasDHCPValues() { + return Object.values(this.DHCP).some(val => val && val !== ''); + } + + _hasAddressValues(addr) { + return Object.values(addr).some(val => val && val !== ''); + } + + _hasRouteValues(route) { + return Object.values(route).some(val => val && val !== ''); + } + + // Formatting methods + _formatMatchSection() { + const lines = []; + const match = this.Match; + + if (match.MACAddress.length) lines.push(`MACAddress=${match.MACAddress.join(' ')}`); + if (match.Name.length) lines.push(`Name=${match.Name.join(' ')}`); + if (match.Driver.length) lines.push(`Driver=${match.Driver.join(' ')}`); + if (match.Path.length) lines.push(`Path=${match.Path.join(' ')}`); + if (match.Type.length) lines.push(`Type=${match.Type.join(' ')}`); + if (match.Property) lines.push(`Property=${match.Property}`); + + return lines; + } + + _formatLinkSection() { + const lines = []; + const link = this.Link; + + Object.entries(link).forEach(([key, value]) => { + if (value && value !== '') lines.push(`${key}=${value}`); + }); + + return lines; + } + + _formatNetworkSection() { + const lines = []; + const network = this.Network; + + if (network.Description) lines.push(`Description=${network.Description}`); + if (network.DHCP.length) lines.push(`DHCP=${network.DHCP.join(' ')}`); + if (network.DNS.length) lines.push(`DNS=${network.DNS.join(' ')}`); + if (network.NTP.length) lines.push(`NTP=${network.NTP.join(' ')}`); + if (network.IPForward.length) lines.push(`IPForward=${network.IPForward.join(' ')}`); + if (network.IPv6PrivacyExtensions) lines.push(`IPv6PrivacyExtensions=${network.IPv6PrivacyExtensions}`); + if (network.LLMNR) lines.push(`LLMNR=${network.LLMNR}`); + + return lines; + } + + _formatDHCPSection() { + const lines = []; + const dhcp = this.DHCP; + + Object.entries(dhcp).forEach(([key, value]) => { + if (value && value !== '') lines.push(`${key}=${value}`); + }); + + return lines; + } + + _formatAddressSection(addr) { + const lines = []; + + Object.entries(addr).forEach(([key, value]) => { + if (value && value !== '') lines.push(`${key}=${value}`); + }); + + return lines; + } + + _formatRouteSection(route) { + const lines = []; + + Object.entries(route).forEach(([key, value]) => { + if (value && value !== '') lines.push(`${key}=${value}`); + }); + + return lines; + } +} + +// Export classes +export { + MACAddress, + IPv4Address, + IPv6Address, + MatchSection, + LinkSection, + NetworkSection, + DHCPSection, + AddressSection, + RouteSection, + NetworkConfiguration +}; |
