rewrite
This commit is contained in:
219
static/js/cert_manager.js
Normal file
219
static/js/cert_manager.js
Normal file
@@ -0,0 +1,219 @@
|
||||
/**
|
||||
* 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]);
|
||||
}
|
||||
156
static/js/user_manager.js
Normal file
156
static/js/user_manager.js
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* User Management UI
|
||||
*/
|
||||
|
||||
let currentEditUserId = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadUsers();
|
||||
|
||||
document.getElementById('createUserBtn').addEventListener('click', createUser);
|
||||
document.getElementById('updateUserBtn').addEventListener('click', updateUser);
|
||||
});
|
||||
|
||||
function loadUsers() {
|
||||
fetch('/api/users')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
renderUsers(data.users);
|
||||
}
|
||||
})
|
||||
.catch(e => console.error('Error loading users:', e));
|
||||
}
|
||||
|
||||
function renderUsers(users) {
|
||||
const tbody = document.getElementById('usersList');
|
||||
|
||||
if (users.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="text-center text-muted">No users found</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = users.map(user => `
|
||||
<tr>
|
||||
<td><strong>${escapeHtml(user.username)}</strong></td>
|
||||
<td>
|
||||
${user.is_admin ? '<span class="badge bg-danger">Admin</span>' : '<span class="badge bg-secondary">User</span>'}
|
||||
</td>
|
||||
<td><small class="text-muted">${formatDate(user.created_at)}</small></td>
|
||||
<td><small class="text-muted">${user.last_login ? formatDate(user.last_login) : 'Never'}</small></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" onclick="editUser(${user.id}, '${escapeHtml(user.username)}', ${user.is_admin})">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
${user.id !== getUserId() ? `
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteUser(${user.id}, '${escapeHtml(user.username)}')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
` : ''}
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function createUser() {
|
||||
const username = document.getElementById('newUsername').value;
|
||||
const password = document.getElementById('newPassword').value;
|
||||
const isAdmin = document.getElementById('newIsAdmin').checked;
|
||||
|
||||
fetch('/api/users', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({ username, password, is_admin: isAdmin })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
bootstrap.Modal.getInstance(document.getElementById('newUserModal')).hide();
|
||||
document.getElementById('newUserForm').reset();
|
||||
loadUsers();
|
||||
showAlert('User created successfully', 'success');
|
||||
} else {
|
||||
showAlert(data.error, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(e => showAlert(e.message, 'danger'));
|
||||
}
|
||||
|
||||
function editUser(userId, username, isAdmin) {
|
||||
currentEditUserId = userId;
|
||||
document.getElementById('editUsername').textContent = username;
|
||||
document.getElementById('editIsAdmin').checked = isAdmin;
|
||||
new bootstrap.Modal(document.getElementById('editUserModal')).show();
|
||||
}
|
||||
|
||||
function updateUser() {
|
||||
const password = document.getElementById('editPassword').value;
|
||||
const isAdmin = document.getElementById('editIsAdmin').checked;
|
||||
|
||||
const body = { is_admin: isAdmin };
|
||||
if (password) body.password = password;
|
||||
|
||||
fetch(`/api/users/${currentEditUserId}`, {
|
||||
method: 'PUT',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
bootstrap.Modal.getInstance(document.getElementById('editUserModal')).hide();
|
||||
loadUsers();
|
||||
showAlert('User updated successfully', 'success');
|
||||
} else {
|
||||
showAlert(data.error, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(e => showAlert(e.message, 'danger'));
|
||||
}
|
||||
|
||||
function deleteUser(userId, username) {
|
||||
if (!confirm(`Delete user '${username}'?`)) return;
|
||||
|
||||
fetch(`/api/users/${userId}`, {method: 'DELETE'})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
loadUsers();
|
||||
showAlert('User deleted successfully', 'success');
|
||||
} else {
|
||||
showAlert(data.error, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(e => showAlert(e.message, 'danger'));
|
||||
}
|
||||
|
||||
function showAlert(message, type) {
|
||||
const alert = document.createElement('div');
|
||||
alert.className = `alert alert-${type} alert-dismissible fade show`;
|
||||
alert.innerHTML = `
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
document.querySelector('.card-body').prepend(alert);
|
||||
setTimeout(() => alert.remove(), 5000);
|
||||
}
|
||||
|
||||
function formatDate(dateStr) {
|
||||
return new Date(dateStr).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]);
|
||||
}
|
||||
|
||||
function getUserId() {
|
||||
// Extract from current user endpoint
|
||||
let userId = null;
|
||||
fetch('/api/current-user')
|
||||
.then(r => r.json())
|
||||
.then(data => { userId = data.id; });
|
||||
return userId;
|
||||
}
|
||||
285
static/js/vhost_manager.js
Normal file
285
static/js/vhost_manager.js
Normal file
@@ -0,0 +1,285 @@
|
||||
/**
|
||||
* VHost Manager - Frontend CRUD Logic
|
||||
*/
|
||||
|
||||
let currentEditVHostId = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadVHosts();
|
||||
|
||||
// Event listeners
|
||||
document.getElementById('createVHostBtn').addEventListener('click', createVHost);
|
||||
document.getElementById('updateVHostBtn').addEventListener('click', updateVHost);
|
||||
document.getElementById('addServerBtn').addEventListener('click', addServerInput);
|
||||
|
||||
// Event delegation for remove buttons
|
||||
document.getElementById('backendServersContainer').addEventListener('click', function(e) {
|
||||
if (e.target.classList.contains('remove-server')) {
|
||||
e.target.closest('.backend-server').remove();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function loadVHosts() {
|
||||
fetch('/api/vhosts')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
renderVHosts(data.vhosts);
|
||||
updateStats(data.vhosts);
|
||||
} else {
|
||||
showAlert(data.error, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(e => console.error('Error loading vhosts:', e));
|
||||
}
|
||||
|
||||
function updateStats(vhosts) {
|
||||
document.getElementById('total_vhosts').textContent = vhosts.length;
|
||||
document.getElementById('enabled_vhosts').textContent = vhosts.filter(v => v.enabled).length;
|
||||
document.getElementById('disabled_vhosts').textContent = vhosts.filter(v => !v.enabled).length;
|
||||
document.getElementById('ssl_vhosts').textContent = vhosts.filter(v => v.use_ssl).length;
|
||||
}
|
||||
|
||||
function renderVHosts(vhosts) {
|
||||
const tbody = document.getElementById('vhostsList');
|
||||
|
||||
if (vhosts.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="7" class="text-center text-muted">No VHosts created yet</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = vhosts.map(v => `
|
||||
<tr>
|
||||
<td>
|
||||
<strong>${escapeHtml(v.name)}</strong>
|
||||
${v.use_ssl ? '<span class="badge bg-success ms-2">SSL</span>' : ''}
|
||||
</td>
|
||||
<td>${escapeHtml(v.hostname)}</td>
|
||||
<td>
|
||||
<code>${v.frontend_ip}:${v.frontend_port}</code>
|
||||
</td>
|
||||
<td><span class="badge bg-info">${v.protocol.toUpperCase()}</span></td>
|
||||
<td>
|
||||
<span class="badge bg-secondary">${v.backend_count} servers</span>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm ${v.enabled ? 'btn-success' : 'btn-warning'}"
|
||||
onclick="toggleVHost(${v.id}, ${v.enabled})">
|
||||
<i class="bi bi-${v.enabled ? 'power' : 'hourglass'}"></i>
|
||||
${v.enabled ? 'Enabled' : 'Disabled'}
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" onclick="editVHost(${v.id})">
|
||||
<i class="bi bi-pencil"></i> Edit
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteVHost(${v.id}, '${escapeHtml(v.name)}')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function createVHost() {
|
||||
const name = document.getElementById('vhost_name').value;
|
||||
const hostname = document.getElementById('vhost_hostname').value;
|
||||
const port = document.getElementById('vhost_port').value;
|
||||
const protocol = document.getElementById('vhost_protocol').value;
|
||||
const lb_method = document.getElementById('vhost_lb_method').value;
|
||||
const use_ssl = document.getElementById('vhost_ssl').checked;
|
||||
|
||||
// Get backend servers
|
||||
const servers = [];
|
||||
document.querySelectorAll('.backend-server').forEach(el => {
|
||||
const ip = el.querySelector('.backend-ip').value;
|
||||
const port = el.querySelector('.backend-port').value;
|
||||
if (ip && port) {
|
||||
servers.push({ ip_address: ip, port: parseInt(port) });
|
||||
}
|
||||
});
|
||||
|
||||
if (servers.length === 0) {
|
||||
showAlert('At least one backend server is required', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
name, hostname,
|
||||
frontend_port: parseInt(port),
|
||||
protocol, lb_method, use_ssl,
|
||||
backend_servers: servers
|
||||
};
|
||||
|
||||
fetch('/api/vhosts', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
bootstrap.Modal.getInstance(document.getElementById('newVHostModal')).hide();
|
||||
document.getElementById('newVHostForm').reset();
|
||||
document.getElementById('backendServersContainer').innerHTML = `
|
||||
<div class="backend-server mb-3 p-3 border rounded">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-2">
|
||||
<input type="text" class="form-control backend-ip" placeholder="IP Address" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-2">
|
||||
<input type="number" class="form-control backend-port" placeholder="Port" value="80" required>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-danger remove-server">Remove</button>
|
||||
</div>
|
||||
`;
|
||||
loadVHosts();
|
||||
showAlert('VHost created successfully', 'success');
|
||||
} else {
|
||||
showAlert(data.error, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(e => showAlert(e.message, 'danger'));
|
||||
}
|
||||
|
||||
function editVHost(vhostId) {
|
||||
currentEditVHostId = vhostId;
|
||||
|
||||
fetch(`/api/vhosts/${vhostId}`)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
const v = data.vhost;
|
||||
document.getElementById('editVHostName').textContent = v.name;
|
||||
|
||||
const formContent = `
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">VHost Name</label>
|
||||
<input type="text" class="form-control" id="edit_name" value="${escapeHtml(v.name)}">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Hostname</label>
|
||||
<input type="text" class="form-control" id="edit_hostname" value="${escapeHtml(v.hostname)}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check mb-3">
|
||||
<input type="checkbox" class="form-check-input" id="edit_enabled" ${v.enabled ? 'checked' : ''}>
|
||||
<label class="form-check-label" for="edit_enabled">Enabled</label>
|
||||
</div>
|
||||
<hr>
|
||||
<h6>Backend Servers</h6>
|
||||
<div id="editServersContainer">
|
||||
${v.backend_servers.map(bs => `
|
||||
<div class="backend-server mb-3 p-3 border rounded">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-2">
|
||||
<input type="text" class="form-control backend-ip" value="${bs.ip_address}" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-2">
|
||||
<input type="number" class="form-control backend-port" value="${bs.port}" required>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-danger remove-server">Remove</button>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById('editFormContent').innerHTML = formContent;
|
||||
new bootstrap.Modal(document.getElementById('editVHostModal')).show();
|
||||
}
|
||||
})
|
||||
.catch(e => showAlert(e.message, 'danger'));
|
||||
}
|
||||
|
||||
function updateVHost() {
|
||||
const name = document.getElementById('edit_name').value;
|
||||
const hostname = document.getElementById('edit_hostname').value;
|
||||
const enabled = document.getElementById('edit_enabled').checked;
|
||||
|
||||
const payload = { name, hostname, enabled };
|
||||
|
||||
fetch(`/api/vhosts/${currentEditVHostId}`, {
|
||||
method: 'PUT',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
bootstrap.Modal.getInstance(document.getElementById('editVHostModal')).hide();
|
||||
loadVHosts();
|
||||
showAlert('VHost updated successfully', 'success');
|
||||
} else {
|
||||
showAlert(data.error, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(e => showAlert(e.message, 'danger'));
|
||||
}
|
||||
|
||||
function toggleVHost(vhostId, isEnabled) {
|
||||
fetch(`/api/vhosts/${vhostId}/toggle`, { method: 'POST' })
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
loadVHosts();
|
||||
showAlert(data.message, 'success');
|
||||
} else {
|
||||
showAlert(data.error, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(e => showAlert(e.message, 'danger'));
|
||||
}
|
||||
|
||||
function deleteVHost(vhostId, name) {
|
||||
if (!confirm(`Delete VHost '${name}'? This cannot be undone.`)) return;
|
||||
|
||||
fetch(`/api/vhosts/${vhostId}`, { method: 'DELETE' })
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
loadVHosts();
|
||||
showAlert('VHost deleted successfully', 'success');
|
||||
} else {
|
||||
showAlert(data.error, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(e => showAlert(e.message, 'danger'));
|
||||
}
|
||||
|
||||
function addServerInput() {
|
||||
const container = document.getElementById('backendServersContainer');
|
||||
const newServer = document.createElement('div');
|
||||
newServer.className = 'backend-server mb-3 p-3 border rounded';
|
||||
newServer.innerHTML = `
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-2">
|
||||
<input type="text" class="form-control backend-ip" placeholder="IP Address" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-2">
|
||||
<input type="number" class="form-control backend-port" placeholder="Port" value="80" required>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-danger remove-server">Remove</button>
|
||||
`;
|
||||
container.appendChild(newServer);
|
||||
}
|
||||
|
||||
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:first-of-type').parentElement.insertBefore(alertDiv, document.querySelector('.card:first-of-type'));
|
||||
setTimeout(() => alertDiv.remove(), 5000);
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const map = {'&': '&', '<': '<', '>': '>', '"': '"', "'": '''};
|
||||
return text.replace(/[&<>"']/g, m => map[m]);
|
||||
}
|
||||
Reference in New Issue
Block a user