First Commit

This commit is contained in:
dedhersel
2026-02-17 12:43:27 +01:00
commit 38f85dc498
26 changed files with 9939 additions and 0 deletions

441
templates/domains.html Normal file
View 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()">&times;</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()">&times;</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 %}