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