Unify backup/restore into single v2.0 file with full state capture
Server export now includes encryption key, themes, and all config files. Client export bundles all DashCaddy localStorage keys (19 named + dynamic widget keys) as browserState. Restore handles both server and browser state in one operation. Legacy v1.0 import format still supported. Removed redundant Export/Import toolbar buttons — Backup modal is now the single entry point. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,142 +1,10 @@
|
||||
// ========== IMPORT/EXPORT FUNCTIONALITY ==========
|
||||
// ========== CADDY RELOAD BUTTON ==========
|
||||
(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;
|
||||
@@ -145,9 +13,9 @@
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
|
||||
if (response.ok && result.success) {
|
||||
button.textContent = '✅ Reloaded!';
|
||||
setTimeout(() => {
|
||||
|
||||
Reference in New Issue
Block a user