First Commit
This commit is contained in:
441
templates/domains.html
Normal file
441
templates/domains.html
Normal file
@@ -0,0 +1,441 @@
|
||||
{% 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 %}
|
||||
Reference in New Issue
Block a user