diff options
Diffstat (limited to 'static/app.js')
| -rw-r--r-- | static/app.js | 244 |
1 files changed, 200 insertions, 44 deletions
diff --git a/static/app.js b/static/app.js index 3599cba..22e52b5 100644 --- a/static/app.js +++ b/static/app.js @@ -2,6 +2,7 @@ import { ApiClient } from './api-client.js'; import { ConfigManager } from './config-manager.js'; +import { EditorMode, ThemeMode, ValidationState } from './enums.js'; import { InterfaceRenderer } from './interface-renderer.js'; import { StructuredEditor } from './structured-editor.js'; import { ThemeManager } from './theme-manager.js'; @@ -19,17 +20,13 @@ class Application { this.state = { currentInterface: null, interfaces: [], - editorMode: 'raw', // 'raw' or 'structured' - currentConfigFile: null + editorMode: EditorMode.STRUCTURED, + currentConfigFile: null, + theme: ThemeMode.DARK }; // Initialize modules this.themeManager = new ThemeManager(elements); - this.apiClient = new ApiClient(); - this.interfaceRenderer = new InterfaceRenderer(elements, this.state); - this.configManager = new ConfigManager(elements, this.apiClient, this.state); - - // Structured editor will be initialized after DOM is ready this.structuredEditor = null; // Create editor mode toggle UI @@ -44,8 +41,6 @@ class Application { this.themeManager.init(); this.setupEventListeners(); this.loadStatus(); - - // Initialize structured editor now that DOM is ready this.initializeStructuredEditor(); } @@ -56,11 +51,11 @@ class Application { createEditorModeToggle() { const editorToggleHTML = ` <div class="editor-mode-toggle" style="margin-bottom: var(--spacing-l);"> - <button class="button small ${this.state.editorMode === 'raw' ? 'active' : ''}" + <button class="button small ${this.state.editorMode === EditorMode.RAW ? 'active' : ''}" data-mode="raw" id="rawEditorBtn"> 📝 Raw Editor </button> - <button class="button small ${this.state.editorMode === 'structured' ? 'active' : ''}" + <button class="button small ${this.state.editorMode === EditorMode.STRUCTURED ? 'active' : ''}" data-mode="structured" id="structuredEditorBtn"> 🏗️ Structured Editor </button> @@ -135,19 +130,19 @@ class Application { // Configs panel - raw editor this.elements.buttons.refreshConfigs?.addEventListener('click', - () => this.configManager.refreshConfigs()); + () => this.handleRefreshConfigs()); this.elements.buttons.saveConfig?.addEventListener('click', () => this.handleSaveConfig()); this.elements.buttons.validateConfig?.addEventListener('click', - () => this.configManager.validateConfig()); + () => this.handleValidateConfig()); this.elements.inputs.configSelect?.addEventListener('change', () => this.handleConfigFileChange()); // Editor mode toggle this.elements.editorButtons?.raw?.addEventListener('click', - () => this.setEditorMode('raw')); + () => this.setEditorMode(EditorMode.RAW)); this.elements.editorButtons?.structured?.addEventListener('click', - () => this.setEditorMode('structured')); + () => this.setEditorMode(EditorMode.STRUCTURED)); // Logs panel this.elements.buttons.refreshLogs?.addEventListener('click', () => this.loadLogs()); @@ -160,15 +155,53 @@ class Application { document.addEventListener('touchstart', this.handleTouchStart, { passive: true }); } - // Add event handlers for structured editor - handleAddSection(detail) { - console.log('Add section:', detail); - // Implement section addition logic + /** + * Handle touch events for better mobile support + * @method handleTouchStart + * @param {TouchEvent} event + */ + handleTouchStart = (event) => { + // Add visual feedback for touch + if (event.target.classList.contains('button') || event.target.classList.contains('nav-button')) { + event.target.style.opacity = '0.7'; + setTimeout(() => { + event.target.style.opacity = ''; + }, 150); + } + }; + + /** + * Handle refresh configuration files + * @method handleRefreshConfigs + */ + async handleRefreshConfigs() { + try { + const files = await ConfigManager.refreshConfigs(); + this.updateConfigSelect(files); + + if (files.length > 0) { + this.state.currentConfigFile = files[0]; + await this.handleConfigFileChange(); + } else { + this.elements.inputs.cfgEditor.value = ''; + this.state.currentConfigFile = null; + } + } catch (error) { + alert(error.message); + } } - handleRemoveSection(detail) { - console.log('Remove section:', detail); - // Implement section removal logic + /** + * Update configuration select element + * @method updateConfigSelect + * @param {Array} files - File names + */ + updateConfigSelect(files) { + this.elements.inputs.configSelect.innerHTML = ''; + files.forEach(file => { + const option = new Option(file, file); + this.elements.inputs.configSelect.add(option); + }); } /** @@ -181,18 +214,31 @@ class Application { this.state.currentConfigFile = name; - if (this.state.editorMode === 'raw') { - await this.configManager.loadConfig(); + if (this.state.editorMode === EditorMode.RAW) { + await this.loadConfigForRawEditor(); } else { await this.loadConfigForStructuredEditor(); } } /** + * Load config for raw editor + * @method loadConfigForRawEditor + */ + async loadConfigForRawEditor() { + try { + const content = await ConfigManager.loadConfig(this.state.currentConfigFile); + this.elements.inputs.cfgEditor.value = content; + this.clearValidationResult(); + } catch (error) { + alert(error.message); + } + } + + /** * Load config for structured editor * @method loadConfigForStructuredEditor */ - async loadConfigForStructuredEditor() { if (!this.structuredEditor) { console.error('Structured editor not initialized'); @@ -200,11 +246,11 @@ class Application { } try { - const text = await this.apiClient.getText(`/api/config/${encodeURIComponent(this.state.currentConfigFile)}`); + const content = await ConfigManager.loadConfig(this.state.currentConfigFile); // Parse configuration and get schema const { NetworkConfiguration } = await import('./systemd-network.js'); - const config = NetworkConfiguration.fromSystemdConfiguration(text); + const config = NetworkConfiguration.fromSystemdConfiguration(content); const schema = config.getSchema(); // Load schema into structured editor @@ -220,26 +266,104 @@ class Application { } /** + * Handle add section from structured editor + * @method handleAddSection + * @param {Object} detail - Event detail + */ + handleAddSection(detail) { + console.log('Add section:', detail); + // TODO: Implement section addition logic + // This would involve updating the schema and re-rendering + } + + /** + * Handle remove section from structured editor + * @method handleRemoveSection + * @param {Object} detail - Event detail + */ + handleRemoveSection(detail) { + console.log('Remove section:', detail); + // TODO: Implement section removal logic + // This would involve updating the schema and re-rendering + } + + /** + * Handle validate configuration + * @method handleValidateConfig + */ + async handleValidateConfig() { + const name = this.state.currentConfigFile; + if (!name) { + alert('Please select a configuration file first.'); + return; + } + + let content; + if (this.state.editorMode === EditorMode.RAW) { + content = this.elements.inputs.cfgEditor.value; + } else if (this.structuredEditor) { + content = this.structuredEditor.getConfigurationText(); + } else { + alert('Structured editor not available'); + return; + } + + this.setValidationResult(ValidationState.PENDING); + + try { + const result = await ConfigManager.validateConfig(name, content); + + if (result.ok) { + this.setValidationResult(ValidationState.SUCCESS); + } else { + this.setValidationResult(ValidationState.ERROR, result.output); + } + } catch (error) { + this.setValidationResult(ValidationState.ERROR, error.message); + } + } + + /** + * Set validation result + * @method setValidationResult + * @param {Symbol} state - Validation state + * @param {string} [message] - Additional message + */ + setValidationResult(state, message = '') { + this.elements.outputs.validateResult.textContent = ConfigManager.getValidationMessage(state, message); + this.elements.outputs.validateResult.className = ConfigManager.getValidationClass(state); + } + + /** + * Clear validation result + * @method clearValidationResult + */ + clearValidationResult() { + this.elements.outputs.validateResult.textContent = ''; + this.elements.outputs.validateResult.className = ''; + } + + /** * Set editor mode (raw or structured) * @method setEditorMode - * @param {string} mode - Editor mode + * @param {Symbol} mode - Editor mode */ setEditorMode(mode) { this.state.editorMode = mode; // Update UI if (this.elements.editorButtons?.raw && this.elements.editorButtons?.structured) { - this.elements.editorButtons.raw.classList.toggle('active', mode === 'raw'); - this.elements.editorButtons.structured.classList.toggle('active', mode === 'structured'); + this.elements.editorButtons.raw.classList.toggle('active', mode === EditorMode.RAW); + this.elements.editorButtons.structured.classList.toggle('active', mode === EditorMode.STRUCTURED); } if (this.elements.editorContainers?.raw && this.elements.editorContainers?.structured) { - this.elements.editorContainers.raw.style.display = mode === 'raw' ? 'block' : 'none'; - this.elements.editorContainers.structured.style.display = mode === 'structured' ? 'block' : 'none'; + this.elements.editorContainers.raw.style.display = mode === EditorMode.RAW ? 'block' : 'none'; + this.elements.editorContainers.structured.style.display = mode === EditorMode.STRUCTURED ? 'block' : 'none'; } // If switching to structured mode and we have a config file loaded, load it - if (mode === 'structured' && this.state.currentConfigFile && this.structuredEditor) { + if (mode === EditorMode.STRUCTURED && this.state.currentConfigFile && this.structuredEditor) { this.loadConfigForStructuredEditor(); } } @@ -263,7 +387,7 @@ class Application { try { let content; - if (this.state.editorMode === 'raw') { + if (this.state.editorMode === EditorMode.RAW) { content = this.elements.inputs.cfgEditor.value; } else if (this.structuredEditor) { content = this.structuredEditor.getConfigurationText(); @@ -271,12 +395,12 @@ class Application { throw new Error('Structured editor not available'); } - const result = await this.apiClient.post('/api/save', { name, content, restart }); + const result = await ConfigManager.saveConfig(name, content, restart); alert(`Saved: ${result.status ?? 'ok'}`); // Refresh the config in structured editor if needed - if (this.state.editorMode === 'structured' && this.structuredEditor) { - await this.structuredEditor.loadConfiguration(content, name); + if (this.state.editorMode === EditorMode.STRUCTURED && this.structuredEditor) { + await this.loadConfigForStructuredEditor(); } } catch (error) { alert(`Save failed: ${error.message}`); @@ -307,7 +431,7 @@ class Application { // Load panel-specific data const panelActions = { status: () => this.loadStatus(), - configs: () => this.configManager.refreshConfigs(), + configs: () => this.handleRefreshConfigs(), logs: () => this.loadLogs(), }; @@ -320,14 +444,29 @@ class Application { */ async loadStatus() { try { - const data = await this.apiClient.get('/api/status'); + const data = await ApiClient.getNetworkStatus(); this.state.interfaces = data.Interfaces ?? []; - this.interfaceRenderer.renderInterfaceTabs(this.state.interfaces); + + // Render interface tabs + const tabsHTML = InterfaceRenderer.renderInterfaceTabs(this.state.interfaces, this.state.currentInterface); + this.elements.outputs.ifaceTabs.innerHTML = tabsHTML; // Show first interface by default if (this.state.interfaces.length > 0 && !this.state.currentInterface) { - this.interfaceRenderer.showInterfaceDetails(this.state.interfaces[0]); + this.showInterfaceDetails(this.state.interfaces[0]); } + + // Add event listeners to tabs + this.elements.outputs.ifaceTabs.querySelectorAll('.interface-tab').forEach(tab => { + tab.addEventListener('click', (event) => { + const ifaceName = event.currentTarget.dataset.interface; + const iface = this.state.interfaces.find(i => i.Name === ifaceName); + if (iface) { + this.showInterfaceDetails(iface); + } + }); + }); + } catch (error) { this.elements.outputs.ifaceDetails.innerHTML = `<div class="error-message">Error loading status: ${error.message}</div>`; @@ -335,12 +474,29 @@ class Application { } /** + * Show interface details + * @method showInterfaceDetails + * @param {Object} iface - Interface object + */ + showInterfaceDetails(iface) { + this.state.currentInterface = iface; + + // Update active tab + this.elements.outputs.ifaceTabs.querySelectorAll('.interface-tab').forEach(tab => { + tab.classList.toggle('active', tab.dataset.interface === iface.Name); + }); + + const detailsHTML = InterfaceRenderer.showInterfaceDetails(iface); + this.elements.outputs.ifaceDetails.innerHTML = detailsHTML; + } + + /** * Load system logs * @method loadLogs */ async loadLogs() { try { - const text = await this.apiClient.getText('/api/logs'); + const text = await ApiClient.getSystemLogs(); this.elements.outputs.logsArea.textContent = text; } catch (error) { this.elements.outputs.logsArea.textContent = `Error: ${error.message}`; @@ -355,7 +511,7 @@ class Application { if (!confirm('Restart systemd-networkd? Active connections may be reset.')) return; try { - const result = await this.apiClient.post('/api/reload'); + const result = await ApiClient.restartNetworkd(); this.elements.outputs.cmdResult.textContent = `Success: ${JSON.stringify(result)}`; } catch (error) { this.elements.outputs.cmdResult.textContent = `Error: ${error.message}`; @@ -370,7 +526,7 @@ class Application { if (!confirm('Reboot device now?')) return; try { - const result = await this.apiClient.post('/api/reboot'); + const result = await ApiClient.rebootDevice(); this.elements.outputs.cmdResult.textContent = `Success: ${JSON.stringify(result)}`; } catch (error) { this.elements.outputs.cmdResult.textContent = `Error: ${error.message}`; |
