Files
CDN-APP-INSIDER/views/paramAdminUser.ejs
Dinawo 2df1b28962
All checks were successful
continuous-integration/drone/push Build is passing
Update v1.2.0-beta - Dynamic context menu & permissions
 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
2025-10-25 23:55:51 +02:00

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>