336 lines
12 KiB
HTML
336 lines
12 KiB
HTML
{% 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 %}
|