196 lines
5.7 KiB
JavaScript
196 lines
5.7 KiB
JavaScript
/**
|
|
* 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
|
|
};
|