442 lines
15 KiB
HTML
442 lines
15 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Gestione Domini - Proxmox Manager{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="dashboard-header">
|
|
<div>
|
|
<h1>Gestione Sottodomini</h1>
|
|
<p class="subtitle">Crea e gestisci i tuoi sottodomini su {{ domain }}</p>
|
|
</div>
|
|
<div class="dashboard-actions">
|
|
<button class="btn btn-primary" onclick="showCreateModal()">
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
<path d="M8 2a.5.5 0 0 1 .5.5v5h5a.5.5 0 0 1 0 1h-5v5a.5.5 0 0 1-1 0v-5h-5a.5.5 0 0 1 0-1h5v-5A.5.5 0 0 1 8 2z"/>
|
|
</svg>
|
|
Crea Sottodominio
|
|
</button>
|
|
<button class="btn btn-ghost btn-sm" onclick="loadSubdomains()">
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/>
|
|
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/>
|
|
</svg>
|
|
Aggiorna
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="subdomains-container">
|
|
<div class="loading">
|
|
<div class="spinner"></div>
|
|
<p>Caricamento sottodomini...</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal per creare sottodominio -->
|
|
<div id="createModal" class="modal">
|
|
<div class="modal-content" style="max-width: 600px;">
|
|
<span class="close" onclick="closeCreateModal()">×</span>
|
|
<h2>Crea Nuovo Sottodominio</h2>
|
|
|
|
<form id="createSubdomainForm" onsubmit="createSubdomain(event)">
|
|
<div class="form-group">
|
|
<label for="subdomain">Nome Sottodominio *</label>
|
|
<div style="display: flex; align-items: center; gap: var(--space-sm);">
|
|
<input type="text"
|
|
id="subdomain"
|
|
name="subdomain"
|
|
class="form-control"
|
|
placeholder="es: server01"
|
|
pattern="[a-z0-9-]+"
|
|
required
|
|
style="flex: 1;">
|
|
<span class="domain-suffix" style="color: var(--text-secondary);">.{{ domain }}</span>
|
|
</div>
|
|
<small class="form-text">Solo lettere minuscole, numeri e trattini</small>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="ip_address">Indirizzo IP *</label>
|
|
<input type="text"
|
|
id="ip_address"
|
|
name="ip_address"
|
|
class="form-control"
|
|
placeholder="es: 192.168.1.100"
|
|
pattern="^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$"
|
|
required>
|
|
<small class="form-text">Indirizzo IP di destinazione</small>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="vm_id">VM Associata (opzionale)</label>
|
|
<select id="vm_id" name="vm_id" class="form-control">
|
|
<option value="">Nessuna VM</option>
|
|
</select>
|
|
<small class="form-text">Collega questo sottodominio a una VM</small>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" id="proxied" name="proxied" checked>
|
|
<span>Abilita Cloudflare Proxy (CDN + Protezione DDoS)</span>
|
|
</label>
|
|
<small class="form-text">Consigliato per maggiore sicurezza e performance</small>
|
|
</div>
|
|
|
|
<div style="display: flex; gap: var(--space-sm); margin-top: var(--space-lg);">
|
|
<button type="submit" class="btn btn-primary" style="flex: 1;">Crea Sottodominio</button>
|
|
<button type="button" class="btn btn-secondary" onclick="closeCreateModal()">Annulla</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal per modificare IP -->
|
|
<div id="editModal" class="modal">
|
|
<div class="modal-content" style="max-width: 500px;">
|
|
<span class="close" onclick="closeEditModal()">×</span>
|
|
<h2>Modifica Indirizzo IP</h2>
|
|
|
|
<form id="editIpForm" onsubmit="updateSubdomainIp(event)">
|
|
<input type="hidden" id="edit_subdomain_id">
|
|
|
|
<div class="form-group">
|
|
<label for="edit_subdomain_name">Sottodominio</label>
|
|
<input type="text"
|
|
id="edit_subdomain_name"
|
|
class="form-control"
|
|
disabled
|
|
style="background: var(--bg-tertiary);">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="edit_ip_address">Nuovo Indirizzo IP *</label>
|
|
<input type="text"
|
|
id="edit_ip_address"
|
|
name="ip_address"
|
|
class="form-control"
|
|
placeholder="es: 192.168.1.100"
|
|
pattern="^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$"
|
|
required>
|
|
</div>
|
|
|
|
<div style="display: flex; gap: var(--space-sm); margin-top: var(--space-lg);">
|
|
<button type="submit" class="btn btn-primary" style="flex: 1;">Aggiorna IP</button>
|
|
<button type="button" class="btn btn-secondary" onclick="closeEditModal()">Annulla</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_scripts %}
|
|
<script>
|
|
const DOMAIN = '{{ domain }}';
|
|
let userVMs = [];
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
loadSubdomains();
|
|
loadUserVMs();
|
|
});
|
|
|
|
async function loadUserVMs() {
|
|
try {
|
|
const result = await apiCall('/api/my-vms');
|
|
if (result.status === 'success') {
|
|
userVMs = result.data;
|
|
const vmSelect = document.getElementById('vm_id');
|
|
vmSelect.innerHTML = '<option value="">Nessuna VM</option>' +
|
|
userVMs.map(vm => `<option value="${vm.vm_id}">VM ${vm.vm_id} - ${vm.vm_name}</option>`).join('');
|
|
}
|
|
} catch (error) {
|
|
console.error('Errore caricamento VM:', error);
|
|
}
|
|
}
|
|
|
|
async function loadSubdomains() {
|
|
const container = document.getElementById('subdomains-container');
|
|
container.innerHTML = '<div class="loading"><div class="spinner"></div><p>Caricamento sottodomini...</p></div>';
|
|
|
|
const result = await apiCall('/api/subdomains');
|
|
|
|
if (result.status === 'success') {
|
|
const subdomains = result.data;
|
|
|
|
if (subdomains.length === 0) {
|
|
container.innerHTML = `
|
|
<div class="no-vms">
|
|
<div class="no-vms-icon">
|
|
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
<circle cx="12" cy="12" r="10"/>
|
|
<path d="M2 12h20"/>
|
|
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>
|
|
</svg>
|
|
</div>
|
|
<h2>Nessun Sottodominio</h2>
|
|
<p>Clicca su "Crea Sottodominio" per iniziare</p>
|
|
</div>
|
|
`;
|
|
} else {
|
|
container.innerHTML = `
|
|
<div class="table-responsive">
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Sottodominio</th>
|
|
<th>Indirizzo IP</th>
|
|
<th>VM</th>
|
|
<th>Proxy</th>
|
|
<th>Creato</th>
|
|
<th>Azioni</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${subdomains.map(sub => {
|
|
const fullDomain = `${sub.subdomain}.${DOMAIN}`;
|
|
const vmInfo = sub.vm_id ? `VM ${sub.vm_id}` : '-';
|
|
const proxiedBadge = sub.proxied
|
|
? '<span class="badge badge-success">☁️ Proxied</span>'
|
|
: '<span class="badge badge-secondary">⚡ DNS Only</span>';
|
|
|
|
return `
|
|
<tr>
|
|
<td>
|
|
<div style="font-weight: 500;">${fullDomain}</div>
|
|
<a href="http://${fullDomain}" target="_blank" style="font-size: 0.8rem; color: var(--accent-primary);">
|
|
Apri →
|
|
</a>
|
|
</td>
|
|
<td><code>${sub.ip_address}</code></td>
|
|
<td>${vmInfo}</td>
|
|
<td>${proxiedBadge}</td>
|
|
<td style="color: var(--text-secondary); font-size: 0.9rem;">
|
|
${formatDate(sub.created_at)}
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-secondary"
|
|
onclick="showEditModal(${sub.id}, '${sub.subdomain}', '${sub.ip_address}')">
|
|
Modifica IP
|
|
</button>
|
|
<button class="btn btn-sm btn-danger"
|
|
onclick="deleteSubdomain(${sub.id}, '${fullDomain}')">
|
|
Elimina
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
}).join('')}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
`;
|
|
}
|
|
} else {
|
|
container.innerHTML = `
|
|
<div class="alert alert-error">
|
|
Errore nel caricamento: ${result.message}
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
function showCreateModal() {
|
|
document.getElementById('createModal').style.display = 'block';
|
|
document.getElementById('createSubdomainForm').reset();
|
|
document.getElementById('proxied').checked = true;
|
|
}
|
|
|
|
function closeCreateModal() {
|
|
document.getElementById('createModal').style.display = 'none';
|
|
}
|
|
|
|
async function createSubdomain(event) {
|
|
event.preventDefault();
|
|
|
|
const formData = new FormData(event.target);
|
|
const data = {
|
|
subdomain: formData.get('subdomain').toLowerCase().trim(),
|
|
ip_address: formData.get('ip_address').trim(),
|
|
vm_id: formData.get('vm_id') ? parseInt(formData.get('vm_id')) : null,
|
|
proxied: formData.get('proxied') === 'on'
|
|
};
|
|
|
|
showToast('Creazione sottodominio in corso...', 'info');
|
|
|
|
const result = await apiCall('/api/subdomain/create', 'POST', data);
|
|
|
|
if (result.status === 'success') {
|
|
showToast(result.message, 'success');
|
|
closeCreateModal();
|
|
loadSubdomains();
|
|
} else {
|
|
showAlert('Errore: ' + result.message, 'error');
|
|
}
|
|
}
|
|
|
|
function showEditModal(subdomainId, subdomain, currentIp) {
|
|
document.getElementById('edit_subdomain_id').value = subdomainId;
|
|
document.getElementById('edit_subdomain_name').value = `${subdomain}.${DOMAIN}`;
|
|
document.getElementById('edit_ip_address').value = currentIp;
|
|
document.getElementById('editModal').style.display = 'block';
|
|
}
|
|
|
|
function closeEditModal() {
|
|
document.getElementById('editModal').style.display = 'none';
|
|
}
|
|
|
|
async function updateSubdomainIp(event) {
|
|
event.preventDefault();
|
|
|
|
const subdomainId = document.getElementById('edit_subdomain_id').value;
|
|
const newIp = document.getElementById('edit_ip_address').value.trim();
|
|
|
|
showToast('Aggiornamento IP in corso...', 'info');
|
|
|
|
const result = await apiCall(`/api/subdomain/${subdomainId}/update-ip`, 'PUT', {
|
|
ip_address: newIp
|
|
});
|
|
|
|
if (result.status === 'success') {
|
|
showToast(result.message, 'success');
|
|
closeEditModal();
|
|
loadSubdomains();
|
|
} else {
|
|
showAlert('Errore: ' + result.message, 'error');
|
|
}
|
|
}
|
|
|
|
async function deleteSubdomain(subdomainId, fullDomain) {
|
|
const result = await Swal.fire({
|
|
title: 'Elimina Sottodominio',
|
|
html: `Vuoi eliminare il sottodominio <strong>${fullDomain}</strong>?<br>Questa operazione non può essere annullata.`,
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonText: 'Elimina',
|
|
cancelButtonText: 'Annulla',
|
|
confirmButtonColor: '#f85149'
|
|
});
|
|
|
|
if (!result.isConfirmed) return;
|
|
|
|
showToast('Eliminazione sottodominio in corso...', 'info');
|
|
|
|
const response = await apiCall(`/api/subdomain/${subdomainId}/delete`, 'DELETE');
|
|
|
|
if (response.status === 'success') {
|
|
showToast('Sottodominio eliminato con successo!', 'success');
|
|
loadSubdomains();
|
|
} else {
|
|
showAlert('Errore: ' + response.message, 'error');
|
|
}
|
|
}
|
|
|
|
// Chiudi modal cliccando fuori
|
|
window.onclick = function(event) {
|
|
if (event.target.classList.contains('modal')) {
|
|
event.target.style.display = 'none';
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.table-responsive {
|
|
overflow-x: auto;
|
|
background: var(--bg-secondary);
|
|
border-radius: var(--radius-lg);
|
|
padding: var(--space-md);
|
|
}
|
|
|
|
.data-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.data-table thead {
|
|
background: var(--bg-tertiary);
|
|
}
|
|
|
|
.data-table th {
|
|
padding: var(--space-md);
|
|
text-align: left;
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
text-transform: uppercase;
|
|
font-size: 0.75rem;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.data-table td {
|
|
padding: var(--space-md);
|
|
border-top: 1px solid var(--border-default);
|
|
}
|
|
|
|
.data-table tbody tr:hover {
|
|
background: var(--bg-tertiary);
|
|
}
|
|
|
|
.badge {
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
display: inline-block;
|
|
}
|
|
|
|
.badge-success {
|
|
background: rgba(34, 197, 94, 0.1);
|
|
color: #22c55e;
|
|
}
|
|
|
|
.badge-secondary {
|
|
background: var(--bg-tertiary);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: var(--space-lg);
|
|
}
|
|
|
|
.form-group label {
|
|
display: block;
|
|
margin-bottom: var(--space-sm);
|
|
font-weight: 500;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.form-control {
|
|
width: 100%;
|
|
padding: var(--space-sm) var(--space-md);
|
|
background: var(--bg-tertiary);
|
|
border: 1px solid var(--border-default);
|
|
border-radius: var(--radius-md);
|
|
color: var(--text-primary);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.form-control:focus {
|
|
outline: none;
|
|
border-color: var(--accent-primary);
|
|
}
|
|
|
|
.form-text {
|
|
display: block;
|
|
margin-top: var(--space-xs);
|
|
font-size: 0.8rem;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.checkbox-label {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-sm);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.checkbox-label input[type="checkbox"] {
|
|
width: 18px;
|
|
height: 18px;
|
|
cursor: pointer;
|
|
}
|
|
</style>
|
|
{% endblock %}
|