368 lines
13 KiB
HTML
368 lines
13 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="it">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>{% block title %}Proxmox Manager{% endblock %}</title>
|
|
|
|
<!-- Chart.js per grafici -->
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|
|
|
<!-- SweetAlert2 per dialoghi -->
|
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
|
|
|
<!-- Theme CSS -->
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/theme.css') }}">
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/dashboard.css') }}">
|
|
|
|
<!-- SweetAlert2 Dark Theme Override -->
|
|
<style>
|
|
/* SweetAlert2 Dark Theme */
|
|
.swal2-popup {
|
|
background: var(--bg-secondary) !important;
|
|
border: 1px solid var(--border-default) !important;
|
|
border-radius: var(--radius-lg) !important;
|
|
color: var(--text-primary) !important;
|
|
}
|
|
|
|
.swal2-title {
|
|
color: var(--text-primary) !important;
|
|
}
|
|
|
|
.swal2-html-container {
|
|
color: var(--text-secondary) !important;
|
|
}
|
|
|
|
.swal2-confirm {
|
|
background: var(--accent-blue) !important;
|
|
border-radius: var(--radius-md) !important;
|
|
}
|
|
|
|
.swal2-cancel {
|
|
background: var(--bg-tertiary) !important;
|
|
color: var(--text-primary) !important;
|
|
border-radius: var(--radius-md) !important;
|
|
}
|
|
|
|
.swal2-timer-progress-bar {
|
|
background: var(--accent-blue) !important;
|
|
}
|
|
</style>
|
|
|
|
{% block extra_styles %}{% endblock %}
|
|
</head>
|
|
<body>
|
|
{% if current_user.is_authenticated %}
|
|
<nav class="navbar">
|
|
<a href="{{ url_for('dashboard') }}" class="navbar-brand">
|
|
<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>
|
|
Proxmox Manager
|
|
</a>
|
|
<div class="navbar-menu">
|
|
<a href="{{ url_for('dashboard') }}" class="{{ 'active' if request.endpoint == 'dashboard' else '' }}">
|
|
Dashboard
|
|
</a>
|
|
<a href="{{ url_for('overview') }}" class="{{ 'active' if request.endpoint == 'overview' else '' }}">
|
|
Overview
|
|
</a>
|
|
<a href="{{ url_for('domains') }}" class="{{ 'active' if request.endpoint == 'domains' else '' }}">
|
|
Domini
|
|
</a>
|
|
{% if current_user.is_admin %}
|
|
<a href="{{ url_for('admin_users') }}" class="{{ 'active' if request.endpoint == 'admin_users' else '' }}">
|
|
Gestione Utenti
|
|
</a>
|
|
{% endif %}
|
|
<a href="{{ url_for('profile') }}" class="{{ 'active' if request.endpoint == 'profile' else '' }}">
|
|
Profilo
|
|
</a>
|
|
</div>
|
|
<div class="navbar-user">
|
|
<span>{{ current_user.username }}</span>
|
|
{% if current_user.is_admin %}
|
|
<span class="badge-admin">ADMIN</span>
|
|
{% endif %}
|
|
<a href="{{ url_for('logout') }}" class="btn btn-ghost btn-sm">Logout</a>
|
|
</div>
|
|
</nav>
|
|
{% endif %}
|
|
|
|
<div class="container">
|
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
{% if messages %}
|
|
{% for category, message in messages %}
|
|
<div class="alert alert-{{ category }}">
|
|
{{ message }}
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
{% endwith %}
|
|
|
|
{% block content %}{% endblock %}
|
|
</div>
|
|
|
|
<!-- Notification Container -->
|
|
<div id="notification-container" class="notification-container"></div>
|
|
|
|
<script>
|
|
// Helper function per chiamate API
|
|
async function apiCall(url, method = 'GET', data = null) {
|
|
const options = {
|
|
method: method,
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
};
|
|
|
|
if (data) {
|
|
options.body = JSON.stringify(data);
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(url, options);
|
|
const result = await response.json();
|
|
return result;
|
|
} catch (error) {
|
|
console.error('API Error:', error);
|
|
return { status: 'error', message: error.message };
|
|
}
|
|
}
|
|
|
|
// Helper per mostrare alert con SweetAlert2
|
|
function showAlert(message, type = 'info') {
|
|
const icons = {
|
|
'success': 'success',
|
|
'error': 'error',
|
|
'info': 'info',
|
|
'warning': 'warning'
|
|
};
|
|
|
|
Swal.fire({
|
|
icon: icons[type] || 'info',
|
|
title: type === 'error' ? 'Errore' : type === 'success' ? 'Successo' : 'Info',
|
|
text: message,
|
|
showConfirmButton: true,
|
|
timer: type === 'success' ? 3000 : undefined,
|
|
timerProgressBar: type === 'success',
|
|
background: 'var(--bg-secondary)',
|
|
color: 'var(--text-primary)',
|
|
confirmButtonColor: 'var(--accent-blue)'
|
|
});
|
|
}
|
|
|
|
// Toast notification leggera
|
|
function showToast(message, type = 'info') {
|
|
const container = document.getElementById('notification-container');
|
|
const toast = document.createElement('div');
|
|
toast.className = `notification-toast notification-${type}`;
|
|
|
|
const icons = {
|
|
'success': '<svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/></svg>',
|
|
'error': '<svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/></svg>',
|
|
'info': '<svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/></svg>',
|
|
'warning': '<svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>'
|
|
};
|
|
|
|
toast.innerHTML = `
|
|
<div class="notification-icon">${icons[type] || icons.info}</div>
|
|
<div class="notification-message">${message}</div>
|
|
<button class="notification-close" onclick="this.parentElement.remove()">×</button>
|
|
`;
|
|
|
|
container.appendChild(toast);
|
|
|
|
// Trigger animation
|
|
requestAnimationFrame(() => {
|
|
toast.classList.add('show');
|
|
});
|
|
|
|
// Auto dismiss
|
|
setTimeout(() => {
|
|
toast.classList.remove('show');
|
|
setTimeout(() => toast.remove(), 300);
|
|
}, 5000);
|
|
}
|
|
|
|
// Helper per formattare timestamp
|
|
function formatDate(timestamp) {
|
|
const date = new Date(timestamp * 1000);
|
|
return date.toLocaleString('it-IT');
|
|
}
|
|
|
|
// Helper per formattare byte
|
|
function formatBytes(bytes) {
|
|
if (!bytes || bytes === 0) return '0 B';
|
|
const k = 1024;
|
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return (bytes / Math.pow(k, i)).toFixed(1) + ' ' + sizes[i];
|
|
}
|
|
|
|
// Helper per formattare uptime
|
|
function formatUptime(seconds) {
|
|
if (!seconds) return 'N/A';
|
|
const days = Math.floor(seconds / 86400);
|
|
const hours = Math.floor((seconds % 86400) / 3600);
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
|
|
if (days > 0) return `${days}g ${hours}h ${minutes}m`;
|
|
if (hours > 0) return `${hours}h ${minutes}m`;
|
|
return `${minutes}m`;
|
|
}
|
|
|
|
// Notification System with SSE
|
|
class NotificationSystem {
|
|
constructor() {
|
|
this.eventSource = null;
|
|
this.reconnectAttempts = 0;
|
|
this.maxReconnectAttempts = 5;
|
|
}
|
|
|
|
connect() {
|
|
if (this.eventSource) {
|
|
this.eventSource.close();
|
|
}
|
|
|
|
this.eventSource = new EventSource('/api/notifications/stream');
|
|
|
|
this.eventSource.onmessage = (event) => {
|
|
try {
|
|
const data = JSON.parse(event.data);
|
|
if (data.type !== 'ping') {
|
|
showToast(data.message, data.notification_type || 'info');
|
|
}
|
|
} catch (e) {
|
|
console.error('Error parsing notification:', e);
|
|
}
|
|
};
|
|
|
|
this.eventSource.onerror = () => {
|
|
this.eventSource.close();
|
|
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
this.reconnectAttempts++;
|
|
setTimeout(() => this.connect(), 5000);
|
|
}
|
|
};
|
|
|
|
this.eventSource.onopen = () => {
|
|
this.reconnectAttempts = 0;
|
|
};
|
|
}
|
|
|
|
disconnect() {
|
|
if (this.eventSource) {
|
|
this.eventSource.close();
|
|
this.eventSource = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize notification system
|
|
const notificationSystem = new NotificationSystem();
|
|
|
|
// Connect if user is authenticated
|
|
{% if current_user.is_authenticated %}
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
notificationSystem.connect();
|
|
});
|
|
{% endif %}
|
|
</script>
|
|
|
|
<!-- Notification Styles -->
|
|
<style>
|
|
.notification-container {
|
|
position: fixed;
|
|
top: 80px;
|
|
right: var(--space-lg);
|
|
z-index: 9999;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-sm);
|
|
max-width: 400px;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.notification-toast {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-md);
|
|
padding: var(--space-md);
|
|
background: var(--bg-secondary);
|
|
border: 1px solid var(--border-default);
|
|
border-radius: var(--radius-md);
|
|
box-shadow: var(--shadow-lg);
|
|
transform: translateX(120%);
|
|
transition: transform 0.3s ease;
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.notification-toast.show {
|
|
transform: translateX(0);
|
|
}
|
|
|
|
.notification-toast.notification-success {
|
|
border-left: 4px solid var(--accent-green);
|
|
}
|
|
|
|
.notification-toast.notification-success .notification-icon {
|
|
color: var(--accent-green);
|
|
}
|
|
|
|
.notification-toast.notification-error {
|
|
border-left: 4px solid var(--accent-red);
|
|
}
|
|
|
|
.notification-toast.notification-error .notification-icon {
|
|
color: var(--accent-red);
|
|
}
|
|
|
|
.notification-toast.notification-warning {
|
|
border-left: 4px solid var(--accent-yellow);
|
|
}
|
|
|
|
.notification-toast.notification-warning .notification-icon {
|
|
color: var(--accent-yellow);
|
|
}
|
|
|
|
.notification-toast.notification-info {
|
|
border-left: 4px solid var(--accent-blue);
|
|
}
|
|
|
|
.notification-toast.notification-info .notification-icon {
|
|
color: var(--accent-blue);
|
|
}
|
|
|
|
.notification-icon {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.notification-message {
|
|
flex: 1;
|
|
color: var(--text-primary);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.notification-close {
|
|
background: none;
|
|
border: none;
|
|
color: var(--text-tertiary);
|
|
cursor: pointer;
|
|
font-size: 1.25rem;
|
|
padding: 0;
|
|
line-height: 1;
|
|
transition: color var(--transition-fast);
|
|
}
|
|
|
|
.notification-close:hover {
|
|
color: var(--text-primary);
|
|
}
|
|
</style>
|
|
|
|
{% block extra_scripts %}{% endblock %}
|
|
</body>
|
|
</html>
|