All checks were successful
continuous-integration/drone/push Build is passing
✨ New Features: - Dynamic permission-based context menus for files and folders - Support for collaborative folder access control - Upload to specific folders including shared folders - Changelog modal for version updates - Improved dark mode synchronization 🐛 Bug Fixes: - Fixed context menu displaying incorrect options - Fixed CSS !important override preventing dynamic menu behavior - Fixed folder collaboration permission checks - Fixed breadcrumb navigation with empty segments - Fixed "Premature close" error loop in attachments - Fixed missing user variable in admin routes - Fixed avatar loading COEP policy issues 🔒 Security: - Added security middleware (CSRF, rate limiting, input validation) - Fixed collaboration folder access validation - Improved shared folder permission handling 🎨 UI/UX Improvements: - Removed Actions column from folder view - Context menu now properly hides/shows based on permissions - Better visual feedback for collaborative folders - Improved upload flow with inline modals 🧹 Code Quality: - Added collaboration data to folder routes - Refactored context menu logic for better maintainability - Added debug logging for troubleshooting - Improved file upload handling with chunking support
976 lines
34 KiB
Plaintext
976 lines
34 KiB
Plaintext
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Gestion des Utilisateurs - Interface Moderne</title>
|
|
<link rel="icon" href="/public/assets/homelab_logo.png" />
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
|
<style>
|
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
|
|
|
:root {
|
|
--background: 0 0% 100%;
|
|
--foreground: 222.2 84% 4.9%;
|
|
--card: 0 0% 100%;
|
|
--card-foreground: 222.2 84% 4.9%;
|
|
--popover: 0 0% 100%;
|
|
--popover-foreground: 222.2 84% 4.9%;
|
|
--primary: 222.2 47.4% 11.2%;
|
|
--primary-foreground: 210 40% 98%;
|
|
--secondary: 210 40% 96.1%;
|
|
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
--muted: 210 40% 96.1%;
|
|
--muted-foreground: 215.4 16.3% 46.9%;
|
|
--accent: 210 40% 96.1%;
|
|
--accent-foreground: 222.2 47.4% 11.2%;
|
|
--destructive: 0 84.2% 60.2%;
|
|
--destructive-foreground: 210 40% 98%;
|
|
--border: 214.3 31.8% 91.4%;
|
|
--input: 214.3 31.8% 91.4%;
|
|
--ring: 222.2 84% 4.9%;
|
|
--radius: 0.5rem;
|
|
}
|
|
|
|
.dark {
|
|
--background: 222.2 84% 4.9%;
|
|
--foreground: 210 40% 98%;
|
|
--card: 222.2 84% 4.9%;
|
|
--card-foreground: 210 40% 98%;
|
|
--popover: 222.2 84% 4.9%;
|
|
--popover-foreground: 210 40% 98%;
|
|
--primary: 210 40% 98%;
|
|
--primary-foreground: 222.2 47.4% 11.2%;
|
|
--secondary: 217.2 32.6% 17.5%;
|
|
--secondary-foreground: 210 40% 98%;
|
|
--muted: 217.2 32.6% 17.5%;
|
|
--muted-foreground: 215 20.2% 65.1%;
|
|
--accent: 217.2 32.6% 17.5%;
|
|
--accent-foreground: 210 40% 98%;
|
|
--destructive: 0 62.8% 30.6%;
|
|
--destructive-foreground: 210 40% 98%;
|
|
--border: 217.2 32.6% 17.5%;
|
|
--input: 217.2 32.6% 17.5%;
|
|
--ring: 212.7 26.8% 83.9%;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Inter', sans-serif;
|
|
background-color: hsl(var(--background));
|
|
color: hsl(var(--foreground));
|
|
transition: background-color 0.3s ease, color 0.3s ease;
|
|
margin: 0;
|
|
padding: 0;
|
|
min-height: 100vh;
|
|
background-image: url('<%= user.wallpaper %>');
|
|
background-size: cover;
|
|
background-position: center;
|
|
background-repeat: no-repeat;
|
|
background-attachment: fixed;
|
|
}
|
|
|
|
.backdrop {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0, 0, 0, 0.3);
|
|
backdrop-filter: blur(8px);
|
|
-webkit-backdrop-filter: blur(8px);
|
|
z-index: 1;
|
|
}
|
|
|
|
.container {
|
|
position: relative;
|
|
z-index: 2;
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
padding: 2rem;
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.admin-card {
|
|
background: hsl(var(--card));
|
|
border: 1px solid hsl(var(--border));
|
|
border-radius: 16px;
|
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.05);
|
|
backdrop-filter: blur(12px);
|
|
-webkit-backdrop-filter: blur(12px);
|
|
overflow: hidden;
|
|
width: 100%;
|
|
animation: slideIn 0.5s ease-out;
|
|
}
|
|
|
|
@keyframes slideIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(20px) scale(0.95);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0) scale(1);
|
|
}
|
|
}
|
|
|
|
/* Header de la page admin */
|
|
.admin-header {
|
|
padding: 2rem;
|
|
text-align: center;
|
|
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #d946ef 100%);
|
|
color: hsl(var(--primary-foreground));
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.admin-header::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="20" cy="20" r="2" fill="white" opacity="0.1"/><circle cx="80" cy="80" r="2" fill="white" opacity="0.1"/><circle cx="40" cy="60" r="1" fill="white" opacity="0.05"/></svg>');
|
|
opacity: 0.3;
|
|
}
|
|
|
|
.admin-title {
|
|
position: relative;
|
|
z-index: 2;
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
margin-bottom: 0.5rem;
|
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.admin-subtitle {
|
|
position: relative;
|
|
z-index: 2;
|
|
font-size: 1rem;
|
|
opacity: 0.9;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
/* Section des contrôles */
|
|
.controls-section {
|
|
padding: 2rem;
|
|
background: hsl(var(--card));
|
|
border-bottom: 1px solid hsl(var(--border));
|
|
}
|
|
|
|
.controls-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr auto auto;
|
|
gap: 1rem;
|
|
align-items: center;
|
|
}
|
|
|
|
/* Barre de recherche */
|
|
.search-container {
|
|
position: relative;
|
|
flex: 1;
|
|
}
|
|
|
|
.search-input {
|
|
width: 100%;
|
|
padding: 0.875rem 1rem 0.875rem 3rem;
|
|
border: 1px solid hsl(var(--border));
|
|
border-radius: 25px;
|
|
background-color: hsl(var(--background));
|
|
color: hsl(var(--foreground));
|
|
font-size: 0.95rem;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.search-input:focus {
|
|
outline: none;
|
|
border-color: hsl(var(--ring));
|
|
box-shadow: 0 0 0 3px hsla(var(--ring), 0.1);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.search-icon {
|
|
position: absolute;
|
|
left: 1rem;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
color: hsl(var(--muted-foreground));
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
.search-hint {
|
|
position: absolute;
|
|
right: 1rem;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
font-size: 0.75rem;
|
|
color: hsl(var(--muted-foreground));
|
|
background: hsl(var(--muted));
|
|
padding: 0.25rem 0.5rem;
|
|
border-radius: 4px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* Sélecteur de pagination */
|
|
.pagination-selector {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.pagination-selector label {
|
|
font-weight: 500;
|
|
color: hsl(var(--foreground));
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.pagination-select {
|
|
padding: 0.75rem 2.5rem 0.75rem 1rem;
|
|
border: 1px solid hsl(var(--border));
|
|
border-radius: 8px;
|
|
background-color: hsl(var(--background));
|
|
color: hsl(var(--foreground));
|
|
font-size: 0.95rem;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
appearance: none;
|
|
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>');
|
|
background-repeat: no-repeat;
|
|
background-position: right 0.75rem center;
|
|
background-size: 1rem;
|
|
}
|
|
|
|
.pagination-select:focus {
|
|
outline: none;
|
|
border-color: hsl(var(--ring));
|
|
box-shadow: 0 0 0 3px hsla(var(--ring), 0.1);
|
|
}
|
|
|
|
/* Boutons modernes */
|
|
.btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.5rem;
|
|
padding: 0.75rem 1.5rem;
|
|
border-radius: 25px;
|
|
font-weight: 500;
|
|
font-size: 0.9rem;
|
|
transition: all 0.3s ease;
|
|
cursor: pointer;
|
|
border: none;
|
|
text-decoration: none;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.btn::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: -100%;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
|
transition: left 0.5s;
|
|
}
|
|
|
|
.btn:hover::before {
|
|
left: 100%;
|
|
}
|
|
|
|
.btn:active {
|
|
transform: scale(0.98);
|
|
}
|
|
|
|
.btn-primary {
|
|
background: linear-gradient(135deg, hsl(var(--primary)) 0%, hsl(var(--ring)) 100%);
|
|
color: hsl(var(--primary-foreground));
|
|
box-shadow: 0 4px 14px 0 rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 8px 25px 0 rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: hsl(var(--secondary));
|
|
color: hsl(var(--secondary-foreground));
|
|
border: 1px solid hsl(var(--border));
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background: hsl(var(--accent));
|
|
color: hsl(var(--accent-foreground));
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.btn-small {
|
|
padding: 0.5rem 1rem;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
/* Tableau des utilisateurs */
|
|
.table-container {
|
|
padding: 2rem;
|
|
overflow-x: auto;
|
|
}
|
|
|
|
.admin-table {
|
|
width: 100%;
|
|
border-collapse: separate;
|
|
border-spacing: 0 0.5rem;
|
|
}
|
|
|
|
.admin-table thead th {
|
|
padding: 1rem 1.5rem;
|
|
text-align: left;
|
|
font-weight: 600;
|
|
font-size: 0.85rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
color: hsl(var(--muted-foreground));
|
|
background: transparent;
|
|
border: none;
|
|
}
|
|
|
|
.admin-table tbody tr {
|
|
background: hsl(var(--card));
|
|
border: 1px solid hsl(var(--border));
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.admin-table tbody tr:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
border-color: hsl(var(--ring));
|
|
}
|
|
|
|
.admin-table tbody tr td:first-child {
|
|
border-top-left-radius: 12px;
|
|
border-bottom-left-radius: 12px;
|
|
}
|
|
|
|
.admin-table tbody tr td:last-child {
|
|
border-top-right-radius: 12px;
|
|
border-bottom-right-radius: 12px;
|
|
}
|
|
|
|
.admin-table tbody td {
|
|
padding: 1.25rem 1.5rem;
|
|
vertical-align: middle;
|
|
border: none;
|
|
}
|
|
|
|
/* Photo de profil dans le tableau */
|
|
.user-avatar {
|
|
width: 48px;
|
|
height: 48px;
|
|
border-radius: 50%;
|
|
object-fit: cover;
|
|
border: 2px solid hsl(var(--border));
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.user-avatar:hover {
|
|
transform: scale(1.1);
|
|
border-color: hsl(var(--ring));
|
|
}
|
|
|
|
/* Badges de rôle */
|
|
.role-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.35rem;
|
|
padding: 0.4rem 0.9rem;
|
|
border-radius: 20px;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.role-badge.admin {
|
|
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
|
|
color: white;
|
|
box-shadow: 0 2px 8px rgba(245, 158, 11, 0.3);
|
|
}
|
|
|
|
.role-badge.user {
|
|
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
color: white;
|
|
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
|
|
}
|
|
|
|
/* Colonnes du tableau */
|
|
.user-id {
|
|
font-weight: 600;
|
|
color: hsl(var(--muted-foreground));
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.user-name {
|
|
font-weight: 600;
|
|
color: hsl(var(--foreground));
|
|
font-size: 1rem;
|
|
}
|
|
|
|
/* Actions */
|
|
.actions-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.action-form {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
align-items: center;
|
|
}
|
|
|
|
.role-select {
|
|
padding: 0.5rem 2rem 0.5rem 0.75rem;
|
|
border: 1px solid hsl(var(--border));
|
|
border-radius: 8px;
|
|
background-color: hsl(var(--background));
|
|
color: hsl(var(--foreground));
|
|
font-size: 0.85rem;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
appearance: none;
|
|
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>');
|
|
background-repeat: no-repeat;
|
|
background-position: right 0.5rem center;
|
|
background-size: 0.9rem;
|
|
}
|
|
|
|
.role-select:focus {
|
|
outline: none;
|
|
border-color: hsl(var(--ring));
|
|
box-shadow: 0 0 0 3px hsla(var(--ring), 0.1);
|
|
}
|
|
|
|
/* Pagination en bas */
|
|
.pagination-container {
|
|
padding: 2rem;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
border-top: 1px solid hsl(var(--border));
|
|
}
|
|
|
|
.pagination-link {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 2.5rem;
|
|
height: 2.5rem;
|
|
padding: 0 0.75rem;
|
|
border: 1px solid hsl(var(--border));
|
|
border-radius: 8px;
|
|
background: hsl(var(--background));
|
|
color: hsl(var(--foreground));
|
|
font-weight: 500;
|
|
font-size: 0.9rem;
|
|
text-decoration: none;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.pagination-link:hover {
|
|
background: hsl(var(--accent));
|
|
border-color: hsl(var(--ring));
|
|
transform: translateY(-2px);
|
|
text-decoration: none;
|
|
}
|
|
|
|
.pagination-link.active {
|
|
background: linear-gradient(135deg, hsl(var(--primary)) 0%, hsl(var(--ring)) 100%);
|
|
color: hsl(var(--primary-foreground));
|
|
border-color: transparent;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
/* Footer */
|
|
.footer-section {
|
|
padding: 2rem;
|
|
text-align: center;
|
|
border-top: 1px solid hsl(var(--border));
|
|
}
|
|
|
|
/* Theme switcher */
|
|
.theme-switcher {
|
|
position: fixed;
|
|
top: 2rem;
|
|
right: 2rem;
|
|
z-index: 10;
|
|
background: hsl(var(--card));
|
|
border: 1px solid hsl(var(--border));
|
|
border-radius: 50%;
|
|
padding: 0.75rem;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.theme-switcher:hover {
|
|
transform: scale(1.1);
|
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.theme-switcher svg {
|
|
width: 1.25rem;
|
|
height: 1.25rem;
|
|
color: hsl(var(--foreground));
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 768px) {
|
|
.container {
|
|
padding: 1rem;
|
|
}
|
|
|
|
.admin-header {
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.admin-title {
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.controls-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.table-container {
|
|
padding: 1rem;
|
|
}
|
|
|
|
.admin-table {
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.admin-table thead {
|
|
display: none;
|
|
}
|
|
|
|
.admin-table tbody tr {
|
|
display: block;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.admin-table tbody td {
|
|
display: block;
|
|
text-align: right;
|
|
padding: 0.75rem 1rem;
|
|
position: relative;
|
|
}
|
|
|
|
.admin-table tbody td::before {
|
|
content: attr(data-label);
|
|
float: left;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
font-size: 0.75rem;
|
|
color: hsl(var(--muted-foreground));
|
|
}
|
|
|
|
.theme-switcher {
|
|
top: 1rem;
|
|
right: 1rem;
|
|
}
|
|
}
|
|
|
|
/* Loading states */
|
|
.loading {
|
|
opacity: 0.7;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.loading .btn::after {
|
|
content: '';
|
|
position: absolute;
|
|
width: 16px;
|
|
height: 16px;
|
|
margin: auto;
|
|
border: 2px solid transparent;
|
|
border-top-color: currentColor;
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="dark">
|
|
<div class="backdrop"></div>
|
|
|
|
<div class="container">
|
|
<div class="admin-card">
|
|
<!-- Header -->
|
|
<div class="admin-header">
|
|
<h1 class="admin-title">
|
|
<i class="fas fa-users-cog"></i>
|
|
Gestion des Utilisateurs
|
|
</h1>
|
|
<div class="admin-subtitle">
|
|
<i class="fas fa-shield-alt"></i>
|
|
<span>Panneau d'administration</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Contrôles de recherche et pagination -->
|
|
<div class="controls-section">
|
|
<div class="controls-grid">
|
|
<div class="search-container">
|
|
<i class="fas fa-search search-icon"></i>
|
|
<input type="text"
|
|
id="searchInput"
|
|
class="search-input"
|
|
placeholder="Rechercher par nom ou ID...">
|
|
<span class="search-hint">Ctrl + K</span>
|
|
</div>
|
|
|
|
<div class="pagination-selector">
|
|
<label for="usersPerPage">
|
|
<i class="fas fa-list"></i>
|
|
Afficher :
|
|
</label>
|
|
<select id="usersPerPage" class="pagination-select">
|
|
<option value="10" <%= limit == 10 ? 'selected' : '' %>>10</option>
|
|
<option value="50" <%= limit == 50 ? 'selected' : '' %>>50</option>
|
|
<option value="100" <%= limit == 100 ? 'selected' : '' %>>100</option>
|
|
<option value="500" <%= limit == 500 ? 'selected' : '' %>>500</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tableau des utilisateurs -->
|
|
<div class="table-container">
|
|
<table class="admin-table">
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Photo</th>
|
|
<th>Nom</th>
|
|
<th>Rôle</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<% users.forEach(user => { %>
|
|
<tr>
|
|
<td data-label="ID">
|
|
<span class="user-id">#<%= user.id %></span>
|
|
</td>
|
|
<td data-label="Photo">
|
|
<img src="<%= user.profilePicture || 'https://api.dicebear.com/7.x/initials/svg?seed=' + user.name %>"
|
|
alt="<%= user.name %>"
|
|
class="user-avatar"
|
|
onerror="this.src='https://api.dicebear.com/7.x/initials/svg?seed=<%= user.name %>'">
|
|
</td>
|
|
<td data-label="Nom">
|
|
<span class="user-name"><%= user.name %></span>
|
|
</td>
|
|
<td data-label="Rôle">
|
|
<span class="role-badge <%= user.role %>">
|
|
<i class="fas <%= user.role === 'admin' ? 'fa-crown' : 'fa-user' %>"></i>
|
|
<%= user.role %>
|
|
</span>
|
|
</td>
|
|
<td data-label="Actions">
|
|
<div class="actions-container">
|
|
<form action="/api/dpanel/dashboard/admin/update-role"
|
|
method="POST"
|
|
class="action-form update-role-form">
|
|
<input type="hidden" name="id" value="<%= user.id %>">
|
|
<input type="hidden" name="name" value="<%= user.name %>">
|
|
<select class="role-select" name="role">
|
|
<option value="user" <%= user.role === 'user' ? 'selected' : '' %>>User</option>
|
|
<option value="admin" <%= user.role === 'admin' ? 'selected' : '' %>>Admin</option>
|
|
</select>
|
|
<button type="submit" class="btn btn-primary btn-small">
|
|
<i class="fas fa-save"></i>
|
|
Mettre à jour
|
|
</button>
|
|
</form>
|
|
<form action="/api/dpanel/generate-token"
|
|
method="POST"
|
|
class="action-form generate-token-form">
|
|
<input type="hidden" name="id" value="<%= user.id %>">
|
|
<input type="hidden" name="name" value="<%= user.name %>">
|
|
<button type="submit" class="btn btn-secondary btn-small">
|
|
<i class="fas fa-key"></i>
|
|
Générer Token
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<% }) %>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<div class="pagination-container">
|
|
<% if (currentPage > 1) { %>
|
|
<a href="/dpanel/dashboard/admin/users?page=<%= currentPage - 1 %>&limit=<%= limit %>"
|
|
class="pagination-link">
|
|
<i class="fas fa-chevron-left"></i>
|
|
</a>
|
|
<% } %>
|
|
|
|
<% for(let i = 1; i <= pages; i++) { %>
|
|
<% if (i === 1 || i === pages || (i >= currentPage - 2 && i <= currentPage + 2)) { %>
|
|
<a href="/dpanel/dashboard/admin/users?page=<%= i %>&limit=<%= limit %>"
|
|
class="pagination-link <%= i === currentPage ? 'active' : '' %>">
|
|
<%= i %>
|
|
</a>
|
|
<% } else if (i === currentPage - 3 || i === currentPage + 3) { %>
|
|
<span class="pagination-link" style="border: none; pointer-events: none;">...</span>
|
|
<% } %>
|
|
<% } %>
|
|
|
|
<% if (currentPage < pages) { %>
|
|
<a href="/dpanel/dashboard/admin/users?page=<%= currentPage + 1 %>&limit=<%= limit %>"
|
|
class="pagination-link">
|
|
<i class="fas fa-chevron-right"></i>
|
|
</a>
|
|
<% } %>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div class="footer-section">
|
|
<a href="/dpanel/dashboard/admin/" class="btn btn-secondary">
|
|
<i class="fas fa-arrow-left"></i>
|
|
Retour au dashboard admin
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Theme Switcher -->
|
|
<button class="theme-switcher" id="themeSwitcher">
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z" />
|
|
</svg>
|
|
</button>
|
|
|
|
<script>
|
|
// =================== VARIABLES GLOBALES ===================
|
|
const body = document.body;
|
|
const themeSwitcher = document.getElementById('themeSwitcher');
|
|
|
|
// =================== GESTION DU THÈME ===================
|
|
function setTheme(theme) {
|
|
if (theme === 'dark') {
|
|
body.classList.add('dark');
|
|
} else {
|
|
body.classList.remove('dark');
|
|
}
|
|
localStorage.setItem('theme', theme);
|
|
}
|
|
|
|
// Initialisation du thème
|
|
const savedTheme = localStorage.getItem('theme');
|
|
if (savedTheme) {
|
|
setTheme(savedTheme);
|
|
} else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
setTheme('dark');
|
|
}
|
|
|
|
themeSwitcher.addEventListener('click', function() {
|
|
if (body.classList.contains('dark')) {
|
|
setTheme('light');
|
|
} else {
|
|
setTheme('dark');
|
|
}
|
|
});
|
|
|
|
// =================== NOTIFICATIONS ===================
|
|
function showToast(icon, title) {
|
|
Swal.fire({
|
|
icon: icon,
|
|
title: title,
|
|
showConfirmButton: false,
|
|
timer: 3000,
|
|
toast: true,
|
|
position: 'top-end'
|
|
});
|
|
}
|
|
|
|
// =================== RECHERCHE ===================
|
|
const searchInput = document.getElementById('searchInput');
|
|
|
|
function searchUsers() {
|
|
const filter = searchInput.value.toUpperCase();
|
|
const table = document.querySelector('.admin-table');
|
|
const rows = table.getElementsByTagName('tr');
|
|
|
|
for (let i = 1; i < rows.length; i++) {
|
|
const row = rows[i];
|
|
const idCell = row.cells[0];
|
|
const nameCell = row.cells[2];
|
|
|
|
if (idCell && nameCell) {
|
|
const idText = idCell.textContent || idCell.innerText;
|
|
const nameText = nameCell.textContent || nameCell.innerText;
|
|
|
|
if (idText.toUpperCase().indexOf(filter) > -1 ||
|
|
nameText.toUpperCase().indexOf(filter) > -1) {
|
|
row.style.display = '';
|
|
} else {
|
|
row.style.display = 'none';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
searchInput.addEventListener('input', searchUsers);
|
|
|
|
// Raccourci clavier Ctrl+K
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.ctrlKey && e.key === 'k') {
|
|
e.preventDefault();
|
|
searchInput.focus();
|
|
}
|
|
});
|
|
|
|
// =================== PAGINATION ===================
|
|
document.getElementById('usersPerPage').addEventListener('change', function() {
|
|
window.location.href = '/dpanel/dashboard/admin/users?page=1&limit=' + this.value;
|
|
});
|
|
|
|
// =================== MISE À JOUR DU RÔLE ===================
|
|
document.querySelectorAll('.update-role-form').forEach(form => {
|
|
form.addEventListener('submit', async function(e) {
|
|
e.preventDefault();
|
|
const formData = new FormData(this);
|
|
const data = Object.fromEntries(formData);
|
|
|
|
try {
|
|
const response = await fetch(this.action, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(data)
|
|
});
|
|
|
|
if (response.ok) {
|
|
showToast('success', 'Rôle mis à jour avec succès !');
|
|
setTimeout(() => location.reload(), 1500);
|
|
} else {
|
|
const error = await response.json();
|
|
showToast('error', error.message || 'Erreur lors de la mise à jour');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur:', error);
|
|
showToast('error', 'Erreur de connexion au serveur');
|
|
}
|
|
});
|
|
});
|
|
|
|
// =================== GÉNÉRATION DE TOKEN ===================
|
|
document.querySelectorAll('.generate-token-form').forEach(form => {
|
|
form.addEventListener('submit', async function(e) {
|
|
e.preventDefault();
|
|
const formData = new FormData(this);
|
|
const data = Object.fromEntries(formData);
|
|
|
|
try {
|
|
const response = await fetch(this.action, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(data)
|
|
});
|
|
|
|
if (response.ok) {
|
|
const result = await response.json();
|
|
|
|
Swal.fire({
|
|
title: 'Token généré avec succès',
|
|
html: `
|
|
<div style="margin: 1rem 0;">
|
|
<input type="text"
|
|
id="tokenInput"
|
|
value="${result.token}"
|
|
readonly
|
|
style="width: 100%;
|
|
padding: 0.75rem;
|
|
border: 1px solid hsl(var(--border));
|
|
border-radius: 8px;
|
|
background: hsl(var(--muted));
|
|
font-family: monospace;
|
|
font-size: 0.9rem;">
|
|
</div>
|
|
<div style="margin-top: 1rem;
|
|
padding: 1rem;
|
|
background: hsl(var(--muted));
|
|
border-radius: 8px;
|
|
text-align: left;
|
|
font-size: 0.85rem;">
|
|
<strong>⚠️ Important :</strong><br>
|
|
Gardez ce token en sécurité. Il ne sera pas possible de le récupérer sans le régénérer.
|
|
</div>
|
|
`,
|
|
icon: 'success',
|
|
confirmButtonText: '<i class="fas fa-copy"></i> Copier le token',
|
|
showCancelButton: true,
|
|
cancelButtonText: 'Fermer',
|
|
preConfirm: () => {
|
|
const tokenInput = document.getElementById('tokenInput');
|
|
tokenInput.select();
|
|
document.execCommand('copy');
|
|
return true;
|
|
}
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
showToast('success', 'Token copié dans le presse-papiers');
|
|
}
|
|
});
|
|
} else {
|
|
const error = await response.json();
|
|
showToast('error', error.message || 'Erreur lors de la génération du token');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur:', error);
|
|
showToast('error', 'Erreur de connexion au serveur');
|
|
}
|
|
});
|
|
});
|
|
|
|
// =================== INITIALISATION ===================
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
setTimeout(() => {
|
|
document.querySelector('.admin-card').style.opacity = '1';
|
|
document.querySelector('.admin-card').style.transform = 'translateY(0) scale(1)';
|
|
}, 100);
|
|
|
|
console.log('🎨 Interface moderne de gestion des utilisateurs chargée !');
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|