<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Proxmox MCP Admin{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css" rel="stylesheet">
<style>
.sidebar {
min-height: 100vh;
background-color: #343a40;
}
.sidebar .nav-link {
color: #fff;
}
.sidebar .nav-link:hover {
background-color: #495057;
color: #fff;
}
.sidebar .nav-link.active {
background-color: #007bff;
}
.main-content {
margin-left: 0;
}
@media (min-width: 768px) {
.main-content {
margin-left: 250px;
}
}
.status-badge {
font-size: 0.8em;
}
.device-card {
border-left: 4px solid #dee2e6;
}
.device-card.pending {
border-left-color: #ffc107;
}
.device-card.approved {
border-left-color: #28a745;
}
.device-card.revoked {
border-left-color: #dc3545;
}
.device-card.expired {
border-left-color: #6c757d;
}
.stats-card {
border-radius: 10px;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
.loading {
opacity: 0.6;
pointer-events: none;
}
.toast-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 1055;
}
</style>
</head>
<body class="bg-light">
<!-- Sidebar -->
<nav class="sidebar position-fixed d-none d-md-block">
<div class="p-3">
<h5 class="text-white mb-4">
<i class="bi bi-shield-check"></i>
Proxmox MCP Admin
</h5>
<ul class="nav nav-pills flex-column">
<li class="nav-item mb-2">
<a class="nav-link {% if active_page == 'dashboard' %}active{% endif %}" href="/">
<i class="bi bi-speedometer2"></i>
Dashboard
</a>
</li>
<li class="nav-item mb-2">
<a class="nav-link {% if active_page == 'pending' %}active{% endif %}" href="/pending">
<i class="bi bi-clock"></i>
Pending Requests
<span id="pending-count" class="badge bg-warning ms-2">0</span>
</a>
</li>
<li class="nav-item mb-2">
<a class="nav-link {% if active_page == 'devices' %}active{% endif %}" href="/devices">
<i class="bi bi-laptop"></i>
Approved Devices
</a>
</li>
<li class="nav-item mb-2">
<a class="nav-link {% if active_page == 'settings' %}active{% endif %}" href="/settings">
<i class="bi bi-gear"></i>
Settings
</a>
</li>
</ul>
</div>
</nav>
<!-- Mobile menu button -->
<nav class="navbar navbar-dark bg-dark d-md-none">
<div class="container-fluid">
<span class="navbar-brand">
<i class="bi bi-shield-check"></i>
Proxmox MCP Admin
</span>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mobileNav">
<span class="navbar-toggler-icon"></span>
</button>
</div>
<div class="collapse navbar-collapse" id="mobileNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link {% if active_page == 'dashboard' %}active{% endif %}" href="/">
<i class="bi bi-speedometer2"></i> Dashboard
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if active_page == 'pending' %}active{% endif %}" href="/pending">
<i class="bi bi-clock"></i> Pending Requests
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if active_page == 'devices' %}active{% endif %}" href="/devices">
<i class="bi bi-laptop"></i> Approved Devices
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if active_page == 'settings' %}active{% endif %}" href="/settings">
<i class="bi bi-gear"></i> Settings
</a>
</li>
</ul>
</div>
</nav>
<!-- Main content -->
<main class="main-content">
<div class="container-fluid p-4">
{% block content %}{% endblock %}
</div>
</main>
<!-- Toast container for notifications -->
<div class="toast-container" id="toast-container"></div>
<!-- Loading overlay -->
<div id="loading-overlay" class="d-none position-fixed w-100 h-100 bg-dark bg-opacity-50" style="top: 0; left: 0; z-index: 9999;">
<div class="d-flex justify-content-center align-items-center h-100">
<div class="spinner-border text-light" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Utility functions
function showToast(message, type = 'info') {
const toastContainer = document.getElementById('toast-container');
const toastId = 'toast-' + Date.now();
const toastHTML = `
<div id="${toastId}" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<i class="bi bi-${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-triangle' : 'info-circle'} text-${type === 'error' ? 'danger' : type}"></i>
<strong class="me-auto ms-2">
${type === 'success' ? 'Success' : type === 'error' ? 'Error' : 'Info'}
</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast"></button>
</div>
<div class="toast-body">${message}</div>
</div>
`;
toastContainer.insertAdjacentHTML('beforeend', toastHTML);
const toast = new bootstrap.Toast(document.getElementById(toastId));
toast.show();
// Remove toast element after it's hidden
document.getElementById(toastId).addEventListener('hidden.bs.toast', function() {
this.remove();
});
}
function showLoading() {
document.getElementById('loading-overlay').classList.remove('d-none');
}
function hideLoading() {
document.getElementById('loading-overlay').classList.add('d-none');
}
// API utility functions
async function apiCall(url, options = {}) {
try {
showLoading();
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers
},
...options
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.detail || 'Request failed');
}
return data;
} catch (error) {
showToast(error.message, 'error');
throw error;
} finally {
hideLoading();
}
}
// Update stats periodically
async function updateStats() {
try {
const stats = await apiCall('/api/stats');
const pendingCountEl = document.getElementById('pending-count');
if (pendingCountEl) {
pendingCountEl.textContent = stats.pending_requests || 0;
}
} catch (error) {
console.error('Failed to update stats:', error);
}
}
// Auto-refresh functionality
let autoRefreshInterval;
function startAutoRefresh(intervalMs = 30000) {
stopAutoRefresh();
autoRefreshInterval = setInterval(() => {
updateStats();
if (typeof refreshData === 'function') {
refreshData();
}
}, intervalMs);
}
function stopAutoRefresh() {
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
autoRefreshInterval = null;
}
}
// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
updateStats();
startAutoRefresh();
});
// Cleanup on page unload
window.addEventListener('beforeunload', function() {
stopAutoRefresh();
});
</script>
{% block scripts %}{% endblock %}
</body>
</html>