summaryrefslogtreecommitdiffstats
path: root/static/structured-editor.js
diff options
context:
space:
mode:
Diffstat (limited to 'static/structured-editor.js')
-rw-r--r--static/structured-editor.js468
1 files changed, 297 insertions, 171 deletions
diff --git a/static/structured-editor.js b/static/structured-editor.js
index dc46bef..4de9217 100644
--- a/static/structured-editor.js
+++ b/static/structured-editor.js
@@ -2,44 +2,54 @@
/**
* Structured Editor for systemd-networkd configuration
- * @module StructuredEditor
+ * @class StructuredEditor
*/
+class StructuredEditor {
+ constructor(container) {
+ this.container = container;
+ this.config = null;
+ this.currentFile = "";
+ this.systemdNetworkModule = null;
+ }
-import { NetworkConfiguration } from './systemd-network.js';
+ /**
+ * Load configuration from text
+ * @param {string} configText - Configuration text
+ * @param {string} filename - File name
+ */
+ async loadConfiguration(configText, filename) {
+ // Dynamically import the systemd-network module
+ if (!this.systemdNetworkModule) {
+ this.systemdNetworkModule = await import("./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 `
+ const { NetworkConfiguration } = this.systemdNetworkModule;
+ this.config = NetworkConfiguration.fromSystemdConfiguration(configText);
+ this.currentFile = filename;
+ this.render();
+ }
+
+ /**
+ * Render the structured editor
+ */
+ render() {
+ if (!this.config) {
+ this.container.innerHTML =
+ '<div class="error-message">No configuration loaded</div>';
+ return;
+ }
+
+ this.container.innerHTML = this._createEditorHTML();
+ this._attachEventListeners();
+ }
+
+ /**
+ * Create editor HTML structure
+ * @private
+ * @returns {string}
+ */
+ _createEditorHTML() {
+ return `
<div class="structured-editor">
<div class="editor-sections">
${this._createMatchSection()}
@@ -50,204 +60,320 @@ class StructuredEditor {
${this._createRouteSections()}
</div>
<div class="editor-actions">
- <button class="button" id="addAddressSection">Add Address</button>
- <button class="button" id="addRouteSection">Add Route</button>
- <button class="button secondary" id="showRawConfig">Show Raw</button>
+ <button class="button secondary" id="addAddressSection">Add Address</button>
+ <button class="button secondary" id="addRouteSection">Add Route</button>
</div>
</div>
`;
- }
+ }
- _createMatchSection() {
- const match = this.config.Match;
- return `
+ _createMatchSection() {
+ const match = this.config.Match;
+ return `
<div class="config-section">
<h4>[Match]</h4>
<div class="config-table">
- ${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')}
+ ${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")}
</div>
</div>
`;
- }
+ }
- _createLinkSection() {
- const link = this.config.Link;
- return `
+ _createLinkSection() {
+ const link = this.config.Link;
+ return `
<div class="config-section">
<h4>[Link]</h4>
<div class="config-table">
- ${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')}
+ ${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")}
</div>
</div>
`;
- }
+ }
- _createNetworkSection() {
- const network = this.config.Network;
- return `
+ _createNetworkSection() {
+ const network = this.config.Network;
+ return `
<div class="config-section">
<h4>[Network]</h4>
<div class="config-table">
- ${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')}
+ ${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")}
</div>
</div>
`;
- }
+ }
- _createDHCPSection() {
- const dhcp = this.config.DHCP;
- return `
+ _createDHCPSection() {
+ const dhcp = this.config.DHCP;
+ return `
<div class="config-section">
<h4>[DHCP]</h4>
<div class="config-table">
- ${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')}
+ ${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")}
</div>
</div>
`;
- }
+ }
+
+ _createAddressSections() {
+ if (!this.config.Address || this.config.Address.length === 0) {
+ return '<div class="config-section"><h4>[Address]</h4><p class="no-items">No address sections</p></div>';
+ }
- _createAddressSections() {
- return this.config.Address.map((addr, index) => `
+ return this.config.Address.map(
+ (addr, index) => `
<div class="config-section">
- <h4>[Address] ${index > 0 ? `#${index + 1}` : ''}</h4>
+ <h4>[Address] ${index > 0 ? `#${index + 1}` : ""}</h4>
<div class="config-table">
- ${this._createInputRow('Address', addr.Address, 'IP address with prefix')}
- ${this._createInputRow('Peer', addr.Peer, 'Peer address')}
- <button class="button small remove-section" data-type="address" data-index="${index}">Remove</button>
+ ${this._createInputRow("Address", addr.Address || "", "IP address with prefix")}
+ ${this._createInputRow("Peer", addr.Peer || "", "Peer address")}
+ <button class="button small warning remove-section" data-type="address" data-index="${index}">Remove</button>
</div>
</div>
- `).join('');
- }
+ `,
+ ).join("");
+ }
- _createRouteSections() {
- return this.config.Route.map((route, index) => `
+ _createRouteSections() {
+ if (!this.config.Route || this.config.Route.length === 0) {
+ return '<div class="config-section"><h4>[Route]</h4><p class="no-items">No route sections</p></div>';
+ }
+
+ return this.config.Route.map(
+ (route, index) => `
<div class="config-section">
- <h4>[Route] ${index > 0 ? `#${index + 1}` : ''}</h4>
+ <h4>[Route] ${index > 0 ? `#${index + 1}` : ""}</h4>
<div class="config-table">
- ${this._createInputRow('Gateway', route.Gateway, 'Gateway address')}
- ${this._createInputRow('Destination', route.Destination, 'Destination prefix')}
- ${this._createInputRow('Metric', route.Metric, 'Route metric')}
- <button class="button small remove-section" data-type="route" data-index="${index}">Remove</button>
+ ${this._createInputRow("Gateway", route.Gateway || "", "Gateway address")}
+ ${this._createInputRow("Destination", route.Destination || "", "Destination prefix")}
+ ${this._createInputRow("Metric", route.Metric || "", "Route metric")}
+ <button class="button small warning remove-section" data-type="route" data-index="${index}">Remove</button>
</div>
</div>
- `).join('');
- }
+ `,
+ ).join("");
+ }
- _createInputRow(key, value, description) {
- return `
+ _createInputRow(key, value, description) {
+ return `
<div class="config-row">
<label class="config-label" title="${description}">
<abbr title="${description}">${key}</abbr>:
</label>
<input type="text"
class="config-input"
+ data-section="${this._getCurrentSection()}"
data-key="${key}"
- value="${value || ''}"
+ value="${value}"
placeholder="${description}">
</div>
`;
- }
+ }
- _createSelectRow(key, value, options, description) {
- const optionsHTML = options.map(opt =>
- `<option value="${opt}" ${opt === value ? 'selected' : ''}>${opt || '(not set)'}</option>`
- ).join('');
+ _createSelectRow(key, value, options, description) {
+ const optionsHTML = options
+ .map(
+ (opt) =>
+ `<option value="${opt}" ${opt === value ? "selected" : ""}>${opt || "(not set)"}</option>`,
+ )
+ .join("");
- return `
+ return `
<div class="config-row">
<label class="config-label" title="${description}">
<abbr title="${description}">${key}</abbr>:
</label>
- <select class="config-select" data-key="${key}">
+ <select class="config-select" data-section="${this._getCurrentSection()}" data-key="${key}">
${optionsHTML}
</select>
</div>
`;
- }
-
- _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();
- }
+ }
+
+ /**
+ * Get current section name for event handling
+ * @private
+ * @returns {string}
+ */
+ _getCurrentSection() {
+ // This is a simplified implementation - you might want to track the current section more precisely
+ return "network";
+ }
+
+ /**
+ * Attach event listeners to the editor
+ * @private
+ */
+ _attachEventListeners() {
+ // Input changes
+ this.container.querySelectorAll(".config-input").forEach((input) => {
+ input.addEventListener("input", (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._addAddressSection();
+ });
+
+ this.container
+ .querySelector("#addRouteSection")
+ ?.addEventListener("click", () => {
+ this._addRouteSection();
+ });
+
+ // Remove sections
+ this.container.querySelectorAll(".remove-section").forEach((btn) => {
+ btn.addEventListener("click", (e) => this._onRemoveSection(e));
+ });
+ }
+
+ /**
+ * Handle input changes
+ * @private
+ * @param {Event} event
+ */
+ _onInputChange(event) {
+ const input = event.target;
+ const section = input.dataset.section;
+ const key = input.dataset.key;
+ const value = input.value;
+
+ this._updateConfigValue(section, key, value);
+ }
+
+ /**
+ * Handle select changes
+ * @private
+ * @param {Event} event
+ */
+ _onSelectChange(event) {
+ const select = event.target;
+ const section = select.dataset.section;
+ const key = select.dataset.key;
+ const value = select.value;
+
+ this._updateConfigValue(section, key, value);
+ }
+
+ /**
+ * Update configuration value
+ * @private
+ * @param {string} section - Section name
+ * @param {string} key - Key name
+ * @param {string} value - Value
+ */
+ _updateConfigValue(section, key, value) {
+ if (!this.config) return;
+
+ // Simplified implementation - you'll want to expand this based on your systemd-network.js structure
+ console.log(`Update ${section}.${key} = ${value}`);
+
+ // Example update logic - you'll need to implement this based on your actual data structure
+ switch (section) {
+ case "match":
+ if (["MACAddress", "Name", "Driver", "Type"].includes(key)) {
+ this.config.Match[key] = value.split(" ").filter((v) => v.trim());
+ } else {
+ this.config.Match[key] = value;
+ }
+ break;
+ case "link":
+ this.config.Link[key] = value;
+ break;
+ case "network":
+ if (["DNS", "NTP", "DHCP", "Domains", "IPForward"].includes(key)) {
+ this.config.Network[key] = value.split(" ").filter((v) => v.trim());
+ } else {
+ this.config.Network[key] = value;
+ }
+ break;
+ case "dhcp":
+ this.config.DHCP[key] = value;
+ break;
+ }
+ }
+
+ /**
+ * Add a new address section
+ * @private
+ */
+ async _addAddressSection() {
+ if (!this.systemdNetworkModule) {
+ this.systemdNetworkModule = await import("./systemd-network.js");
+ }
+
+ const { AddressSection } = this.systemdNetworkModule;
+ this.config.Address.push(new AddressSection());
+ this.render();
+ }
+
+ /**
+ * Add a new route section
+ * @private
+ */
+ async _addRouteSection() {
+ if (!this.systemdNetworkModule) {
+ this.systemdNetworkModule = await import("./systemd-network.js");
+ }
+
+ const { RouteSection } = this.systemdNetworkModule;
+ this.config.Route.push(new RouteSection());
+ this.render();
+ }
+
+ /**
+ * Remove a section
+ * @private
+ * @param {Event} event
+ */
+ _onRemoveSection(event) {
+ const btn = event.target;
+ const type = btn.dataset.type;
+ const index = parseInt(btn.dataset.index);
+
+ if (type === "address" && this.config.Address) {
+ this.config.Address.splice(index, 1);
+ } else if (type === "route" && this.config.Route) {
+ this.config.Route.splice(index, 1);
+ }
+
+ this.render();
+ }
+
+ /**
+ * Get current configuration as text
+ * @returns {string}
+ */
+ getConfigurationText() {
+ return this.config ? this.config.toSystemdConfiguration() : "";
+ }
+
+ /**
+ * Get current configuration object
+ * @returns {Object|null}
+ */
+ getConfiguration() {
+ return this.config;
+ }
}
export { StructuredEditor };
+