From a1862888a7818ae9663b02dda48d25ef5f2ab6a6 Mon Sep 17 00:00:00 2001 From: Petri Hienonen Date: Sun, 28 Sep 2025 13:30:59 +0300 Subject: Iteration 2 --- static/structured-editor.js | 253 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 static/structured-editor.js (limited to 'static/structured-editor.js') diff --git a/static/structured-editor.js b/static/structured-editor.js new file mode 100644 index 0000000..dc46bef --- /dev/null +++ b/static/structured-editor.js @@ -0,0 +1,253 @@ +/* jshint esversion: 2024, module: true */ + +/** + * Structured Editor for systemd-networkd configuration + * @module StructuredEditor + */ + +import { NetworkConfiguration } from './systemd-network.js'; + +class StructuredEditor { + constructor(container) { + this.container = container; + this.config = new NetworkConfiguration(); + this.currentFile = ''; + } + + /** + * Load configuration from text + * @param {string} configText - Configuration text + * @param {string} filename - File name + */ + loadConfiguration(configText, filename) { + this.config = NetworkConfiguration.fromSystemdConfiguration(configText); + this.currentFile = filename; + this.render(); + } + + /** + * Render the structured editor + */ + render() { + this.container.innerHTML = this._createEditorHTML(); + this._attachEventListeners(); + } + + /** + * Create editor HTML structure + * @private + * @returns {string} + */ + _createEditorHTML() { + return ` +
+
+ ${this._createMatchSection()} + ${this._createLinkSection()} + ${this._createNetworkSection()} + ${this._createDHCPSection()} + ${this._createAddressSections()} + ${this._createRouteSections()} +
+
+ + + +
+
+ `; + } + + _createMatchSection() { + const match = this.config.Match; + return ` +
+

[Match]

+
+ ${this._createInputRow('MACAddress', match.MACAddress.join(' '), 'Space-separated MAC addresses')} + ${this._createInputRow('Name', match.Name.join(' '), 'Interface names')} + ${this._createInputRow('Driver', match.Driver.join(' '), 'Driver names')} + ${this._createInputRow('Type', match.Type.join(' '), 'Interface types')} +
+
+ `; + } + + _createLinkSection() { + const link = this.config.Link; + return ` +
+

[Link]

+
+ ${this._createInputRow('MACAddress', link.MACAddress, 'Hardware address')} + ${this._createInputRow('MTUBytes', link.MTUBytes, 'Maximum transmission unit')} + ${this._createSelectRow('WakeOnLan', link.WakeOnLan, ['', 'phy', 'unicast', 'broadcast', 'arp', 'magic'], 'Wake-on-LAN')} +
+
+ `; + } + + _createNetworkSection() { + const network = this.config.Network; + return ` +
+

[Network]

+
+ ${this._createInputRow('Description', network.Description, 'Interface description')} + ${this._createSelectRow('DHCP', network.DHCP.join(' '), ['', 'yes', 'no', 'ipv4', 'ipv6'], 'DHCP client')} + ${this._createInputRow('DNS', network.DNS.join(' '), 'DNS servers')} + ${this._createInputRow('NTP', network.NTP.join(' '), 'NTP servers')} + ${this._createSelectRow('IPv6PrivacyExtensions', network.IPv6PrivacyExtensions, ['', 'yes', 'no', 'prefer-public'], 'IPv6 privacy extensions')} +
+
+ `; + } + + _createDHCPSection() { + const dhcp = this.config.DHCP; + return ` +
+

[DHCP]

+
+ ${this._createSelectRow('UseDNS', dhcp.UseDNS, ['', 'yes', 'no'], 'Use DNS from DHCP')} + ${this._createSelectRow('UseNTP', dhcp.UseNTP, ['', 'yes', 'no'], 'Use NTP from DHCP')} + ${this._createInputRow('RouteMetric', dhcp.RouteMetric, 'Route metric')} +
+
+ `; + } + + _createAddressSections() { + return this.config.Address.map((addr, index) => ` +
+

[Address] ${index > 0 ? `#${index + 1}` : ''}

+
+ ${this._createInputRow('Address', addr.Address, 'IP address with prefix')} + ${this._createInputRow('Peer', addr.Peer, 'Peer address')} + +
+
+ `).join(''); + } + + _createRouteSections() { + return this.config.Route.map((route, index) => ` +
+

[Route] ${index > 0 ? `#${index + 1}` : ''}

+
+ ${this._createInputRow('Gateway', route.Gateway, 'Gateway address')} + ${this._createInputRow('Destination', route.Destination, 'Destination prefix')} + ${this._createInputRow('Metric', route.Metric, 'Route metric')} + +
+
+ `).join(''); + } + + _createInputRow(key, value, description) { + return ` +
+ + +
+ `; + } + + _createSelectRow(key, value, options, description) { + const optionsHTML = options.map(opt => + `` + ).join(''); + + return ` +
+ + +
+ `; + } + + _attachEventListeners() { + // Input changes + this.container.querySelectorAll('.config-input').forEach(input => { + input.addEventListener('change', (e) => this._onInputChange(e)); + }); + + // Select changes + this.container.querySelectorAll('.config-select').forEach(select => { + select.addEventListener('change', (e) => this._onSelectChange(e)); + }); + + // Add sections + this.container.querySelector('#addAddressSection')?.addEventListener('click', () => { + this.config.Address.push(new (await import('./systemd-network.js')).AddressSection()); + this.render(); + }); + + this.container.querySelector('#addRouteSection')?.addEventListener('click', () => { + this.config.Route.push(new (await import('./systemd-network.js')).RouteSection()); + this.render(); + }); + + // Remove sections + this.container.querySelectorAll('.remove-section').forEach(btn => { + btn.addEventListener('click', (e) => this._onRemoveSection(e)); + }); + } + + _onInputChange(event) { + const input = event.target; + const key = input.dataset.key; + const value = input.value; + + // Update configuration based on context + this._updateConfigValue(key, value); + } + + _onSelectChange(event) { + const select = event.target; + const key = select.dataset.key; + const value = select.value; + + this._updateConfigValue(key, value); + } + + _updateConfigValue(key, value) { + // This would need to be implemented based on the current section context + console.log(`Update ${key} = ${value}`); + // Implementation would update the this.config object + } + + _onRemoveSection(event) { + const btn = event.target; + const type = btn.dataset.type; + const index = parseInt(btn.dataset.index); + + if (type === 'address') { + this.config.Address.splice(index, 1); + } else if (type === 'route') { + this.config.Route.splice(index, 1); + } + + this.render(); + } + + /** + * Get current configuration as text + * @returns {string} + */ + getConfigurationText() { + return this.config.toSystemdConfiguration(); + } +} + +export { StructuredEditor }; -- cgit v1.2.3-70-g09d2