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:
414
status/js/setup-wizard.js
Normal file
414
status/js/setup-wizard.js
Normal file
@@ -0,0 +1,414 @@
|
||||
// Shared timezone utility — used by setup wizard and settings modal
|
||||
window.populateTimezoneSelect = function(selectEl, selectedTz) {
|
||||
const timezones = Intl.supportedValuesOf('timeZone');
|
||||
const detected = selectedTz || Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
||||
selectEl.innerHTML = '';
|
||||
for (const tz of timezones) {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = tz;
|
||||
opt.textContent = tz.replace(/_/g, ' ');
|
||||
if (tz === detected) opt.selected = true;
|
||||
selectEl.appendChild(opt);
|
||||
}
|
||||
};
|
||||
|
||||
// Setup Wizard System - Server-side config storage
|
||||
(function () {
|
||||
let currentConfigType = 'homelab';
|
||||
let serverConfig = null;
|
||||
|
||||
// Check server for existing config on page load
|
||||
async function checkServerConfig() {
|
||||
try {
|
||||
const response = await fetch('/api/v1/config');
|
||||
if (response.ok) {
|
||||
serverConfig = await response.json();
|
||||
if (serverConfig && serverConfig.setupComplete) {
|
||||
// Config exists on server - don't show wizard
|
||||
document.getElementById('setup-wizard').style.display = 'none';
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Could not fetch server config, checking localStorage fallback:', error.message);
|
||||
}
|
||||
|
||||
// Fallback: check localStorage for backwards compatibility
|
||||
const localSetup = safeGet('dashcaddy-setup');
|
||||
if (localSetup) {
|
||||
document.getElementById('setup-wizard').style.display = 'none';
|
||||
return true;
|
||||
}
|
||||
|
||||
// No config found - show wizard
|
||||
document.getElementById('setup-wizard').style.display = 'flex';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize on page load
|
||||
checkServerConfig();
|
||||
|
||||
// Populate timezone dropdown with auto-detection
|
||||
const setupTzSelect = document.getElementById('setup-timezone');
|
||||
if (setupTzSelect) window.populateTimezoneSelect(setupTzSelect);
|
||||
|
||||
// Step navigation
|
||||
function showStep(stepId) {
|
||||
document.querySelectorAll('.setup-step').forEach(step => {
|
||||
step.style.display = 'none';
|
||||
});
|
||||
const targetStep = document.getElementById(stepId);
|
||||
if (targetStep) {
|
||||
targetStep.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// Show summary
|
||||
function showSummary() {
|
||||
const summaryContent = document.getElementById('setup-summary-content');
|
||||
if (!summaryContent) return;
|
||||
|
||||
let html = '<div style="display: grid; gap: 20px;">';
|
||||
|
||||
if (currentConfigType === 'homelab') {
|
||||
const tld = document.getElementById('setup-tld')?.value?.trim() || '.home';
|
||||
const caName = document.getElementById('setup-ca-name')?.value?.trim() || '';
|
||||
const dnsIP = document.getElementById('setup-dns-ip')?.value?.trim() || '';
|
||||
const dnsPort = document.getElementById('setup-dns-port')?.value?.trim() || DC.DEFAULTS.DNS_PORT;
|
||||
|
||||
html += `
|
||||
<div>
|
||||
<h3 style="margin: 0 0 12px; color: var(--accent);">Home Lab Configuration</h3>
|
||||
<div style="display: grid; gap: 12px; font-size: 0.95rem;">
|
||||
<div><strong>TLD:</strong> ${tld}</div>
|
||||
<div><strong>Certificate Authority:</strong> ${caName}</div>
|
||||
<div><strong>DNS Server:</strong> ${dnsIP}:${dnsPort}</div>
|
||||
<div><strong>Example URLs:</strong> https://uptime${tld}, https://nextcloud${tld}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else if (currentConfigType === 'simple') {
|
||||
const ip = document.getElementById('setup-simple-ip')?.value?.trim() || 'localhost';
|
||||
|
||||
html += `
|
||||
<div>
|
||||
<h3 style="margin: 0 0 12px; color: var(--accent);">Simple Setup</h3>
|
||||
<div style="display: grid; gap: 12px; font-size: 0.95rem;">
|
||||
<div><strong>Access Method:</strong> IP:Port only</div>
|
||||
<div><strong>Default IP:</strong> ${ip}</div>
|
||||
<div><strong>SSL:</strong> None (HTTP only)</div>
|
||||
<div><strong>Example URLs:</strong> http://${ip}:8080, http://${ip}:3000</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else if (currentConfigType === 'public') {
|
||||
const domain = document.getElementById('setup-public-domain')?.value?.trim() || '';
|
||||
const email = document.getElementById('setup-public-email')?.value?.trim() || '';
|
||||
|
||||
html += `
|
||||
<div>
|
||||
<h3 style="margin: 0 0 12px; color: var(--accent);">Public Server</h3>
|
||||
<div style="display: grid; gap: 12px; font-size: 0.95rem;">
|
||||
<div><strong>Domain:</strong> ${domain}</div>
|
||||
<div><strong>SSL:</strong> Let's Encrypt</div>
|
||||
<div><strong>Email:</strong> ${email}</div>
|
||||
<div><strong>Example URLs:</strong> https://app.${domain}, https://cloud.${domain}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Timezone (universal across all config types)
|
||||
const tz = document.getElementById('setup-timezone')?.value || Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
||||
html += `
|
||||
<div style="margin-top: 8px; padding-top: 12px; border-top: 1px solid var(--border);">
|
||||
<div style="font-size: 0.95rem;"><strong>Timezone:</strong> ${tz.replace(/_/g, ' ')}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
html += '</div>';
|
||||
summaryContent.innerHTML = html;
|
||||
showStep('setup-step-summary');
|
||||
}
|
||||
|
||||
// Save config to server
|
||||
async function saveConfigToServer(config) {
|
||||
try {
|
||||
const response = await secureFetch('/api/v1/config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(config)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
await response.json();
|
||||
return true;
|
||||
} else {
|
||||
console.error('Failed to save config to server:', response.status);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving config to server:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Finish setup handler
|
||||
async function finishSetup() {
|
||||
const config = {
|
||||
setupComplete: true,
|
||||
configurationType: currentConfigType,
|
||||
timestamp: new Date().toISOString(),
|
||||
timezone: document.getElementById('setup-timezone')?.value || Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'
|
||||
};
|
||||
|
||||
if (currentConfigType === 'homelab') {
|
||||
config.tld = document.getElementById('setup-tld')?.value?.trim() || '.home';
|
||||
config.caName = document.getElementById('setup-ca-name')?.value?.trim() || '';
|
||||
config.dns = {
|
||||
provider: 'technitium',
|
||||
ip: document.getElementById('setup-dns-ip')?.value?.trim() || '',
|
||||
port: document.getElementById('setup-dns-port')?.value?.trim() || DC.DEFAULTS.DNS_PORT,
|
||||
token: document.getElementById('setup-dns-token')?.value?.trim() || ''
|
||||
};
|
||||
config.defaults = {
|
||||
dnsType: 'private',
|
||||
sslType: 'internal',
|
||||
targetIP: 'localhost'
|
||||
};
|
||||
} else if (currentConfigType === 'simple') {
|
||||
config.defaultIP = document.getElementById('setup-simple-ip')?.value?.trim() || 'localhost';
|
||||
config.defaults = {
|
||||
dnsType: 'none',
|
||||
sslType: 'none',
|
||||
targetIP: config.defaultIP
|
||||
};
|
||||
} else if (currentConfigType === 'public') {
|
||||
config.domain = document.getElementById('setup-public-domain')?.value?.trim() || '';
|
||||
config.email = document.getElementById('setup-public-email')?.value?.trim() || '';
|
||||
config.defaults = {
|
||||
dnsType: 'public',
|
||||
sslType: 'letsencrypt',
|
||||
targetIP: 'localhost'
|
||||
};
|
||||
}
|
||||
|
||||
// Save to server (primary) and localStorage (fallback)
|
||||
const savedToServer = await saveConfigToServer(config);
|
||||
safeSet('dashcaddy-config', JSON.stringify(config));
|
||||
safeSet('dashcaddy-setup', 'completed');
|
||||
|
||||
// Hide wizard
|
||||
document.getElementById('setup-wizard').style.display = 'none';
|
||||
|
||||
// Show success notification
|
||||
const configName = currentConfigType === 'homelab' ? 'Professional Home Lab' :
|
||||
currentConfigType === 'simple' ? 'Simple Setup' : 'Public Server';
|
||||
const saveLocation = savedToServer ? 'server (shared across all devices)' : 'locally (this browser only)';
|
||||
|
||||
showNotification(`Setup Complete! Configured for: ${configName}. Settings saved to: ${saveLocation}`, 'success', 5000);
|
||||
|
||||
// Reload page to apply configuration
|
||||
setTimeout(() => location.reload(), 500);
|
||||
}
|
||||
|
||||
// ===== Event Handlers using direct onclick for reliability =====
|
||||
|
||||
// Step 1: Continue button
|
||||
const step1Next = document.getElementById('setup-step-1-next');
|
||||
if (step1Next) {
|
||||
step1Next.onclick = function(e) {
|
||||
e.preventDefault();
|
||||
const selected = document.querySelector('input[name="config-type"]:checked');
|
||||
if (selected) {
|
||||
currentConfigType = selected.value;
|
||||
}
|
||||
if (currentConfigType === 'homelab') {
|
||||
showStep('setup-step-homelab');
|
||||
} else if (currentConfigType === 'simple') {
|
||||
showStep('setup-step-simple');
|
||||
} else if (currentConfigType === 'public') {
|
||||
showStep('setup-step-public');
|
||||
} else {
|
||||
showStep('setup-step-homelab');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Skip setup button
|
||||
const skipBtn = document.getElementById('setup-skip');
|
||||
if (skipBtn) {
|
||||
skipBtn.onclick = async function(e) {
|
||||
e.preventDefault();
|
||||
if (confirm('Skip setup? You can run it later from Settings.')) {
|
||||
// Save skip status to server
|
||||
await saveConfigToServer({ setupComplete: true, skipped: true, timestamp: new Date().toISOString() });
|
||||
safeSet('dashcaddy-setup', 'skipped');
|
||||
document.getElementById('setup-wizard').style.display = 'none';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Home Lab: TLD Preview
|
||||
const tldInput = document.getElementById('setup-tld');
|
||||
if (tldInput) {
|
||||
tldInput.oninput = function(e) {
|
||||
const tld = e.target.value || '.home';
|
||||
const preview1 = document.getElementById('tld-preview');
|
||||
const preview2 = document.getElementById('tld-preview-2');
|
||||
if (preview1) preview1.textContent = tld;
|
||||
if (preview2) preview2.textContent = tld;
|
||||
};
|
||||
}
|
||||
|
||||
// Home Lab navigation
|
||||
const homelabBack = document.getElementById('setup-homelab-back');
|
||||
if (homelabBack) {
|
||||
homelabBack.onclick = function(e) {
|
||||
e.preventDefault();
|
||||
showStep('setup-step-1');
|
||||
};
|
||||
}
|
||||
|
||||
const homelabNext = document.getElementById('setup-homelab-next');
|
||||
if (homelabNext) {
|
||||
homelabNext.onclick = function(e) {
|
||||
e.preventDefault();
|
||||
const tld = document.getElementById('setup-tld')?.value?.trim() || '';
|
||||
const caName = document.getElementById('setup-ca-name')?.value?.trim() || '';
|
||||
const dnsIP = document.getElementById('setup-dns-ip')?.value?.trim() || '';
|
||||
|
||||
if (!tld || !tld.startsWith('.')) {
|
||||
showNotification('Please enter a valid TLD starting with a dot (e.g., .home)', 'warning');
|
||||
return;
|
||||
}
|
||||
if (!caName) {
|
||||
showNotification('Please enter a Certificate Authority name', 'warning');
|
||||
return;
|
||||
}
|
||||
if (!dnsIP) {
|
||||
showNotification('Please enter your DNS server IP address', 'warning');
|
||||
return;
|
||||
}
|
||||
showSummary();
|
||||
};
|
||||
}
|
||||
|
||||
// Simple navigation
|
||||
const simpleBack = document.getElementById('setup-simple-back');
|
||||
if (simpleBack) {
|
||||
simpleBack.onclick = function(e) {
|
||||
e.preventDefault();
|
||||
showStep('setup-step-1');
|
||||
};
|
||||
}
|
||||
|
||||
const simpleNext = document.getElementById('setup-simple-next');
|
||||
if (simpleNext) {
|
||||
simpleNext.onclick = function(e) {
|
||||
e.preventDefault();
|
||||
showSummary();
|
||||
};
|
||||
}
|
||||
|
||||
// Public navigation
|
||||
const publicBack = document.getElementById('setup-public-back');
|
||||
if (publicBack) {
|
||||
publicBack.onclick = function(e) {
|
||||
e.preventDefault();
|
||||
showStep('setup-step-1');
|
||||
};
|
||||
}
|
||||
|
||||
const publicNext = document.getElementById('setup-public-next');
|
||||
if (publicNext) {
|
||||
publicNext.onclick = function(e) {
|
||||
e.preventDefault();
|
||||
const domain = document.getElementById('setup-public-domain')?.value?.trim() || '';
|
||||
const email = document.getElementById('setup-public-email')?.value?.trim() || '';
|
||||
|
||||
if (!domain) {
|
||||
showNotification('Please enter your domain name', 'warning');
|
||||
return;
|
||||
}
|
||||
if (!email || !email.includes('@')) {
|
||||
showNotification('Please enter a valid email address', 'warning');
|
||||
return;
|
||||
}
|
||||
showSummary();
|
||||
};
|
||||
}
|
||||
|
||||
// Summary navigation
|
||||
const summaryBack = document.getElementById('setup-summary-back');
|
||||
if (summaryBack) {
|
||||
summaryBack.onclick = function(e) {
|
||||
e.preventDefault();
|
||||
if (currentConfigType === 'homelab') {
|
||||
showStep('setup-step-homelab');
|
||||
} else if (currentConfigType === 'simple') {
|
||||
showStep('setup-step-simple');
|
||||
} else if (currentConfigType === 'public') {
|
||||
showStep('setup-step-public');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Finish setup button
|
||||
const finishBtn = document.getElementById('setup-finish');
|
||||
if (finishBtn) {
|
||||
finishBtn.onclick = function(e) {
|
||||
e.preventDefault();
|
||||
finishSetup();
|
||||
};
|
||||
}
|
||||
|
||||
// Expose function to get global config (from server or localStorage)
|
||||
window.getGlobalConfig = async function() {
|
||||
// Try server first
|
||||
try {
|
||||
const response = await fetch('/api/v1/config');
|
||||
if (response.ok) {
|
||||
const config = await response.json();
|
||||
if (config && config.setupComplete) {
|
||||
return config;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Could not fetch config from server');
|
||||
}
|
||||
|
||||
// Fallback to localStorage
|
||||
const configStr = safeGet('dashcaddy-config');
|
||||
if (configStr) {
|
||||
return JSON.parse(configStr);
|
||||
}
|
||||
|
||||
// Return default config if not set
|
||||
return {
|
||||
setupComplete: false,
|
||||
configurationType: 'homelab',
|
||||
tld: '.home',
|
||||
caName: '',
|
||||
defaults: {
|
||||
dnsType: 'private',
|
||||
sslType: 'internal',
|
||||
targetIP: 'localhost'
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Expose reset function for settings
|
||||
window.resetSetupWizard = async function() {
|
||||
if (confirm('Reset DashCaddy configuration? This will show the setup wizard again.')) {
|
||||
try {
|
||||
await secureFetch('/api/v1/config', { method: 'DELETE' });
|
||||
} catch (e) {
|
||||
console.warn('Could not delete server config');
|
||||
}
|
||||
safeRemove('dashcaddy-setup');
|
||||
safeRemove('dashcaddy-config');
|
||||
location.reload();
|
||||
}
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user