summaryrefslogtreecommitdiffstats
path: root/static/systemd-network.js
diff options
context:
space:
mode:
authorPetri Hienonen <petri.hienonen@gmail.com>2025-09-28 16:25:48 +0300
committerPetri Hienonen <petri.hienonen@gmail.com>2025-09-28 16:25:48 +0300
commit38ac4db03560200df2517ed7e06e555828ce0a15 (patch)
tree7bd8cc7d08d352bea05490e81b230a9d418a8553 /static/systemd-network.js
parent8489f1f83da5dcc5818401393b1f6a430eea677c (diff)
downloadnetwork-38ac4db03560200df2517ed7e06e555828ce0a15.tar.zst
Update
Diffstat (limited to 'static/systemd-network.js')
-rw-r--r--static/systemd-network.js1301
1 files changed, 870 insertions, 431 deletions
diff --git a/static/systemd-network.js b/static/systemd-network.js
index 0c57ec1..69020f1 100644
--- a/static/systemd-network.js
+++ b/static/systemd-network.js
@@ -1,54 +1,132 @@
/* jshint esversion: 2024, module: true */
-/**
- * Systemd Network Configuration Parser
- * Based on systemd.network(5) documentation
- * @module SystemdNetwork
- */
+import {
+ BooleanYesNo,
+ ClientIdentifier,
+ DHCPMode,
+ DNSSECOptions,
+ DuplexMode,
+ FieldType,
+ IPForward,
+ IPv6PrivacyExtensions,
+ LLMNROptions,
+ MulticastDNS,
+ PortType,
+ RouteScope,
+ RouteType,
+ UseDomains,
+ WakeOnLAN,
+} from "./network-types.js";
/**
* Base field type with standardized interface
* @class BaseField
*/
class BaseField {
- /**
- * @param {*} value - Field value
- * @param {string} type - Field type
- * @param {string} description - Field description
- * @param {Object} options - Additional options (pattern, enum, etc.)
- */
- constructor(value = null, type = 'string', description = '', options = {}) {
- this.value = value;
- this.type = type;
- this.description = description;
- this.options = options;
- }
-
- /**
- * Validate field value
- * @returns {boolean}
- */
- validate() {
- if (this.value === null || this.value === undefined) return true;
-
- if (this.options.pattern && typeof this.value === 'string') {
- return this.options.pattern.test(this.value);
- }
-
- if (this.options.enum && this.options.enum.length > 0) {
- return this.options.enum.includes(this.value);
- }
-
- return true;
- }
-
- /**
- * Convert to string representation
- * @returns {string}
- */
- toString() {
- return this.value !== null ? String(this.value) : '';
- }
+ #value;
+ #type;
+ #description;
+ #options;
+
+ /**
+ * @param {*} value - Field value
+ * @param {Symbol} type - Field type from FieldType enum
+ * @param {string} description - Field description
+ * @param {Object} options - Additional options (pattern, enum, etc.)
+ */
+ constructor(
+ value = null,
+ type = FieldType.STRING,
+ description = "",
+ options = {},
+ ) {
+ this.#value = value;
+ this.#type = type;
+ this.#description = description;
+ this.#options = options;
+ }
+
+ /**
+ * Get field value
+ * @returns {*}
+ */
+ get value() {
+ return this.#value;
+ }
+
+ /**
+ * Set field value
+ * @param {*} newValue
+ */
+ set value(newValue) {
+ this.#value = newValue;
+ }
+
+ /**
+ * Get field type
+ * @returns {Symbol}
+ */
+ get type() {
+ return this.#type;
+ }
+
+ /**
+ * Get field description
+ * @returns {string}
+ */
+ get description() {
+ return this.#description;
+ }
+
+ /**
+ * Get field options
+ * @returns {Object}
+ */
+ get options() {
+ return { ...this.#options };
+ }
+
+ /**
+ * Validate field value
+ * @returns {boolean}
+ */
+ validate() {
+ if (
+ this.#value === null ||
+ this.#value === undefined ||
+ this.#value === ""
+ ) {
+ return true;
+ }
+
+ if (this.#options.pattern && typeof this.#value === "string") {
+ return this.#options.pattern.test(this.#value);
+ }
+
+ if (this.#options.enum && this.#options.enum.length > 0) {
+ return this.#options.enum.includes(this.#value);
+ }
+
+ return true;
+ }
+
+ /**
+ * Convert to string representation
+ * @returns {string}
+ */
+ toString() {
+ return this.#value !== null ? String(this.#value) : "";
+ }
+
+ /**
+ * Check if field has a value
+ * @returns {boolean}
+ */
+ hasValue() {
+ return (
+ this.#value !== null && this.#value !== undefined && this.#value !== ""
+ );
+ }
}
/**
@@ -56,11 +134,11 @@ class BaseField {
* @class MACAddress
*/
class MACAddress extends BaseField {
- constructor(value = null) {
- super(value, 'mac-address', 'Hardware address (MAC)', {
- pattern: /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/
- });
- }
+ constructor(value = null) {
+ super(value, FieldType.MAC_ADDRESS, "Hardware address (MAC)", {
+ pattern: /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/,
+ });
+ }
}
/**
@@ -68,11 +146,11 @@ class MACAddress extends BaseField {
* @class IPv4Address
*/
class IPv4Address extends BaseField {
- constructor(value = null) {
- super(value, 'ipv4-address', 'IPv4 address', {
- pattern: /^(\d{1,3}\.){3}\d{1,3}$/
- });
- }
+ constructor(value = null) {
+ super(value, FieldType.IPV4_ADDRESS, "IPv4 address", {
+ pattern: /^(\d{1,3}\.){3}\d{1,3}$/,
+ });
+ }
}
/**
@@ -80,23 +158,11 @@ class IPv4Address extends BaseField {
* @class IPv6Address
*/
class IPv6Address extends BaseField {
- constructor(value = null) {
- super(value, 'ipv6-address', 'IPv6 address', {
- pattern: /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/
- });
- }
-}
-
-/**
- * Boolean type with yes/no values
- * @class BooleanYesNo
- */
-class BooleanYesNo extends BaseField {
- constructor(value = null) {
- super(value, 'boolean', 'Boolean (yes/no)', {
- enum: ['yes', 'no']
- });
- }
+ constructor(value = null) {
+ super(value, FieldType.IPV6_ADDRESS, "IPv6 address", {
+ pattern: /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/,
+ });
+ }
}
/**
@@ -104,11 +170,12 @@ class BooleanYesNo extends BaseField {
* @class Port
*/
class Port extends BaseField {
- constructor(value = null) {
- super(value, 'port', 'Network port (1-65535)', {
- pattern: /^([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/
- });
- }
+ constructor(value = null) {
+ super(value, FieldType.PORT, "Network port (1-65535)", {
+ pattern:
+ /^([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/,
+ });
+ }
}
/**
@@ -116,11 +183,11 @@ class Port extends BaseField {
* @class MTU
*/
class MTU extends BaseField {
- constructor(value = null) {
- super(value, 'mtu', 'Maximum Transmission Unit', {
- pattern: /^[1-9][0-9]*$/
- });
- }
+ constructor(value = null) {
+ super(value, FieldType.MTU, "Maximum Transmission Unit", {
+ pattern: /^[1-9][0-9]*$/,
+ });
+ }
}
/**
@@ -128,19 +195,43 @@ class MTU extends BaseField {
* @class MatchSection
*/
class MatchSection {
- constructor() {
- this.MACAddress = new BaseField(null, 'mac-addresses', 'Space-separated MAC addresses');
- this.OriginalName = new BaseField(null, 'strings', 'Original interface names');
- this.Path = new BaseField(null, 'strings', 'Device path patterns');
- this.Driver = new BaseField(null, 'strings', 'Driver names');
- this.Type = new BaseField(null, 'strings', 'Interface types (ether, wifi, etc.)');
- this.Name = new BaseField(null, 'strings', 'Interface names');
- this.Property = new BaseField(null, 'string', 'Device property');
- this.Host = new BaseField(null, 'string', 'Host name');
- this.Virtualization = new BaseField(null, 'string', 'Virtualization detection');
- this.KernelCommandLine = new BaseField(null, 'string', 'Kernel command line');
- this.Architecture = new BaseField(null, 'string', 'System architecture');
- }
+ constructor() {
+ this.MACAddress = new BaseField(
+ null,
+ FieldType.STRINGS,
+ "Space-separated MAC addresses",
+ );
+ this.OriginalName = new BaseField(
+ null,
+ FieldType.STRINGS,
+ "Original interface names",
+ );
+ this.Path = new BaseField(null, FieldType.STRINGS, "Device path patterns");
+ this.Driver = new BaseField(null, FieldType.STRINGS, "Driver names");
+ this.Type = new BaseField(
+ null,
+ FieldType.STRINGS,
+ "Interface types (ether, wifi, etc.)",
+ );
+ this.Name = new BaseField(null, FieldType.STRINGS, "Interface names");
+ this.Property = new BaseField(null, FieldType.STRING, "Device property");
+ this.Host = new BaseField(null, FieldType.STRING, "Host name");
+ this.Virtualization = new BaseField(
+ null,
+ FieldType.STRING,
+ "Virtualization detection",
+ );
+ this.KernelCommandLine = new BaseField(
+ null,
+ FieldType.STRING,
+ "Kernel command line",
+ );
+ this.Architecture = new BaseField(
+ null,
+ FieldType.STRING,
+ "System architecture",
+ );
+ }
}
/**
@@ -148,24 +239,32 @@ class MatchSection {
* @class LinkSection
*/
class LinkSection {
- constructor() {
- this.MACAddress = new MACAddress();
- this.MTUBytes = new MTU();
- this.BitsPerSecond = new BaseField(null, 'number', 'Link speed in bits per second');
- this.Duplex = new BaseField(null, 'string', 'Duplex mode', {
- enum: ['half', 'full']
- });
- this.AutoNegotiation = new BooleanYesNo();
- this.WakeOnLan = new BaseField(null, 'string', 'Wake-on-LAN', {
- enum: ['phy', 'unicast', 'broadcast', 'arp', 'magic', '']
- });
- this.Port = new BaseField(null, 'string', 'Port type', {
- enum: ['tp', 'aui', 'bnc', 'mii', 'fibre', '']
- });
- this.Advertise = new BaseField(null, 'strings', 'Advertised features');
- this.RxFlowControl = new BooleanYesNo();
- this.TxFlowControl = new BooleanYesNo();
- }
+ constructor() {
+ this.MACAddress = new MACAddress();
+ this.MTUBytes = new MTU();
+ this.BitsPerSecond = new BaseField(
+ null,
+ FieldType.NUMBER,
+ "Link speed in bits per second",
+ );
+ this.Duplex = new BaseField(null, FieldType.STRING, "Duplex mode", {
+ enum: Object.values(DuplexMode),
+ });
+ this.AutoNegotiation = new BooleanYesNo();
+ this.WakeOnLan = new BaseField(null, FieldType.STRING, "Wake-on-LAN", {
+ enum: Object.values(WakeOnLAN),
+ });
+ this.Port = new BaseField(null, FieldType.STRING, "Port type", {
+ enum: Object.values(PortType),
+ });
+ this.Advertise = new BaseField(
+ null,
+ FieldType.STRINGS,
+ "Advertised features",
+ );
+ this.RxFlowControl = new BooleanYesNo();
+ this.TxFlowControl = new BooleanYesNo();
+ }
}
/**
@@ -173,35 +272,55 @@ class LinkSection {
* @class NetworkSection
*/
class NetworkSection {
- constructor() {
- this.Description = new BaseField(null, 'string', 'Interface description');
- this.DHCP = new BaseField(null, 'dhcp-mode', 'DHCP client', {
- enum: ['yes', 'no', 'ipv4', 'ipv6']
- });
- this.DHCPServer = new BooleanYesNo();
- this.DNS = new BaseField(null, 'ip-addresses', 'DNS servers');
- this.NTP = new BaseField(null, 'ip-addresses', 'NTP servers');
- this.IPForward = new BaseField(null, 'ip-forward', 'IP forwarding', {
- enum: ['yes', 'no', 'ipv4', 'ipv6']
- });
- this.IPv6PrivacyExtensions = new BaseField(null, 'privacy-extensions', 'IPv6 privacy extensions', {
- enum: ['yes', 'no', 'prefer-public']
- });
- this.IPv6AcceptRA = new BooleanYesNo();
- this.LLMNR = new BaseField(null, 'llmnr', 'LLMNR support', {
- enum: ['yes', 'no', 'resolve']
- });
- this.MulticastDNS = new BaseField(null, 'mdns', 'Multicast DNS', {
- enum: ['yes', 'no', 'resolve']
- });
- this.DNSSEC = new BaseField(null, 'dnssec', 'DNSSEC support', {
- enum: ['yes', 'no', 'allow-downgrade']
- });
- this.Domains = new BaseField(null, 'strings', 'DNS search domains');
- this.ConfigureWithoutCarrier = new BooleanYesNo();
- this.IgnoreCarrierLoss = new BooleanYesNo();
- this.KeepConfiguration = new BaseField(null, 'number', 'Keep configuration time in seconds');
- }
+ constructor() {
+ this.Description = new BaseField(
+ null,
+ FieldType.STRING,
+ "Interface description",
+ );
+ this.DHCP = new BaseField(null, FieldType.DHCP_MODE, "DHCP client", {
+ enum: Object.values(DHCPMode),
+ });
+ this.DHCPServer = new BooleanYesNo();
+ this.DNS = new BaseField(null, FieldType.IP_ADDRESSES, "DNS servers");
+ this.NTP = new BaseField(null, FieldType.IP_ADDRESSES, "NTP servers");
+ this.IPForward = new BaseField(
+ null,
+ FieldType.IP_FORWARD,
+ "IP forwarding",
+ {
+ enum: Object.values(IPForward),
+ },
+ );
+ this.IPv6PrivacyExtensions = new BaseField(
+ null,
+ FieldType.PRIVACY_EXTENSIONS,
+ "IPv6 privacy extensions",
+ {
+ enum: Object.values(IPv6PrivacyExtensions),
+ },
+ );
+ this.IPv6AcceptRA = new BooleanYesNo();
+ this.LLMNR = new BaseField(null, FieldType.LLMNR, "LLMNR support", {
+ enum: Object.values(LLMNROptions),
+ });
+ this.MulticastDNS = new BaseField(null, FieldType.MDNS, "Multicast DNS", {
+ enum: Object.values(MulticastDNS),
+ });
+ this.DNSSEC = new BaseField(null, FieldType.DNSSEC, "DNSSEC support", {
+ enum: Object.values(DNSSECOptions),
+ });
+ this.Domains = new BaseField(null, FieldType.STRINGS, "DNS search domains");
+ this.ConfigureWithoutCarrier = new BooleanYesNo();
+ this.IgnoreCarrierLoss = new BooleanYesNo();
+ this.KeepConfiguration = new BaseField(
+ null,
+ FieldType.NUMBER,
+ "Keep configuration time in seconds",
+ );
+ this.LLDP = new BooleanYesNo();
+ this.EmitLLDP = new BooleanYesNo();
+ }
}
/**
@@ -209,19 +328,142 @@ class NetworkSection {
* @class DHCPSection
*/
class DHCPSection {
- constructor() {
- this.UseDNS = new BooleanYesNo();
- this.UseNTP = new BooleanYesNo();
- this.UseMTU = new BooleanYesNo();
- this.UseHostname = new BooleanYesNo();
- this.UseDomains = new BaseField(null, 'use-domains', 'Use domains from DHCP', {
- enum: ['yes', 'no', 'route']
- });
- this.ClientIdentifier = new BaseField(null, 'client-identifier', 'DHCP client identifier', {
- enum: ['mac', 'duid']
- });
- this.RouteMetric = new BaseField(null, 'number', 'Route metric for DHCP routes');
- }
+ constructor() {
+ this.UseDNS = new BooleanYesNo();
+ this.UseNTP = new BooleanYesNo();
+ this.UseMTU = new BooleanYesNo();
+ this.UseHostname = new BooleanYesNo();
+ this.UseDomains = new BaseField(
+ null,
+ FieldType.USE_DOMAINS,
+ "Use domains from DHCP",
+ {
+ enum: Object.values(UseDomains),
+ },
+ );
+ this.ClientIdentifier = new BaseField(
+ null,
+ FieldType.CLIENT_IDENTIFIER,
+ "DHCP client identifier",
+ {
+ enum: Object.values(ClientIdentifier),
+ },
+ );
+ this.RouteMetric = new BaseField(
+ null,
+ FieldType.NUMBER,
+ "Route metric for DHCP routes",
+ );
+ this.UseRoutes = new BooleanYesNo();
+ this.SendRelease = new BooleanYesNo();
+ }
+}
+
+/**
+ * [DHCPv4] section configuration
+ * @class DHCPv4Section
+ */
+class DHCPv4Section {
+ constructor() {
+ this.ClientIdentifier = new BaseField(
+ null,
+ FieldType.CLIENT_IDENTIFIER,
+ "DHCPv4 client identifier",
+ {
+ enum: Object.values(ClientIdentifier),
+ },
+ );
+ this.UseDNS = new BooleanYesNo();
+ this.UseNTP = new BooleanYesNo();
+ this.UseMTU = new BooleanYesNo();
+ this.UseHostname = new BooleanYesNo();
+ this.UseDomains = new BaseField(
+ null,
+ FieldType.USE_DOMAINS,
+ "Use domains from DHCPv4",
+ {
+ enum: Object.values(UseDomains),
+ },
+ );
+ this.SendRelease = new BooleanYesNo();
+ }
+}
+
+/**
+ * [DHCPv6] section configuration
+ * @class DHCPv6Section
+ */
+class DHCPv6Section {
+ constructor() {
+ this.UseDNS = new BooleanYesNo();
+ this.UseNTP = new BooleanYesNo();
+ this.UseHostname = new BooleanYesNo();
+ this.UseDomains = new BaseField(
+ null,
+ FieldType.USE_DOMAINS,
+ "Use domains from DHCPv6",
+ {
+ enum: Object.values(UseDomains),
+ },
+ );
+ this.WithoutRA = new BooleanYesNo();
+ this.UseAddress = new BooleanYesNo();
+ }
+}
+
+/**
+ * [IPv6AcceptRA] section configuration
+ * @class IPv6AcceptRASection
+ */
+class IPv6AcceptRASection {
+ constructor() {
+ this.UseDNS = new BooleanYesNo();
+ this.UseDomains = new BaseField(
+ null,
+ FieldType.USE_DOMAINS,
+ "Use domains from RA",
+ {
+ enum: Object.values(UseDomains),
+ },
+ );
+ this.UseAutonomousPrefix = new BooleanYesNo();
+ this.UseOnLinkPrefix = new BooleanYesNo();
+ this.UseRoutePrefix = new BooleanYesNo();
+ this.RouteMetric = new BaseField(
+ null,
+ FieldType.NUMBER,
+ "Route metric for RA routes",
+ );
+ }
+}
+
+/**
+ * [SLAAC] section configuration
+ * @class SLAACSection
+ */
+class SLAACSection {
+ constructor() {
+ this.UseDNS = new BooleanYesNo();
+ this.UseDomains = new BaseField(
+ null,
+ FieldType.USE_DOMAINS,
+ "Use domains from SLAAC",
+ {
+ enum: Object.values(UseDomains),
+ },
+ );
+ this.UseAddress = new BooleanYesNo();
+ 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();
+ }
}
/**
@@ -229,14 +471,23 @@ class DHCPSection {
* @class AddressSection
*/
class AddressSection {
- constructor() {
- this.Address = new BaseField(null, 'ip-prefix', 'IP address with prefix');
- this.Peer = new BaseField(null, 'ip-address', 'Peer address');
- this.Broadcast = new BaseField(null, 'ip-address', 'Broadcast address');
- this.Label = new BaseField(null, 'string', 'Address label');
- this.Scope = new BaseField(null, 'number', 'Address scope');
- this.Flags = new BaseField(null, 'strings', 'Address flags');
- }
+ constructor() {
+ this.Address = new BaseField(
+ null,
+ FieldType.IP_PREFIX,
+ "IP address with prefix",
+ );
+ this.Peer = new BaseField(null, FieldType.IP_ADDRESS, "Peer address");
+ this.Broadcast = new BaseField(
+ null,
+ FieldType.IP_ADDRESS,
+ "Broadcast address",
+ );
+ this.Label = new BaseField(null, FieldType.STRING, "Address label");
+ this.Scope = new BaseField(null, FieldType.NUMBER, "Address scope");
+ this.Flags = new BaseField(null, FieldType.STRINGS, "Address flags");
+ this.Lifetime = new BaseField(null, FieldType.STRING, "Address lifetime");
+ }
}
/**
@@ -244,20 +495,40 @@ class AddressSection {
* @class RouteSection
*/
class RouteSection {
- constructor() {
- this.Gateway = new BaseField(null, 'ip-address', 'Gateway address');
- this.GatewayOnLink = new BooleanYesNo();
- this.Destination = new BaseField(null, 'ip-prefix', 'Destination prefix');
- this.Source = new BaseField(null, 'ip-address', 'Source address');
- this.PreferredSource = new BaseField(null, 'ip-address', 'Preferred source address');
- this.Metric = new BaseField(null, 'number', 'Route metric');
- this.Scope = new BaseField(null, 'route-scope', 'Route scope', {
- enum: ['global', 'link', 'host']
- });
- this.Type = new BaseField(null, 'route-type', 'Route type', {
- enum: ['unicast', 'local', 'broadcast', 'anycast', 'multicast', 'blackhole', 'unreachable', 'prohibit']
- });
- }
+ constructor() {
+ this.Gateway = new BaseField(null, FieldType.IP_ADDRESS, "Gateway address");
+ this.GatewayOnLink = new BooleanYesNo();
+ this.Destination = new BaseField(
+ null,
+ FieldType.IP_PREFIX,
+ "Destination prefix",
+ );
+ this.Source = new BaseField(null, FieldType.IP_ADDRESS, "Source address");
+ this.PreferredSource = new BaseField(
+ null,
+ FieldType.IP_ADDRESS,
+ "Preferred source address",
+ );
+ this.Metric = new BaseField(null, FieldType.NUMBER, "Route metric");
+ this.Scope = new BaseField(null, FieldType.ROUTE_SCOPE, "Route scope", {
+ enum: Object.values(RouteScope),
+ });
+ this.Type = new BaseField(null, FieldType.ROUTE_TYPE, "Route type", {
+ enum: Object.values(RouteType),
+ });
+ this.InitialCongestionWindow = new BaseField(
+ null,
+ FieldType.NUMBER,
+ "Initial congestion window",
+ );
+ this.InitialAdvertisedReceiveWindow = new BaseField(
+ null,
+ FieldType.NUMBER,
+ "Initial advertised receive window",
+ );
+ this.Table = new BaseField(null, FieldType.NUMBER, "Routing table");
+ this.Protocol = new BaseField(null, FieldType.NUMBER, "Routing protocol");
+ }
}
/**
@@ -265,264 +536,432 @@ class RouteSection {
* @class NetworkConfiguration
*/
class NetworkConfiguration {
- constructor() {
- this.Match = new MatchSection();
- this.Link = new LinkSection();
- this.Network = new NetworkSection();
- this.DHCP = new DHCPSection();
- this.Address = [];
- this.Route = [];
- }
-
- /**
- * Get schema for structured editor
- * @returns {Object} Schema definition
- */
- getSchema() {
- return {
- Match: this._getSectionSchema(this.Match),
- Link: this._getSectionSchema(this.Link),
- Network: this._getSectionSchema(this.Network),
- DHCP: this._getSectionSchema(this.DHCP),
- 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)) {
- schema[key] = {
- value: field.value,
- type: field.type,
- description: field.description,
- options: field.options
- };
- }
- 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 {
- itemSchema: this._getSectionSchema(template),
- items: this[sectionName].map(item => this._getSectionSchema(item))
- };
- }
-
- /**
- * 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;
- let currentAddress = null;
- let currentRoute = 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();
-
- // Start new array sections
- if (currentSection === 'address') {
- currentAddress = new AddressSection();
- config.Address.push(currentAddress);
- } else if (currentSection === 'route') {
- currentRoute = new RouteSection();
- config.Route.push(currentRoute);
- }
- 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, currentAddress, currentRoute);
- }
- }
-
- return config;
- }
-
- /**
- * Set configuration value
- * @private
- * @param {string} section - Section name
- * @param {string} key - Key name
- * @param {string} value - Value
- * @param {AddressSection} currentAddress - Current address section
- * @param {RouteSection} currentRoute - Current route section
- */
- _setValue(section, key, value, currentAddress, currentRoute) {
- 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':
- if (currentAddress) {
- this._setAddressValue(currentAddress, key, value);
- }
- break;
- case 'route':
- if (currentRoute) {
- this._setRouteValue(currentRoute, key, value);
- }
- break;
- }
- }
-
- _setMatchValue(key, value) {
- if (this.Match[key] !== undefined) {
- this.Match[key].value = value;
- }
- }
-
- _setLinkValue(key, value) {
- if (this.Link[key] !== undefined) {
- this.Link[key].value = value;
- }
- }
-
- _setNetworkValue(key, value) {
- if (this.Network[key] !== undefined) {
- this.Network[key].value = value;
- }
- }
-
- _setDHCPValue(key, value) {
- if (this.DHCP[key] !== undefined) {
- this.DHCP[key].value = value;
- }
- }
-
- _setAddressValue(address, key, value) {
- if (address[key] !== undefined) {
- address[key].value = value;
- }
- }
-
- _setRouteValue(route, key, value) {
- if (route[key] !== undefined) {
- route[key].value = value;
- }
- }
-
- /**
- * Convert to systemd network configuration format
- * @returns {string}
- */
- 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));
- }
-
- // [Address] sections
- this.Address.forEach(addr => {
- if (this._hasSectionValues(addr)) {
- sections.push('[Address]');
- sections.push(...this._formatSection(addr));
- }
- });
-
- // [Route] sections
- this.Route.forEach(route => {
- if (this._hasSectionValues(route)) {
- sections.push('[Route]');
- sections.push(...this._formatSection(route));
- }
- });
-
- return `${sections.join('\n')}\n`;
- }
-
- _hasSectionValues(section) {
- return Object.values(section).some(field =>
- field.value !== null && field.value !== undefined && field.value !== ''
- );
- }
-
- _formatSection(section) {
- const lines = [];
- for (const [key, field] of Object.entries(section)) {
- if (field.value !== null && field.value !== undefined && field.value !== '') {
- lines.push(`${key}=${field.toString()}`);
- }
- }
- return lines;
- }
+ #match;
+ #link;
+ #network;
+ #dhcp;
+ #dhcpv4;
+ #dhcpv6;
+ #ipv6AcceptRA;
+ #slaac;
+ #address;
+ #route;
+
+ constructor() {
+ this.#match = new MatchSection();
+ this.#link = new LinkSection();
+ this.#network = new NetworkSection();
+ this.#dhcp = new DHCPSection();
+ this.#dhcpv4 = new DHCPv4Section();
+ this.#dhcpv6 = new DHCPv6Section();
+ this.#ipv6AcceptRA = new IPv6AcceptRASection();
+ this.#slaac = new SLAACSection();
+ this.#address = [];
+ this.#route = [];
+ }
+
+ /**
+ * Get Match section
+ * @returns {MatchSection}
+ */
+ 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 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];
+ }
+
+ /**
+ * Get schema for structured editor
+ * @returns {Object} Schema definition
+ */
+ getSchema() {
+ return {
+ Match: this.#getSectionSchema(this.#match),
+ Link: this.#getSectionSchema(this.#link),
+ Network: this.#getSectionSchema(this.#network),
+ DHCP: this.#getSectionSchema(this.#dhcp),
+ DHCPv4: this.#getSectionSchema(this.#dhcpv4),
+ DHCPv6: this.#getSectionSchema(this.#dhcpv6),
+ IPv6AcceptRA: this.#getSectionSchema(this.#ipv6AcceptRA),
+ 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)) {
+ schema[key] = {
+ value: field.value,
+ type: field.type,
+ description: field.description,
+ options: field.options,
+ };
+ }
+ 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 {
+ itemSchema: this.#getSectionSchema(template),
+ items: this[sectionName].map((item) => this.#getSectionSchema(item)),
+ };
+ }
+
+ /**
+ * Parse systemd network configuration from text
+ * @static
+ * @param {string} configText - Configuration file content
+ * @returns {NetworkConfiguration}
+ */
+ static fromSystemdConfiguration(configText) {
+ const config = new NetworkConfiguration();
+ const lines = configText.split("\n");
+ let currentSection = null;
+ let currentAddress = null;
+ let currentRoute = 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();
+
+ // Start new array sections
+ if (currentSection === "address") {
+ currentAddress = new AddressSection();
+ config.#address.push(currentAddress);
+ } else if (currentSection === "route") {
+ currentRoute = new RouteSection();
+ config.#route.push(currentRoute);
+ }
+ 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,
+ currentAddress,
+ currentRoute,
+ );
+ }
+ }
+
+ return config;
+ }
+
+ /**
+ * Set configuration value
+ * @private
+ * @param {string} section - Section name
+ * @param {string} key - Key name
+ * @param {string} value - Value
+ * @param {AddressSection} currentAddress - Current address section
+ * @param {RouteSection} currentRoute - Current route section
+ */
+ #setValue(section, key, value, currentAddress, currentRoute) {
+ 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 "dhcpv4":
+ this.#setDHCPv4Value(key, value);
+ break;
+ case "dhcpv6":
+ this.#setDHCPv6Value(key, value);
+ break;
+ case "ipv6acceptra":
+ this.#setIPv6AcceptRAValue(key, value);
+ break;
+ case "slaac":
+ this.#setSLAACValue(key, value);
+ break;
+ case "address":
+ if (currentAddress) {
+ this.#setAddressValue(currentAddress, key, value);
+ }
+ break;
+ case "route":
+ if (currentRoute) {
+ this.#setRouteValue(currentRoute, key, value);
+ }
+ break;
+ }
+ }
+
+ #setMatchValue(key, value) {
+ if (this.#match[key] !== undefined) {
+ this.#match[key].value = value;
+ }
+ }
+
+ #setLinkValue(key, value) {
+ if (this.#link[key] !== undefined) {
+ this.#link[key].value = value;
+ }
+ }
+
+ #setNetworkValue(key, value) {
+ if (this.#network[key] !== undefined) {
+ this.#network[key].value = value;
+ }
+ }
+
+ #setDHCPValue(key, value) {
+ if (this.#dhcp[key] !== undefined) {
+ this.#dhcp[key].value = value;
+ }
+ }
+
+ #setDHCPv4Value(key, value) {
+ if (this.#dhcpv4[key] !== undefined) {
+ this.#dhcpv4[key].value = value;
+ }
+ }
+
+ #setDHCPv6Value(key, value) {
+ if (this.#dhcpv6[key] !== undefined) {
+ this.#dhcpv6[key].value = value;
+ }
+ }
+
+ #setIPv6AcceptRAValue(key, value) {
+ if (this.#ipv6AcceptRA[key] !== undefined) {
+ this.#ipv6AcceptRA[key].value = value;
+ }
+ }
+
+ #setSLAACValue(key, value) {
+ if (this.#slaac[key] !== undefined) {
+ this.#slaac[key].value = value;
+ }
+ }
+
+ #setAddressValue(address, key, value) {
+ if (address[key] !== undefined) {
+ address[key].value = value;
+ }
+ }
+
+ #setRouteValue(route, key, value) {
+ if (route[key] !== undefined) {
+ route[key].value = value;
+ }
+ }
+
+ /**
+ * Convert to systemd network configuration format
+ * @returns {string}
+ */
+ 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));
+ }
+
+ // [Address] sections
+ this.#address.forEach((addr) => {
+ if (this.#hasSectionValues(addr)) {
+ sections.push("[Address]");
+ sections.push(...this.#formatSection(addr));
+ }
+ });
+
+ // [Route] sections
+ this.#route.forEach((route) => {
+ if (this.#hasSectionValues(route)) {
+ sections.push("[Route]");
+ sections.push(...this.#formatSection(route));
+ }
+ });
+
+ return sections.length > 0 ? `${sections.join("\n")}\n` : "";
+ }
+
+ #hasSectionValues(section) {
+ return Object.values(section).some((field) => field.hasValue());
+ }
+
+ #formatSection(section) {
+ const lines = [];
+ for (const [key, field] of Object.entries(section)) {
+ if (field.hasValue()) {
+ lines.push(`${key}=${field.toString()}`);
+ }
+ }
+ return lines;
+ }
}
// Export classes
export {
- BaseField,
- MACAddress,
- IPv4Address,
- IPv6Address,
- BooleanYesNo,
- Port,
- MTU,
- MatchSection,
- LinkSection,
- NetworkSection,
- DHCPSection,
- AddressSection,
- RouteSection,
- NetworkConfiguration
+ BaseField,
+ MACAddress,
+ IPv4Address,
+ IPv6Address,
+ BooleanYesNo,
+ Port,
+ MTU,
+ MatchSection,
+ LinkSection,
+ NetworkSection,
+ DHCPSection,
+ DHCPv4Section,
+ DHCPv6Section,
+ IPv6AcceptRASection,
+ SLAACSection,
+ AddressSection,
+ RouteSection,
+ NetworkConfiguration,
};