Files
proxmox_manager/templates/system_logs.html
2026-02-17 12:43:27 +01:00

440 lines
14 KiB
HTML

{% extends "base.html" %}
{% block title %}Log di Sistema - Proxmox Manager{% endblock %}
{% block extra_styles %}
<style>
.logs-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--space-lg);
flex-wrap: wrap;
gap: var(--space-md);
}
.logs-filters {
display: flex;
gap: var(--space-md);
align-items: center;
flex-wrap: wrap;
}
.filter-group {
display: flex;
flex-direction: column;
gap: var(--space-xs);
}
.filter-group label {
font-size: 0.85rem;
color: var(--text-secondary);
font-weight: 500;
}
.filter-group select,
.filter-group input {
padding: var(--space-sm) var(--space-md);
border: 1px solid var(--border-default);
border-radius: var(--radius-md);
background: var(--bg-secondary);
color: var(--text-primary);
font-size: 0.9rem;
}
.logs-table-container {
overflow-x: auto;
background: var(--bg-secondary);
border: 1px solid var(--border-default);
border-radius: var(--radius-lg);
}
.logs-table {
width: 100%;
border-collapse: collapse;
}
.logs-table thead {
background: var(--bg-tertiary);
border-bottom: 2px solid var(--border-default);
}
.logs-table th {
padding: var(--space-md);
text-align: left;
font-weight: 600;
color: var(--text-secondary);
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.logs-table tbody tr {
border-bottom: 1px solid var(--border-default);
transition: background var(--transition-fast);
}
.logs-table tbody tr:hover {
background: var(--bg-tertiary);
}
.logs-table td {
padding: var(--space-md);
color: var(--text-primary);
font-size: 0.9rem;
}
.log-status {
display: inline-flex;
align-items: center;
padding: var(--space-xs) var(--space-sm);
border-radius: var(--radius-sm);
font-size: 0.8rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.log-status.success {
background: rgba(63, 185, 80, 0.15);
color: var(--accent-green);
}
.log-status.failed {
background: rgba(255, 78, 80, 0.15);
color: var(--accent-red);
}
.log-action-badge {
display: inline-flex;
align-items: center;
gap: var(--space-xs);
padding: var(--space-xs) var(--space-sm);
background: rgba(88, 166, 255, 0.15);
color: var(--accent-blue);
border-radius: var(--radius-sm);
font-size: 0.85rem;
font-weight: 500;
}
.log-error-message {
color: var(--accent-red);
font-size: 0.85rem;
font-style: italic;
margin-top: var(--space-xs);
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: var(--space-md);
margin-top: var(--space-lg);
}
.pagination button {
padding: var(--space-sm) var(--space-md);
background: var(--bg-secondary);
border: 1px solid var(--border-default);
border-radius: var(--radius-md);
color: var(--text-primary);
cursor: pointer;
transition: all var(--transition-fast);
}
.pagination button:hover:not(:disabled) {
background: var(--accent-blue);
color: white;
border-color: var(--accent-blue);
}
.pagination button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.pagination-info {
color: var(--text-secondary);
font-size: 0.9rem;
}
.export-btn {
padding: var(--space-sm) var(--space-md);
background: var(--accent-blue);
color: white;
border: none;
border-radius: var(--radius-md);
font-weight: 500;
cursor: pointer;
transition: all var(--transition-fast);
}
.export-btn:hover {
background: var(--accent-blue-hover);
transform: translateY(-1px);
}
.no-logs {
text-align: center;
padding: var(--space-xl);
color: var(--text-tertiary);
}
</style>
{% endblock %}
{% block content %}
<div class="logs-header">
<div>
<h1>Log di Sistema</h1>
<p class="subtitle">Visualizza tutte le attività del sistema</p>
</div>
<button class="export-btn" onclick="exportLogs()">
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16" style="margin-right: 8px; vertical-align: middle;">
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/>
<path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z"/>
</svg>
Esporta CSV
</button>
</div>
<div class="card">
<div class="logs-filters">
<div class="filter-group">
<label>Utente</label>
<select id="filterUser" onchange="filterLogs()">
<option value="">Tutti gli utenti</option>
</select>
</div>
<div class="filter-group">
<label>Azione</label>
<select id="filterAction" onchange="filterLogs()">
<option value="">Tutte le azioni</option>
<option value="start">Start</option>
<option value="stop">Stop</option>
<option value="restart">Restart</option>
<option value="shutdown">Shutdown</option>
<option value="snapshot">Snapshot</option>
<option value="backup">Backup</option>
<option value="login">Login</option>
</select>
</div>
<div class="filter-group">
<label>Stato</label>
<select id="filterStatus" onchange="filterLogs()">
<option value="">Tutti gli stati</option>
<option value="success">Successo</option>
<option value="failed">Fallito</option>
</select>
</div>
<div class="filter-group">
<label>VM ID</label>
<input type="number" id="filterVmId" placeholder="ID VM" onchange="filterLogs()" min="0">
</div>
<div class="filter-group">
<label>Data</label>
<input type="date" id="filterDate" onchange="filterLogs()">
</div>
<div class="filter-group">
<label>Limiti</label>
<select id="filterLimit" onchange="filterLogs()">
<option value="50">50 record</option>
<option value="100" selected>100 record</option>
<option value="200">200 record</option>
<option value="500">500 record</option>
</select>
</div>
</div>
</div>
<div class="card">
<div class="logs-table-container">
<table class="logs-table">
<thead>
<tr>
<th>ID</th>
<th>Data/Ora</th>
<th>Utente</th>
<th>Azione</th>
<th>VM ID</th>
<th>Stato</th>
<th>IP</th>
<th>Dettagli</th>
</tr>
</thead>
<tbody id="logsTableBody">
<tr>
<td colspan="8" class="no-logs">
<div class="loading">
<div class="spinner"></div>
<p>Caricamento log...</p>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="pagination" id="pagination" style="display: none;">
<button id="prevBtn" onclick="changePage(-1)">← Precedente</button>
<span class="pagination-info" id="pageInfo">Pagina 1</span>
<button id="nextBtn" onclick="changePage(1)">Successiva →</button>
</div>
</div>
{% endblock %}
{% block extra_scripts %}
<script>
let allLogs = [];
let filteredLogs = [];
let currentPage = 1;
const logsPerPage = 50;
document.addEventListener('DOMContentLoaded', function() {
loadLogs();
loadUsers();
});
async function loadUsers() {
const result = await apiCall('/api/admin/users');
if (result.status === 'success') {
const filterUser = document.getElementById('filterUser');
result.data.forEach(user => {
const option = document.createElement('option');
option.value = user.username;
option.textContent = user.username;
filterUser.appendChild(option);
});
}
}
async function loadLogs() {
const limit = document.getElementById('filterLimit').value;
const result = await apiCall(`/api/admin/logs?limit=${limit}`);
if (result.status === 'success') {
allLogs = result.data;
filterLogs();
} else {
document.getElementById('logsTableBody').innerHTML = `
<tr><td colspan="8" class="no-logs">
<div class="alert alert-error">${result.message}</div>
</td></tr>
`;
}
}
function filterLogs() {
const filterUser = document.getElementById('filterUser').value.toLowerCase();
const filterAction = document.getElementById('filterAction').value.toLowerCase();
const filterStatus = document.getElementById('filterStatus').value.toLowerCase();
const filterVmId = document.getElementById('filterVmId').value;
const filterDate = document.getElementById('filterDate').value;
filteredLogs = allLogs.filter(log => {
const matchUser = !filterUser || log.username.toLowerCase().includes(filterUser);
const matchAction = !filterAction || log.action_type === filterAction;
const matchStatus = !filterStatus || log.status === filterStatus;
const matchVmId = !filterVmId || log.vm_id.toString() === filterVmId;
const matchDate = !filterDate || new Date(log.created_at).toISOString().split('T')[0] === filterDate;
return matchUser && matchAction && matchStatus && matchVmId && matchDate;
});
currentPage = 1;
renderLogs();
}
function renderLogs() {
const tbody = document.getElementById('logsTableBody');
const pagination = document.getElementById('pagination');
if (filteredLogs.length === 0) {
tbody.innerHTML = '<tr><td colspan="8" class="no-logs">Nessun log trovato con i filtri applicati</td></tr>';
pagination.style.display = 'none';
return;
}
const totalPages = Math.ceil(filteredLogs.length / logsPerPage);
const startIdx = (currentPage - 1) * logsPerPage;
const endIdx = startIdx + logsPerPage;
const logsToShow = filteredLogs.slice(startIdx, endIdx);
const actionIcons = {
'start': '▶️',
'stop': '⏹️',
'restart': '🔄',
'shutdown': '🛑',
'snapshot': '📸',
'backup': '💾',
'login': '🔐'
};
tbody.innerHTML = logsToShow.map(log => {
const date = new Date(log.created_at);
const formattedDate = date.toLocaleString('it-IT');
const icon = actionIcons[log.action_type] || '•';
return `
<tr>
<td><strong>#${log.id}</strong></td>
<td>${formattedDate}</td>
<td><strong>${log.username}</strong></td>
<td><span class="log-action-badge">${icon} ${log.action_type}</span></td>
<td>${log.vm_id > 0 ? `VM ${log.vm_id}` : '-'}</td>
<td><span class="log-status ${log.status}">${log.status === 'success' ? 'Successo' : 'Fallito'}</span></td>
<td><code>${log.ip_address || '-'}</code></td>
<td>
${log.error_message ? `<div class="log-error-message">${log.error_message}</div>` : '-'}
</td>
</tr>
`;
}).join('');
// Update pagination
document.getElementById('pageInfo').textContent = `Pagina ${currentPage} di ${totalPages} (${filteredLogs.length} risultati)`;
document.getElementById('prevBtn').disabled = currentPage === 1;
document.getElementById('nextBtn').disabled = currentPage === totalPages;
pagination.style.display = 'flex';
}
function changePage(delta) {
currentPage += delta;
renderLogs();
}
function exportLogs() {
if (filteredLogs.length === 0) {
showAlert('Nessun log da esportare', 'warning');
return;
}
const headers = ['ID', 'Data/Ora', 'Utente', 'Azione', 'VM ID', 'Stato', 'IP', 'Errore'];
const rows = filteredLogs.map(log => [
log.id,
new Date(log.created_at).toLocaleString('it-IT'),
log.username,
log.action_type,
log.vm_id,
log.status,
log.ip_address || '',
log.error_message || ''
]);
let csvContent = headers.join(',') + '\n';
csvContent += rows.map(row => row.map(cell => `"${cell}"`).join(',')).join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', `system_logs_${new Date().toISOString().split('T')[0]}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
showAlert('Log esportati con successo', 'success');
}
</script>
{% endblock %}