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

273
status/js/core/dns.js Normal file
View File

@@ -0,0 +1,273 @@
// ========== DNS MANAGEMENT ==========
(function () {
// Restart DNS service via backend proxy (backend handles auth automatically)
async function restartDnsService(dnsId) {
const response = await secureFetch(`/api/v1/dns/restart/${dnsId}`, { method: 'POST' });
const result = await response.json();
if (!result.success) {
throw new Error(result.error || 'Restart failed');
}
return result;
}
// Event delegation for DNS restart buttons (dynamic cards)
document.querySelector('.top')?.addEventListener('click', async (e) => {
const restartBtn = e.target.closest('[id$="-restart"]');
if (!restartBtn) return;
const dnsId = restartBtn.id.replace('-restart', '');
if (!SITE.dnsServers[dnsId]) return;
if (!confirm(`Restart ${dnsId.toUpperCase()} service?`)) return;
try {
await withButton(restartBtn, '...', () => restartDnsService(dnsId));
setTimeout(window.refreshAll, DC.DELAYS.RELOAD);
} catch (e) {
showNotification('Restart failed: ' + e.message, 'error');
}
});
// DNS Update buttons
async function updateDnsServer(dnsId, serverIp) {
const btn = document.getElementById(`${dnsId}-update`);
const originalText = btn?.textContent || '⬆️';
try {
// First check for updates
btn.textContent = '🔍';
btn.disabled = true;
btn.title = 'Checking for updates...';
const checkResponse = await fetch(`/api/v1/dns/check-update?server=${encodeURIComponent(serverIp)}`);
const checkResult = await checkResponse.json();
if (!checkResult.success) {
throw new Error(checkResult.error || 'Failed to check for updates');
}
if (!checkResult.updateAvailable) {
btn.textContent = '✅';
btn.title = `Already on latest version (${checkResult.currentVersion})`;
showNotification(`${dnsId.toUpperCase()} is already up to date! Current version: ${checkResult.currentVersion}`, 'info');
setTimeout(() => {
btn.textContent = originalText;
btn.disabled = false;
btn.title = 'Update DNS server';
}, 3000);
return;
}
// Update available - confirm with user
const confirmUpdate = confirm(
`Update available for ${dnsId.toUpperCase()}!\n\n` +
`Current: ${checkResult.currentVersion}\n` +
`New: ${checkResult.updateVersion}\n\n` +
(checkResult.updateTitle ? `${checkResult.updateTitle}\n\n` : '') +
`The DNS server will restart during the update.\nProceed?`
);
if (!confirmUpdate) {
btn.textContent = originalText;
btn.disabled = false;
btn.title = 'Update DNS server';
return;
}
// Perform the update
btn.textContent = '🔄';
btn.title = 'Updating...';
const updateResponse = await secureFetch(`/api/v1/dns/update?server=${encodeURIComponent(serverIp)}`, {
method: 'POST'
});
const updateResult = await updateResponse.json();
if (!updateResult.success) {
throw new Error(updateResult.error || 'Update failed');
}
if (updateResult.manualUpdateRequired) {
// Technitium v14+ doesn't support auto-install via API
btn.textContent = '⬆️';
btn.title = `Update available: ${updateResult.newVersion}`;
const downloadInfo = updateResult.downloadLink
? `\nDownload: ${updateResult.downloadLink}`
: '';
const instructionsInfo = updateResult.instructionsLink
? `\nInstructions: ${updateResult.instructionsLink}`
: '';
showNotification(`${dnsId.toUpperCase()} update requires manual installation. Current: ${updateResult.previousVersion}${updateResult.newVersion}. Please update manually on the host machine.`, 'warning', 8000);
btn.disabled = false;
return;
}
btn.textContent = '✅';
btn.title = 'Updated successfully!';
showNotification(`${dnsId.toUpperCase()} updated successfully! ${updateResult.previousVersion}${updateResult.newVersion}. Server is restarting...`, 'success');
// Refresh after delay for server restart
setTimeout(() => {
btn.textContent = originalText;
btn.disabled = false;
btn.title = 'Update DNS server';
window.refreshAll();
}, 10000);
} catch (error) {
console.error('DNS update error:', error);
btn.textContent = '❌';
btn.title = 'Update failed';
showNotification(`Failed to update ${dnsId.toUpperCase()}: ${error.message}`, 'error');
setTimeout(() => {
btn.textContent = originalText;
btn.disabled = false;
btn.title = 'Update DNS server';
}, 3000);
}
}
// Event delegation for DNS update buttons (dynamic cards)
document.querySelector('.top')?.addEventListener('click', (e) => {
const updateBtn = e.target.closest('[id$="-update"]');
if (!updateBtn) return;
const dnsId = updateBtn.id.replace('-update', '');
if (!SITE.dnsServers[dnsId]) return;
updateDnsServer(dnsId, SITE.dnsServers[dnsId]?.ip);
});
// ===== DNS SETTINGS MODAL =====
// Inject modal HTML
injectModal('dns-settings-modal', `
<div id="dns-settings-modal" class="weather-modal">
<div class="weather-modal-content" style="min-width: 420px; max-width: 520px;">
<h3 id="dns-settings-title">DNS Settings</h3>
<div style="display: grid; gap: 16px; margin-top: 16px;">
<div>
<label for="dns-edit-ip" class="form-label-accent-sm">Server IP</label>
<input type="text" id="dns-edit-ip" class="form-input-md" placeholder="192.168.1.1" />
</div>
<div>
<label for="dns-edit-port" class="form-label-accent-sm">Port</label>
<input type="number" id="dns-edit-port" class="form-input-md" placeholder="5380" />
</div>
<div>
<label for="dns-edit-name" class="form-label-accent-sm">Display Name (optional)</label>
<input type="text" id="dns-edit-name" class="form-input-md" placeholder="e.g. Primary DNS" />
</div>
<div class="form-hint-sm">Manage credentials via Tokens in the toolbar</div>
</div>
<div class="weather-modal-buttons" style="margin-top: 24px;">
<button id="dns-settings-cancel">Cancel</button>
<button id="dns-settings-delete" style="background: color-mix(in srgb, #e74c3c 20%, transparent); border-color: #e74c3c; color: #e74c3c;">Remove</button>
<button id="dns-settings-save" class="btn-accent">Save</button>
</div>
</div>
</div>`);
let currentDnsId = null;
function openDnsSettings(dnsId) {
currentDnsId = dnsId;
const server = SITE.dnsServers[dnsId] || {};
const modal = document.getElementById('dns-settings-modal');
document.getElementById('dns-settings-title').textContent = `${(server.name || dnsId).toUpperCase()} Settings`;
document.getElementById('dns-edit-ip').value = server.ip || '';
document.getElementById('dns-edit-port').value = server.port || DC.DEFAULTS.DNS_PORT;
document.getElementById('dns-edit-name').value = server.name || '';
modal.classList.add('show');
}
async function saveDnsSettings() {
if (!currentDnsId) return;
const ip = document.getElementById('dns-edit-ip').value.trim();
const port = document.getElementById('dns-edit-port').value.trim() || DC.DEFAULTS.DNS_PORT;
const name = document.getElementById('dns-edit-name').value.trim();
if (!ip) {
showNotification('Server IP is required', 'warning');
return;
}
const update = { dnsServers: {} };
update.dnsServers[currentDnsId] = { ip, port: String(port) };
if (name) update.dnsServers[currentDnsId].name = name;
try {
const response = await secureFetch('/api/v1/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(update)
});
const result = await response.json();
if (result.success) {
SITE.dnsServers[currentDnsId] = update.dnsServers[currentDnsId];
showNotification(`${currentDnsId.toUpperCase()} settings saved`, 'success');
closeDnsSettings();
window.refreshAll();
} else {
showNotification(result.error || 'Failed to save settings', 'error');
}
} catch (err) {
showNotification('Failed to save: ' + err.message, 'error');
}
}
async function removeDnsServer() {
if (!currentDnsId) return;
if (!confirm(`Remove ${currentDnsId.toUpperCase()} from dashboard? This won't affect the actual DNS server.`)) return;
// Remove by setting to null in dnsServers
try {
const resp = await secureFetch('/api/v1/config');
const config = await resp.json();
if (config.dnsServers) {
delete config.dnsServers[currentDnsId];
}
const saveResp = await secureFetch('/api/v1/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ dnsServers: config.dnsServers || {} })
});
const result = await saveResp.json();
if (result.success) {
delete SITE.dnsServers[currentDnsId];
// Remove card from DOM
const card = document.querySelector(`.top [data-app="${currentDnsId}"]`);
if (card) card.remove();
showNotification(`${currentDnsId.toUpperCase()} removed from dashboard`, 'success');
closeDnsSettings();
} else {
showNotification(result.error || 'Failed to remove', 'error');
}
} catch (err) {
showNotification('Failed to remove: ' + err.message, 'error');
}
}
function closeDnsSettings() {
closeModal('dns-settings-modal');
currentDnsId = null;
}
// Event listeners for modal buttons
document.getElementById('dns-settings-cancel')?.addEventListener('click', closeDnsSettings);
document.getElementById('dns-settings-save')?.addEventListener('click', saveDnsSettings);
document.getElementById('dns-settings-delete')?.addEventListener('click', removeDnsServer);
document.getElementById('dns-settings-modal')?.addEventListener('click', (e) => {
if (e.target.id === 'dns-settings-modal') closeDnsSettings();
});
// Event delegation for DNS settings buttons (dynamic cards)
document.querySelector('.top')?.addEventListener('click', (e) => {
const settingsBtn = e.target.closest('[id$="-settings"]');
if (!settingsBtn) return;
const dnsId = settingsBtn.id.replace('-settings', '');
if (!SITE.dnsServers[dnsId]) return;
e.stopPropagation();
openDnsSettings(dnsId);
});
document.getElementById('refresh')?.addEventListener('click', window.refreshAll);
})();