summaryrefslogtreecommitdiffstats
path: root/static/systemd-network.js
diff options
context:
space:
mode:
authorPetri Hienonen <petri.hienonen@gmail.com>2025-09-28 13:30:59 +0300
committerPetri Hienonen <petri.hienonen@gmail.com>2025-09-28 13:30:59 +0300
commita1862888a7818ae9663b02dda48d25ef5f2ab6a6 (patch)
tree8f6ff541135d369cc6f58839a4534baf723e4abe /static/systemd-network.js
parent2bf20fe39cd70b25f89ccd3b40d06895f25a0833 (diff)
downloadnetwork-a1862888a7818ae9663b02dda48d25ef5f2ab6a6.tar.zst
Iteration 2
Diffstat (limited to '')
-rw-r--r--static/systemd-network.js564
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
+};