286 lines
11 KiB
JavaScript
286 lines
11 KiB
JavaScript
/**
|
|
* 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]);
|
|
}
|