Add export and import functionality for service configurations with XML/JSON support
This commit is contained in:
195
js/export-import.js
Normal file
195
js/export-import.js
Normal file
@@ -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 = '<?xml version="1.0" encoding="utf-8"?>\n';
|
||||
xml += `<services${config.tailscaleIp ? ` tailscale-ip="${config.tailscaleIp}"` : ''}>\n`;
|
||||
|
||||
config.groups.forEach(group => {
|
||||
xml += ` <group name="${escapeXml(group.name)}">\n`;
|
||||
|
||||
group.services.forEach(service => {
|
||||
xml += ' <service';
|
||||
Object.entries(service).forEach(([key, value]) => {
|
||||
xml += ` ${key}="${escapeXml(value)}"`;
|
||||
});
|
||||
xml += ' />\n';
|
||||
});
|
||||
|
||||
xml += ' </group>\n';
|
||||
});
|
||||
|
||||
xml += '</services>\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
|
||||
};
|
||||
Reference in New Issue
Block a user