// ========== IMPORT/EXPORT FUNCTIONALITY ========== (function() { // Export dashboard configuration async function exportDashboard() { // Fetch themes from server (source of truth) with localStorage fallback let userThemes = safeGetJSON('user-themes', {}); try { const res = await secureFetch('/api/v1/themes'); const data = await res.json(); if (data.success && data.themes) userThemes = data.themes; } catch (e) {} const exportData = { version: '1.0.0', exportDate: new Date().toISOString(), services: window.APPS || [], customServices: safeGetJSON('custom-services', []), customApps: safeGetJSON('custom-apps', []), weatherZip: safeGet('weather-zip') || '', theme: safeGet('theme') || 'dark', userThemes: userThemes, // Note: API tokens are intentionally NOT exported for security }; const dataStr = JSON.stringify(exportData, null, 2); const dataBlob = new Blob([dataStr], { type: 'application/json' }); const url = URL.createObjectURL(dataBlob); const link = document.createElement('a'); link.href = url; link.download = `dashcaddy-backup-${new Date().toISOString().split('T')[0]}.json`; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); showNotification('Dashboard exported successfully! Note: API tokens are not included for security reasons.', 'success'); } // Import dashboard configuration function importDashboard() { const input = document.createElement('input'); input.type = 'file'; input.accept = 'application/json,.json'; input.onchange = async (e) => { const file = e.target.files[0]; if (!file) return; try { const text = await file.text(); const importData = JSON.parse(text); // Validate import data if (!importData.version || !importData.services) { throw new Error('Invalid dashboard backup file'); } // Confirm import const confirmed = confirm( `Import dashboard configuration?\n\n` + `Export Date: ${new Date(importData.exportDate).toLocaleString()}\n` + `Services: ${importData.services.length}\n` + `Custom Apps: ${(importData.customApps || []).length}\n\n` + `⚠️ This will replace your current dashboard configuration.\n` + `API tokens will need to be reconfigured.` ); if (!confirmed) return; // Import services to API try { const response = await secureFetch('/api/v1/services', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(importData.services) }); if (!response.ok) { console.warn('Could not save services to API, saving locally only'); } } catch (err) { console.warn('API not available, saving locally only:', err); } // Import to localStorage if (importData.customServices) { safeSet('custom-services', JSON.stringify(importData.customServices)); } if (importData.customApps) { safeSet('custom-apps', JSON.stringify(importData.customApps)); } if (importData.weatherZip) { safeSet('weather-zip', importData.weatherZip); } if (importData.theme) { safeSet('theme', importData.theme); } if (importData.userThemes && Object.keys(importData.userThemes).length) { safeSet('user-themes', JSON.stringify(importData.userThemes)); // Push imported themes to server Object.keys(importData.userThemes).forEach(function (slug) { var t = importData.userThemes[slug]; var colors = {}; (window.THEME_PROPS || []).forEach(function (p) { if (t[p]) colors[p] = t[p]; }); secureFetch('/api/v1/themes/' + slug, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: t.name || slug, colors: colors }) }).catch(function () {}); }); } // Update APPS array window.APPS = importData.services; showNotification('Dashboard imported successfully! The page will now reload.', 'success'); // Reload page to apply changes window.location.reload(); } catch (err) { showNotification(`Import failed: ${err.message}. Please check the file and try again.`, 'error'); console.error('Import error:', err); } }; input.click(); } // Add event listeners for import/export buttons document.getElementById('export-dashboard')?.addEventListener('click', exportDashboard); document.getElementById('import-dashboard')?.addEventListener('click', importDashboard); // Reload Caddy button handler document.getElementById('reload-caddy-top')?.addEventListener('click', async () => { const button = document.getElementById('reload-caddy-top'); const originalText = button.textContent; try { button.textContent = '⏳ Reloading...'; button.disabled = true; const response = await secureFetch('/api/v1/caddy/reload', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const result = await response.json(); if (response.ok && result.success) { button.textContent = '✅ Reloaded!'; setTimeout(() => { button.textContent = originalText; button.disabled = false; }, 2000); } else { throw new Error(result.error || 'Reload failed'); } } catch (error) { button.textContent = '❌ Failed'; showNotification(`Failed to reload Caddy: ${error.message}`, 'error'); setTimeout(() => { button.textContent = originalText; button.disabled = false; }, 2000); } }); })();