220 lines
7.9 KiB
JavaScript
220 lines
7.9 KiB
JavaScript
/**
|
|
* 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 = '<tr><td colspan="6" class="text-center text-muted">No certificates uploaded yet</td></tr>';
|
|
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 `
|
|
<tr ${isExpired ? 'class="table-danger"' : ''}>
|
|
<td><strong>${escapeHtml(c.name)}</strong></td>
|
|
<td><code>${escapeHtml(c.common_name || 'N/A')}</code></td>
|
|
<td>
|
|
${isExpired ? '<span class="badge bg-danger">EXPIRED</span>' : ''}
|
|
<small>${expiresText}</small>
|
|
</td>
|
|
<td>
|
|
${c.vhost_count > 0
|
|
? `<span class="badge bg-info">${c.vhost_count}</span>`
|
|
: '<span class="text-muted">Not used</span>'}
|
|
</td>
|
|
<td>
|
|
${isExpired
|
|
? '<span class="badge bg-danger">Expired</span>'
|
|
: '<span class="badge bg-success">Valid</span>'}
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-info" onclick="viewDetails(${c.id})">
|
|
<i class="bi bi-eye"></i> View
|
|
</button>
|
|
${c.vhost_count === 0 ? `
|
|
<button class="btn btn-sm btn-danger" onclick="deleteCert(${c.id}, '${escapeHtml(c.name)}')">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
` : ''}
|
|
</td>
|
|
</tr>
|
|
`;
|
|
}).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 => `<li>${v.name} (${v.hostname})</li>`).join('')
|
|
: '<li class="text-muted">Not used</li>';
|
|
|
|
const detailsHTML = `
|
|
<table class="table table-sm">
|
|
<tr>
|
|
<th>Common Name</th>
|
|
<td><code>${escapeHtml(c.common_name || 'N/A')}</code></td>
|
|
</tr>
|
|
<tr>
|
|
<th>Subject Alt Names</th>
|
|
<td><code>${escapeHtml(san)}</code></td>
|
|
</tr>
|
|
<tr>
|
|
<th>Issued</th>
|
|
<td>${c.issued_at ? formatDate(new Date(c.issued_at)) : 'Unknown'}</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Expires</th>
|
|
<td>
|
|
${c.expires_at ? formatDate(new Date(c.expires_at)) : 'Unknown'}
|
|
${new Date(c.expires_at) < new Date() ? '<span class="badge bg-danger ms-2">EXPIRED</span>' : ''}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Created</th>
|
|
<td>${formatDate(new Date(c.created_at))}</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<h6>Used by VHosts:</h6>
|
|
<ul>${vhosts}</ul>
|
|
`;
|
|
|
|
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}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
`;
|
|
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]);
|
|
}
|