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

335
templates/overview.html Normal file
View File

@@ -0,0 +1,335 @@
{% extends "base.html" %}
{% block title %}Overview - Proxmox Manager{% endblock %}
{% block content %}
<div class="dashboard-header">
<div>
<h1>Dashboard Overview</h1>
<p class="subtitle">Riepilogo di tutte le tue macchine virtuali</p>
</div>
<div class="dashboard-actions">
<button class="btn btn-ghost btn-sm" onclick="loadOverviewStats()">
<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>
<!-- Stats Cards -->
<div class="stats-grid" id="stats-grid">
<div class="stat-card stat-total">
<div class="stat-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
<line x1="8" y1="21" x2="16" y2="21"/>
<line x1="12" y1="17" x2="12" y2="21"/>
</svg>
</div>
<div class="stat-content">
<div class="stat-value" id="total-vms">-</div>
<div class="stat-label">VM Totali</div>
</div>
</div>
<div class="stat-card stat-running">
<div class="stat-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--accent-green)" stroke-width="2">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
<polyline points="22 4 12 14.01 9 11.01"/>
</svg>
</div>
<div class="stat-content">
<div class="stat-value" id="running-vms">-</div>
<div class="stat-label">In Esecuzione</div>
</div>
</div>
<div class="stat-card stat-stopped">
<div class="stat-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--accent-red)" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
<line x1="15" y1="9" x2="9" y2="15"/>
<line x1="9" y1="9" x2="15" y2="15"/>
</svg>
</div>
<div class="stat-content">
<div class="stat-value" id="stopped-vms">-</div>
<div class="stat-label">Ferme</div>
</div>
</div>
<div class="stat-card stat-cpu">
<div class="stat-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--accent-purple)" stroke-width="2">
<rect x="4" y="4" width="16" height="16" rx="2" ry="2"/>
<rect x="9" y="9" width="6" height="6"/>
<line x1="9" y1="1" x2="9" y2="4"/>
<line x1="15" y1="1" x2="15" y2="4"/>
<line x1="9" y1="20" x2="9" y2="23"/>
<line x1="15" y1="20" x2="15" y2="23"/>
<line x1="20" y1="9" x2="23" y2="9"/>
<line x1="20" y1="14" x2="23" y2="14"/>
<line x1="1" y1="9" x2="4" y2="9"/>
<line x1="1" y1="14" x2="4" y2="14"/>
</svg>
</div>
<div class="stat-content">
<div class="stat-value" id="avg-cpu">-</div>
<div class="stat-label">CPU Medio</div>
</div>
</div>
</div>
<!-- Resource Charts -->
<div class="card mb-3">
<div class="card-title">Utilizzo Risorse</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-xl);">
<div>
<h3 style="color: var(--text-secondary); font-size: 0.9rem; margin-bottom: var(--space-md);">Memoria Totale</h3>
<div style="height: 200px; position: relative;">
<canvas id="memoryChart"></canvas>
</div>
</div>
<div>
<h3 style="color: var(--text-secondary); font-size: 0.9rem; margin-bottom: var(--space-md);">Stato VM</h3>
<div style="height: 200px; position: relative;">
<canvas id="statusChart"></canvas>
</div>
</div>
</div>
</div>
<!-- VM List -->
<div class="card">
<div class="card-title">Le Tue Macchine Virtuali</div>
<div class="vm-overview-list" id="vm-list">
<div class="loading">
<div class="spinner"></div>
<p>Caricamento...</p>
</div>
</div>
</div>
{% endblock %}
{% block extra_scripts %}
<script>
let memoryChart = null;
let statusChart = null;
document.addEventListener('DOMContentLoaded', function() {
loadOverviewStats();
});
async function loadOverviewStats() {
const result = await apiCall('/api/overview/stats');
if (result.status === 'success') {
const data = result.data;
// Update stat cards
document.getElementById('total-vms').textContent = data.total_vms;
document.getElementById('running-vms').textContent = data.running_vms;
document.getElementById('stopped-vms').textContent = data.stopped_vms;
document.getElementById('avg-cpu').textContent = data.avg_cpu_usage + '%';
// Render charts
renderMemoryChart(data);
renderStatusChart(data);
// Render VM list
renderVMList(data.vms);
} else {
document.getElementById('vm-list').innerHTML = `
<div class="alert alert-error">
Errore nel caricamento: ${result.message}
</div>
`;
}
}
function renderMemoryChart(data) {
const ctx = document.getElementById('memoryChart').getContext('2d');
if (memoryChart) {
memoryChart.destroy();
}
const usedMemory = data.total_memory_used || 0;
const freeMemory = (data.total_memory_max || 0) - usedMemory;
memoryChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['Utilizzata', 'Disponibile'],
datasets: [{
data: [usedMemory, freeMemory],
backgroundColor: ['#a371f7', '#21262d'],
borderWidth: 0,
borderRadius: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
cutout: '70%',
plugins: {
legend: {
display: true,
position: 'bottom',
labels: {
color: '#8b949e',
usePointStyle: true,
padding: 16
}
},
tooltip: {
backgroundColor: '#21262d',
titleColor: '#e6edf3',
bodyColor: '#8b949e',
borderColor: '#30363d',
borderWidth: 1,
callbacks: {
label: function(context) {
return formatBytes(context.raw);
}
}
}
}
},
plugins: [{
id: 'centerText',
afterDraw: function(chart) {
const ctx = chart.ctx;
const centerX = (chart.chartArea.left + chart.chartArea.right) / 2;
const centerY = (chart.chartArea.top + chart.chartArea.bottom) / 2;
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = '#e6edf3';
ctx.font = 'bold 24px sans-serif';
ctx.fillText(data.memory_percent + '%', centerX, centerY - 8);
ctx.fillStyle = '#8b949e';
ctx.font = '12px sans-serif';
ctx.fillText('utilizzata', centerX, centerY + 14);
ctx.restore();
}
}]
});
}
function renderStatusChart(data) {
const ctx = document.getElementById('statusChart').getContext('2d');
if (statusChart) {
statusChart.destroy();
}
statusChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['Running', 'Stopped'],
datasets: [{
data: [data.running_vms, data.stopped_vms],
backgroundColor: ['#3fb950', '#f85149'],
borderWidth: 0,
borderRadius: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
cutout: '70%',
plugins: {
legend: {
display: true,
position: 'bottom',
labels: {
color: '#8b949e',
usePointStyle: true,
padding: 16
}
},
tooltip: {
backgroundColor: '#21262d',
titleColor: '#e6edf3',
bodyColor: '#8b949e',
borderColor: '#30363d',
borderWidth: 1
}
}
},
plugins: [{
id: 'centerText',
afterDraw: function(chart) {
const ctx = chart.ctx;
const centerX = (chart.chartArea.left + chart.chartArea.right) / 2;
const centerY = (chart.chartArea.top + chart.chartArea.bottom) / 2;
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = '#e6edf3';
ctx.font = 'bold 24px sans-serif';
ctx.fillText(data.total_vms, centerX, centerY - 8);
ctx.fillStyle = '#8b949e';
ctx.font = '12px sans-serif';
ctx.fillText('totali', centerX, centerY + 14);
ctx.restore();
}
}]
});
}
function renderVMList(vms) {
const container = document.getElementById('vm-list');
if (!vms || vms.length === 0) {
container.innerHTML = `
<p class="text-muted text-center" style="padding: var(--space-xl);">
Nessuna VM assegnata
</p>
`;
return;
}
container.innerHTML = vms.map(vm => {
const statusClass = vm.status === 'running' ? 'status-running' : 'status-stopped';
const statusText = vm.status === 'running' ? 'Running' : 'Stopped';
const cpuPercent = Math.round((vm.cpu || 0) * 100);
return `
<div class="vm-overview-item">
<div class="vm-overview-info">
<span class="status-badge ${statusClass}" style="margin-right: var(--space-md);">${statusText}</span>
<div>
<div class="vm-overview-name">${vm.name}</div>
<div class="vm-overview-id">ID: ${vm.vm_id}</div>
</div>
</div>
<div class="vm-overview-metrics">
<div class="vm-overview-metric">
<div class="vm-overview-metric-value">${cpuPercent}%</div>
<div class="vm-overview-metric-label">CPU</div>
</div>
<div class="vm-overview-metric">
<div class="vm-overview-metric-value">${vm.memory_percent || 0}%</div>
<div class="vm-overview-metric-label">RAM</div>
</div>
</div>
<div class="vm-overview-actions">
<a href="{{ url_for('dashboard') }}" class="btn btn-ghost btn-sm">Gestisci</a>
</div>
</div>
`;
}).join('');
}
</script>
{% endblock %}