/** * Certificate Manager - Upload, List, Delete */ let currentCertId = null; document.addEventListener('DOMContentLoaded', function() { loadCertificates(); document.getElementById('uploadBtn').addEventListener('click', uploadCertificate); document.getElementById('exportBtn').addEventListener('click', exportCertificate); }); function loadCertificates() { fetch('/api/certificates') .then(r => r.json()) .then(data => { if (data.success) { renderCertificates(data.certificates); } else { showAlert(data.error, 'danger'); } }) .catch(e => console.error('Error loading certificates:', e)); } function renderCertificates(certs) { const tbody = document.getElementById('certsList'); if (certs.length === 0) { tbody.innerHTML = 'No certificates uploaded yet'; return; } tbody.innerHTML = certs.map(c => { const expiresDate = c.expires_at ? new Date(c.expires_at) : null; const isExpired = c.is_expired; const expiresText = expiresDate ? formatDate(expiresDate) : 'Unknown'; return ` ${escapeHtml(c.name)} ${escapeHtml(c.common_name || 'N/A')} ${isExpired ? 'EXPIRED' : ''} ${expiresText} ${c.vhost_count > 0 ? `${c.vhost_count}` : 'Not used'} ${isExpired ? 'Expired' : 'Valid'} ${c.vhost_count === 0 ? ` ` : ''} `; }).join(''); } function uploadCertificate() { const name = document.getElementById('cert_name').value; const file = document.getElementById('cert_file').files[0]; if (!name || !file) { showAlert('Certificate name and file are required', 'warning'); return; } const formData = new FormData(); formData.append('name', name); formData.append('cert_file', file); fetch('/api/certificates', { method: 'POST', body: formData }) .then(r => r.json()) .then(data => { if (data.success) { bootstrap.Modal.getInstance(document.getElementById('uploadModal')).hide(); document.getElementById('uploadForm').reset(); loadCertificates(); showAlert('Certificate uploaded successfully', 'success'); } else { showAlert(data.error, 'danger'); } }) .catch(e => showAlert(e.message, 'danger')); } function viewDetails(certId) { currentCertId = certId; fetch(`/api/certificates/${certId}`) .then(r => r.json()) .then(data => { if (data.success) { const c = data.certificate; document.getElementById('detailsName').textContent = c.name; const san = c.subject_alt_names && c.subject_alt_names.length > 0 ? c.subject_alt_names.join(', ') : 'No SAN'; const vhosts = c.vhosts && c.vhosts.length > 0 ? c.vhosts.map(v => `
  • ${v.name} (${v.hostname})
  • `).join('') : '
  • Not used
  • '; const detailsHTML = `
    Common Name ${escapeHtml(c.common_name || 'N/A')}
    Subject Alt Names ${escapeHtml(san)}
    Issued ${c.issued_at ? formatDate(new Date(c.issued_at)) : 'Unknown'}
    Expires ${c.expires_at ? formatDate(new Date(c.expires_at)) : 'Unknown'} ${new Date(c.expires_at) < new Date() ? 'EXPIRED' : ''}
    Created ${formatDate(new Date(c.created_at))}
    Used by VHosts:
    `; document.getElementById('detailsContent').innerHTML = detailsHTML; new bootstrap.Modal(document.getElementById('detailsModal')).show(); } }) .catch(e => showAlert(e.message, 'danger')); } function exportCertificate() { fetch(`/api/certificates/${currentCertId}/export`) .then(r => r.json()) .then(data => { if (data.success) { // Create download link const blob = new Blob([data.content], { type: 'text/plain' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${data.name}.pem`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); } else { showAlert(data.error, 'danger'); } }) .catch(e => showAlert(e.message, 'danger')); } function deleteCert(certId, name) { if (!confirm(`Delete certificate '${name}'? This cannot be undone.`)) return; fetch(`/api/certificates/${certId}`, { method: 'DELETE' }) .then(r => r.json()) .then(data => { if (data.success) { loadCertificates(); showAlert('Certificate deleted successfully', 'success'); } else { showAlert(data.error, 'danger'); } }) .catch(e => showAlert(e.message, 'danger')); } function showAlert(message, type) { const alertDiv = document.createElement('div'); alertDiv.className = `alert alert-${type} alert-dismissible fade show`; alertDiv.innerHTML = ` ${message} `; document.querySelector('.card').parentElement.insertBefore(alertDiv, document.querySelector('.card')); setTimeout(() => alertDiv.remove(), 5000); } function formatDate(date) { return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } function escapeHtml(text) { const map = {'&': '&', '<': '<', '>': '>', '"': '"', "'": '''}; return text.replace(/[&<>"']/g, m => map[m]); }