/* 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 };