summaryrefslogtreecommitdiffstats
path: root/static/app.js
diff options
context:
space:
mode:
authorPetri Hienonen <petri.hienonen@gmail.com>2025-09-28 15:08:14 +0300
committerPetri Hienonen <petri.hienonen@gmail.com>2025-09-28 15:08:14 +0300
commit8489f1f83da5dcc5818401393b1f6a430eea677c (patch)
treed55c205a0b45127765c1547fef5904d08e1171a4 /static/app.js
parent289e73570674b67f7a1561cff9cec61498efc6cc (diff)
downloadnetwork-8489f1f83da5dcc5818401393b1f6a430eea677c.tar.zst
Intial version
Diffstat (limited to 'static/app.js')
-rw-r--r--static/app.js244
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}`;