/**
* 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]);
}