From 6b40f6180595575a3013750c9b358334343f9a5f Mon Sep 17 00:00:00 2001 From: MayaChat Date: Mon, 24 Nov 2025 12:47:17 -0500 Subject: [PATCH] Add export and import functionality for service configurations with XML/JSON support --- js/export-import.js | 195 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 js/export-import.js diff --git a/js/export-import.js b/js/export-import.js new file mode 100644 index 0000000..888cfb1 --- /dev/null +++ b/js/export-import.js @@ -0,0 +1,195 @@ +/** + * export-import.js - Export and import service configurations + */ + +// Export services configuration to JSON +function exportConfiguration() { + // Fetch services.xml and convert to JSON + fetch('/services.xml') + .then(response => response.text()) + .then(xmlText => { + const parser = new DOMParser(); + const xmlDoc = parser.parseFromString(xmlText, 'text/xml'); + + const config = { + version: '1.0', + exportDate: new Date().toISOString(), + tailscaleIp: xmlDoc.documentElement.getAttribute('tailscale-ip'), + groups: [] + }; + + // Parse groups + const groups = xmlDoc.querySelectorAll('group'); + groups.forEach(group => { + const groupData = { + name: group.getAttribute('name'), + services: [] + }; + + const services = group.querySelectorAll('service'); + services.forEach(service => { + const serviceData = {}; + + // Get all attributes + Array.from(service.attributes).forEach(attr => { + serviceData[attr.name] = attr.value; + }); + + groupData.services.push(serviceData); + }); + + config.groups.push(groupData); + }); + + // Download JSON file + const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `services-config-${new Date().toISOString().split('T')[0]}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + showNotification('Configuration exported successfully!', 'success'); + }) + .catch(error => { + console.error('Export error:', error); + showNotification('Failed to export configuration', 'error'); + }); +} + +// Import services configuration from JSON +function importConfiguration(file) { + const reader = new FileReader(); + + reader.onload = (e) => { + try { + const config = JSON.parse(e.target.result); + + // Validate config structure + if (!config.version || !config.groups) { + throw new Error('Invalid configuration format'); + } + + // Convert JSON back to XML + let xml = '\n'; + xml += `\n`; + + config.groups.forEach(group => { + xml += ` \n`; + + group.services.forEach(service => { + xml += ' { + xml += ` ${key}="${escapeXml(value)}"`; + }); + xml += ' />\n'; + }); + + xml += ' \n'; + }); + + xml += '\n'; + + // Download the generated XML + const blob = new Blob([xml], { type: 'text/xml' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'services.xml'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + showNotification('Configuration imported! Download services.xml and replace the existing file.', 'success'); + } catch (error) { + console.error('Import error:', error); + showNotification('Failed to import configuration: ' + error.message, 'error'); + } + }; + + reader.readAsText(file); +} + +// Escape XML special characters +function escapeXml(text) { + const map = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + return text.replace(/[&<>"']/g, m => map[m]); +} + +// Show notification +function showNotification(message, type = 'info') { + const notification = document.createElement('div'); + notification.className = `notification notification-${type}`; + notification.textContent = message; + + document.body.appendChild(notification); + + // Trigger animation + setTimeout(() => notification.classList.add('show'), 10); + + // Remove after 3 seconds + setTimeout(() => { + notification.classList.remove('show'); + setTimeout(() => notification.remove(), 300); + }, 3000); +} + +// Create import/export UI +function createImportExportUI() { + const container = document.createElement('div'); + container.className = 'import-export-controls'; + + const exportBtn = document.createElement('button'); + exportBtn.className = 'control-btn'; + exportBtn.innerHTML = '📥 Export'; + exportBtn.title = 'Export configuration to JSON'; + exportBtn.addEventListener('click', exportConfiguration); + + const importBtn = document.createElement('button'); + importBtn.className = 'control-btn'; + importBtn.innerHTML = '📤 Import'; + importBtn.title = 'Import configuration from JSON'; + + const fileInput = document.createElement('input'); + fileInput.type = 'file'; + fileInput.accept = '.json'; + fileInput.style.display = 'none'; + fileInput.addEventListener('change', (e) => { + if (e.target.files.length > 0) { + importConfiguration(e.target.files[0]); + } + }); + + importBtn.addEventListener('click', () => fileInput.click()); + + container.appendChild(exportBtn); + container.appendChild(importBtn); + container.appendChild(fileInput); + + return container; +} + +// Initialize import/export functionality +function initImportExport() { + const header = document.querySelector('header'); + if (header) { + const ui = createImportExportUI(); + header.appendChild(ui); + } +} + +// Export for use in other modules +window.importExportModule = { + init: initImportExport, + exportConfiguration: exportConfiguration +};