Three merge-fallout bugs that combined to leave the services grid empty
and most UI inert:
1. error-handler.js was bundled into onboarding.js (loaded 3rd), but
globals.js in core.js (loaded 1st) does `const errorHandler = new
ErrorHandler()` at top level. ErrorHandler was undefined when core.js
ran -> ReferenceError -> globals.js stopped, so window.APPS,
_showTotpOverlay, loadServices, etc. were never set, and init.js
blew up on every call into core's exports.
Moved error-handler.js to the start of the core.js bundle so the
class is on window before any other script touches it.
2. setup-wizard.js also declared `const errorHandler = new ErrorHandler()`
at top level. Classic scripts share the document's top-level lexical
environment, so this collided with globals.js's declaration ->
redeclaration SyntaxError in features.js. Removed setup-wizard.js's
copy; it picks up the global one.
3. tooltip-definitions.js closed its `(function(window){...})(window);`
IIFE at line ~171 ("Validation module loaded"), then the TOOLTIP_
DEFINITIONS array, getter helpers, window.TooltipDefinitions export,
and final `debug(...)` log all sat at top level — outside the IIFE,
where `debug` was no longer in scope. Removed the early close and
added one at EOF so the whole file is in one IIFE.
437 lines
15 KiB
JavaScript
437 lines
15 KiB
JavaScript
// Shared timezone utility — used by setup wizard and settings modal
|
|
// errorHandler is declared globally in globals.js (core.js bundle); reusing it
|
|
// here so we don't get "redeclaration of const errorHandler" at script scope.
|
|
|
|
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() || '';
|
|
const routingMode = document.querySelector('input[name="routing-mode"]:checked')?.value || 'subdirectory';
|
|
const exampleUrls = routingMode === 'subdirectory'
|
|
? `https://${domain}/sonarr, https://${domain}/grafana`
|
|
: `https://sonarr.${domain}, https://grafana.${domain}`;
|
|
const routingLabel = routingMode === 'subdirectory' ? 'Subdirectory (domain.com/app)' : 'Subdomain (app.domain.com)';
|
|
|
|
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>Routing:</strong> ${routingLabel}</div>
|
|
<div><strong>Example URLs:</strong> ${exampleUrls}</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 {
|
|
errorHandler.logError('[SetupWizard] Save Config', new Error(`Server returned ${response.status}`), { function: 'saveConfigToServer' });
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
errorHandler.logError('[SetupWizard] Save Config', error, { function: 'saveConfigToServer' });
|
|
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.routingMode = document.querySelector('input[name="routing-mode"]:checked')?.value || 'subdirectory';
|
|
config.defaults = {
|
|
dnsType: config.routingMode === 'subdirectory' ? 'none' : '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 routing mode toggle — update requirement text
|
|
document.querySelectorAll('input[name="routing-mode"]').forEach(function(radio) {
|
|
radio.onchange = function() {
|
|
var note = document.getElementById('dns-requirement-note');
|
|
if (note) {
|
|
note.textContent = this.value === 'subdirectory'
|
|
? 'Only one DNS record needed (for the main domain)'
|
|
: 'You\'ll need to configure DNS manually for each subdomain';
|
|
}
|
|
};
|
|
});
|
|
|
|
// 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();
|
|
}
|
|
};
|
|
})();
|