Initial commit: DashCaddy v1.0

Full codebase including API server (32 modules + routes), dashboard frontend,
DashCA certificate distribution, installer script, and deployment skills.
This commit is contained in:
2026-03-05 02:26:12 -08:00
commit f61e85d9a7
337 changed files with 75282 additions and 0 deletions

169
status/js/import-export.js Normal file
View File

@@ -0,0 +1,169 @@
// ========== 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);
}
});
})();