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
1107 lines
40 KiB
Plaintext
1107 lines
40 KiB
Plaintext
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Profil Utilisateur - 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: 1200px;
|
|
margin: 0 auto;
|
|
padding: 2rem;
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.profile-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%;
|
|
max-width: 800px;
|
|
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 du profil */
|
|
.profile-header {
|
|
padding: 2rem;
|
|
text-align: center;
|
|
background: linear-gradient(135deg, hsl(var(--primary)) 0%, hsl(var(--accent)) 100%);
|
|
color: hsl(var(--primary-foreground));
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.profile-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;
|
|
}
|
|
|
|
.profile-picture {
|
|
position: relative;
|
|
z-index: 2;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.profile-picture img {
|
|
width: 120px;
|
|
height: 120px;
|
|
border-radius: 50%;
|
|
border: 4px solid rgba(255, 255, 255, 0.2);
|
|
object-fit: cover;
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.profile-picture img:hover {
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.user-info {
|
|
position: relative;
|
|
z-index: 2;
|
|
}
|
|
|
|
.user-name {
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
margin-bottom: 0.5rem;
|
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.user-details {
|
|
display: flex;
|
|
justify-content: center;
|
|
gap: 2rem;
|
|
margin-top: 1rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.user-detail {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.5rem 1rem;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border-radius: 25px;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.role-badge {
|
|
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
padding: 0.25rem 0.75rem;
|
|
border-radius: 20px;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
|
|
}
|
|
|
|
/* Interface en test notice */
|
|
.beta-notice {
|
|
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
|
|
color: white;
|
|
padding: 1rem 2rem;
|
|
text-align: center;
|
|
border-radius: 0;
|
|
margin: 0;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.beta-notice::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: -100%;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
|
animation: shine 3s infinite;
|
|
}
|
|
|
|
@keyframes shine {
|
|
0% { left: -100%; }
|
|
100% { left: 100%; }
|
|
}
|
|
|
|
.beta-notice i {
|
|
margin-right: 0.5rem;
|
|
animation: pulse 2s infinite;
|
|
}
|
|
|
|
/* Onglets */
|
|
.tabs-container {
|
|
background: hsl(var(--card));
|
|
border-bottom: 1px solid hsl(var(--border));
|
|
}
|
|
|
|
.tabs {
|
|
display: flex;
|
|
overflow-x: auto;
|
|
scrollbar-width: none;
|
|
-ms-overflow-style: none;
|
|
}
|
|
|
|
.tabs::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
.tab {
|
|
padding: 1.25rem 2rem;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
white-space: nowrap;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
background: transparent;
|
|
color: hsl(var(--muted-foreground));
|
|
border: none;
|
|
font-size: 0.95rem;
|
|
font-weight: 500;
|
|
position: relative;
|
|
border-bottom: 3px solid transparent;
|
|
}
|
|
|
|
.tab:hover {
|
|
background-color: hsl(var(--accent));
|
|
color: hsl(var(--accent-foreground));
|
|
}
|
|
|
|
.tab.active {
|
|
background: hsl(var(--primary));
|
|
color: hsl(var(--primary-foreground));
|
|
border-bottom-color: hsl(var(--primary));
|
|
font-weight: 600;
|
|
}
|
|
|
|
.tab.active::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: -3px;
|
|
left: 0;
|
|
right: 0;
|
|
height: 3px;
|
|
background: linear-gradient(90deg, hsl(var(--primary)), hsl(var(--ring)));
|
|
border-radius: 3px 3px 0 0;
|
|
}
|
|
|
|
.tab i {
|
|
font-size: 1.1rem;
|
|
}
|
|
|
|
/* Contenu des onglets */
|
|
.tab-content {
|
|
display: none;
|
|
padding: 2rem;
|
|
animation: fadeIn 0.3s ease-out;
|
|
}
|
|
|
|
.tab-content.active {
|
|
display: block;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; transform: translateY(10px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
/* Formulaires modernes */
|
|
.form-section {
|
|
background: hsl(var(--card));
|
|
border: 1px solid hsl(var(--border));
|
|
border-radius: 12px;
|
|
padding: 1.5rem;
|
|
margin-bottom: 1.5rem;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.form-section:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.form-section-title {
|
|
font-size: 1.1rem;
|
|
font-weight: 600;
|
|
color: hsl(var(--foreground));
|
|
margin-bottom: 1rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.form-section-title i {
|
|
color: hsl(var(--primary));
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.form-label {
|
|
display: block;
|
|
margin-bottom: 0.5rem;
|
|
font-weight: 500;
|
|
color: hsl(var(--foreground));
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.form-control {
|
|
width: 100%;
|
|
padding: 0.875rem 1rem;
|
|
border: 1px solid hsl(var(--border));
|
|
border-radius: 8px;
|
|
background-color: hsl(var(--background));
|
|
color: hsl(var(--foreground));
|
|
font-size: 0.95rem;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.form-control:focus {
|
|
outline: none;
|
|
border-color: hsl(var(--ring));
|
|
box-shadow: 0 0 0 3px hsla(var(--ring), 0.1);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.form-control::placeholder {
|
|
color: hsl(var(--muted-foreground));
|
|
} /* Boutons modernes */
|
|
.btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 0.5rem;
|
|
padding: 0.875rem 1.5rem;
|
|
border-radius: 25px;
|
|
font-weight: 500;
|
|
font-size: 0.95rem;
|
|
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-full {
|
|
width: 100%;
|
|
}
|
|
|
|
.btn-icon-only {
|
|
padding: 0.75rem;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
/* Actions rapides */
|
|
.quick-actions {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 1rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.quick-action {
|
|
background: hsl(var(--card));
|
|
border: 1px solid hsl(var(--border));
|
|
border-radius: 12px;
|
|
padding: 1.5rem;
|
|
text-align: center;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
text-decoration: none;
|
|
color: hsl(var(--foreground));
|
|
}
|
|
|
|
.quick-action:hover {
|
|
transform: translateY(-4px);
|
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
|
text-decoration: none;
|
|
color: hsl(var(--foreground));
|
|
}
|
|
|
|
.quick-action-icon {
|
|
font-size: 2rem;
|
|
margin-bottom: 1rem;
|
|
color: hsl(var(--primary));
|
|
}
|
|
|
|
.quick-action-title {
|
|
font-weight: 600;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.quick-action-desc {
|
|
font-size: 0.85rem;
|
|
color: hsl(var(--muted-foreground));
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
.profile-header {
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.user-name {
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.user-details {
|
|
gap: 1rem;
|
|
}
|
|
|
|
.tab {
|
|
padding: 1rem 1.5rem;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.tab-content {
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.theme-switcher {
|
|
top: 1rem;
|
|
right: 1rem;
|
|
}
|
|
}
|
|
|
|
/* Toast notifications */
|
|
.swal2-toast {
|
|
background-color: hsl(var(--card)) !important;
|
|
color: hsl(var(--foreground)) !important;
|
|
border: 1px solid hsl(var(--border)) !important;
|
|
border-radius: var(--radius) !important;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1) !important;
|
|
}
|
|
|
|
.dark .swal2-toast {
|
|
background-color: hsl(var(--card)) !important;
|
|
color: hsl(var(--foreground)) !important;
|
|
border: 1px solid hsl(var(--border)) !important;
|
|
}
|
|
|
|
/* Loading states */
|
|
.loading {
|
|
opacity: 0.7;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.loading .btn {
|
|
position: relative;
|
|
}
|
|
|
|
.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="profile-card">
|
|
<!-- Header du profil -->
|
|
<div class="profile-header">
|
|
<div class="profile-picture">
|
|
<img src="<%= user.profilePicture || 'https://api.dicebear.com/7.x/initials/svg?seed=' + user.name %>"
|
|
alt="Photo de profil"
|
|
id="profileImage" />
|
|
</div>
|
|
<div class="user-info">
|
|
<h1 class="user-name">Bienvenue, <%= user.name %> !</h1>
|
|
<div class="user-details">
|
|
<div class="user-detail">
|
|
<i class="fas fa-id-card"></i>
|
|
<span>ID: <%= user.id %></span>
|
|
</div>
|
|
<div class="user-detail">
|
|
<i class="fas fa-shield-alt"></i>
|
|
<span class="role-badge"><%= user.role %></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Onglets -->
|
|
<div class="tabs-container">
|
|
<div class="tabs">
|
|
<button class="tab active" data-tab="overview">
|
|
<i class="fas fa-chart-line"></i>
|
|
Aperçu
|
|
</button>
|
|
<button class="tab" data-tab="appearance">
|
|
<i class="fas fa-palette"></i>
|
|
Apparence
|
|
</button>
|
|
<button class="tab" data-tab="profile">
|
|
<i class="fas fa-user-edit"></i>
|
|
Profil
|
|
</button>
|
|
<button class="tab" data-tab="security">
|
|
<i class="fas fa-lock"></i>
|
|
Sécurité
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Contenu de l'onglet Aperçu -->
|
|
<div class="tab-content active" id="overview">
|
|
<div class="quick-actions">
|
|
<a href="/dpanel/dashboard" class="quick-action">
|
|
<div class="quick-action-icon">
|
|
<i class="fas fa-tachometer-alt"></i>
|
|
</div>
|
|
<div class="quick-action-title">Dashboard</div>
|
|
<div class="quick-action-desc">Accéder au tableau de bord</div>
|
|
</a>
|
|
<a href="/dpanel/upload" class="quick-action">
|
|
<div class="quick-action-icon">
|
|
<i class="fas fa-cloud-upload-alt"></i>
|
|
</div>
|
|
<div class="quick-action-title">Téléverser</div>
|
|
<div class="quick-action-desc">Ajouter de nouveaux fichiers</div>
|
|
</a>
|
|
<div class="quick-action" onclick="switchTab('appearance')">
|
|
<div class="quick-action-icon">
|
|
<i class="fas fa-image"></i>
|
|
</div>
|
|
<div class="quick-action-title">Personnaliser</div>
|
|
<div class="quick-action-desc">Modifier l'apparence</div>
|
|
</div>
|
|
</div>
|
|
</div> <!-- Contenu de l'onglet Apparence -->
|
|
<div class="tab-content" id="appearance">
|
|
<div class="form-section">
|
|
<div class="form-section-title">
|
|
<i class="fas fa-palette"></i>
|
|
Thème de l'interface
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Mode d'affichage</label>
|
|
<div style="display: flex; gap: 1rem; margin-top: 0.5rem;">
|
|
<button type="button" class="btn btn-secondary" id="lightModeBtn">
|
|
<i class="fas fa-sun"></i>
|
|
Mode clair
|
|
</button>
|
|
<button type="button" class="btn btn-secondary" id="darkModeBtn">
|
|
<i class="fas fa-moon"></i>
|
|
Mode sombre
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-section">
|
|
<div class="form-section-title">
|
|
<i class="fas fa-image"></i>
|
|
Fond d'écran personnalisé
|
|
</div>
|
|
<form id="wallpaperForm">
|
|
<div class="form-group">
|
|
<label class="form-label" for="wallpaperUrl">URL du fond d'écran</label>
|
|
<input type="url"
|
|
id="wallpaperUrl"
|
|
name="wallpaperUrl"
|
|
class="form-control"
|
|
placeholder="https://exemple.com/image.jpg"
|
|
required>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary btn-full">
|
|
<i class="fas fa-image"></i>
|
|
Mettre à jour le fond d'écran
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Contenu de l'onglet Profil -->
|
|
<div class="tab-content" id="profile">
|
|
<div class="form-section">
|
|
<div class="form-section-title">
|
|
<i class="fas fa-user-circle"></i>
|
|
Photo de profil
|
|
</div>
|
|
<form id="profilePictureForm">
|
|
<div class="form-group">
|
|
<label class="form-label" for="profilePictureUrl">URL de la photo de profil</label>
|
|
<input type="url"
|
|
id="profilePictureUrl"
|
|
name="profilePictureUrl"
|
|
class="form-control"
|
|
placeholder="https://exemple.com/photo.jpg"
|
|
required>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary btn-full">
|
|
<i class="fas fa-user-circle"></i>
|
|
Mettre à jour la photo de profil
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div> <!-- Contenu de l'onglet Sécurité -->
|
|
<div class="tab-content" id="security">
|
|
<div class="form-section">
|
|
<div class="form-section-title">
|
|
<i class="fas fa-key"></i>
|
|
Gestion des clés API
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label">Votre clé API actuelle</label>
|
|
<div style="display: flex; gap: 1rem; align-items: center;">
|
|
<input type="text"
|
|
id="apiKeyDisplay"
|
|
class="form-control"
|
|
value="sk-****************************"
|
|
readonly
|
|
style="flex: 1;">
|
|
<button type="button" class="btn btn-secondary" id="showApiKey">
|
|
<i class="fas fa-eye"></i>
|
|
Afficher
|
|
</button>
|
|
<button type="button" class="btn btn-secondary" id="copyApiKey">
|
|
<i class="fas fa-copy"></i>
|
|
Copier
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div style="display: flex; gap: 1rem; margin-top: 1rem;">
|
|
<button type="button" class="btn btn-primary" id="generateApiKey">
|
|
<i class="fas fa-sync-alt"></i>
|
|
Générer une nouvelle clé
|
|
</button>
|
|
<button type="button" class="btn btn-secondary" id="revokeApiKey">
|
|
<i class="fas fa-trash"></i>
|
|
Révoquer la clé
|
|
</button>
|
|
</div>
|
|
<div style="padding: 1rem; background: hsl(var(--muted)); border-radius: 8px; margin-top: 1rem;">
|
|
<div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem;">
|
|
<i class="fas fa-info-circle" style="color: hsl(var(--primary));"></i>
|
|
<strong>Important :</strong>
|
|
</div>
|
|
<p style="margin: 0; font-size: 0.85rem; color: hsl(var(--muted-foreground));">
|
|
Votre clé API vous permet d'accéder aux services du CDN via les API.
|
|
Gardez-la confidentielle et ne la partagez jamais.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</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');
|
|
}
|
|
});
|
|
|
|
// =================== GESTION DES ONGLETS ===================
|
|
function switchTab(tabName) {
|
|
// Désactiver tous les onglets
|
|
document.querySelectorAll('.tab').forEach(tab => {
|
|
tab.classList.remove('active');
|
|
});
|
|
|
|
// Masquer tout le contenu
|
|
document.querySelectorAll('.tab-content').forEach(content => {
|
|
content.classList.remove('active');
|
|
});
|
|
|
|
// Activer l'onglet et le contenu sélectionnés
|
|
document.querySelector(`[data-tab="${tabName}"]`).classList.add('active');
|
|
document.getElementById(tabName).classList.add('active');
|
|
}
|
|
|
|
// Gestionnaires d'événements pour les onglets
|
|
document.querySelectorAll('.tab').forEach(tab => {
|
|
tab.addEventListener('click', function() {
|
|
const tabName = this.getAttribute('data-tab');
|
|
switchTab(tabName);
|
|
});
|
|
});
|
|
|
|
// =================== NOTIFICATIONS ===================
|
|
function showToast(icon, title) {
|
|
Swal.fire({
|
|
icon: icon,
|
|
title: title,
|
|
showConfirmButton: false,
|
|
timer: 3000,
|
|
toast: true,
|
|
position: 'top-end',
|
|
customClass: {
|
|
container: 'swal2-toast'
|
|
}
|
|
});
|
|
}
|
|
|
|
// =================== GESTION DES FORMULAIRES ===================
|
|
function setFormLoading(form, loading) {
|
|
const submitBtn = form.querySelector('button[type="submit"]');
|
|
if (loading) {
|
|
form.classList.add('loading');
|
|
submitBtn.disabled = true;
|
|
} else {
|
|
form.classList.remove('loading');
|
|
submitBtn.disabled = false;
|
|
}
|
|
}
|
|
|
|
// =================== MISE À JOUR DU FOND D'ÉCRAN ===================
|
|
document.getElementById('wallpaperForm').addEventListener('submit', async function(e) {
|
|
e.preventDefault();
|
|
const form = this;
|
|
const urlInput = document.getElementById('wallpaperUrl').value.trim();
|
|
|
|
if (!urlInput) {
|
|
showToast('warning', 'Veuillez entrer une URL valide');
|
|
return;
|
|
}
|
|
|
|
setFormLoading(form, true);
|
|
|
|
try {
|
|
const response = await fetch('/api/dpanel/dashboard/backgroundcustom/wallpaper', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': 'Bearer ' + localStorage.getItem('token'),
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
wallpaperUrl: urlInput,
|
|
userId: '<%= user.id %>'
|
|
})
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
document.body.style.backgroundImage = `url('${data.wallpaper}')`;
|
|
form.reset();
|
|
showToast('success', 'Fond d\'écran mis à jour avec succès !');
|
|
} else {
|
|
const error = await response.json();
|
|
showToast('error', error.message || 'Échec de la mise à jour du fond d\'écran');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur:', error);
|
|
showToast('error', 'Erreur de connexion au serveur');
|
|
} finally {
|
|
setFormLoading(form, false);
|
|
}
|
|
}); // =================== MISE À JOUR DE LA PHOTO DE PROFIL ===================
|
|
document.getElementById('profilePictureForm').addEventListener('submit', async function(e) {
|
|
e.preventDefault();
|
|
const form = this;
|
|
const urlInput = document.getElementById('profilePictureUrl').value.trim();
|
|
|
|
if (!urlInput) {
|
|
showToast('warning', 'Veuillez entrer une URL valide');
|
|
return;
|
|
}
|
|
|
|
setFormLoading(form, true);
|
|
|
|
try {
|
|
const response = await fetch('/api/dpanel/dashboard/profilpicture', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': 'Bearer ' + localStorage.getItem('token'),
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
profilePictureUrl: urlInput,
|
|
userId: '<%= user.id %>'
|
|
})
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
document.getElementById('profileImage').src = data.profilePicture;
|
|
form.reset();
|
|
showToast('success', 'Photo de profil mise à jour avec succès !');
|
|
} else {
|
|
const error = await response.json();
|
|
showToast('error', error.message || 'Échec de la mise à jour de la photo de profil');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur:', error);
|
|
showToast('error', 'Erreur de connexion au serveur');
|
|
} finally {
|
|
setFormLoading(form, false);
|
|
}
|
|
});
|
|
|
|
// =================== GESTION DES BOUTONS DE THÈME ===================
|
|
document.getElementById('lightModeBtn').addEventListener('click', function() {
|
|
setTheme('light');
|
|
showToast('success', 'Thème clair activé');
|
|
});
|
|
|
|
document.getElementById('darkModeBtn').addEventListener('click', function() {
|
|
setTheme('dark');
|
|
showToast('success', 'Thème sombre activé');
|
|
}); // =================== GESTION DES CLÉS API ===================
|
|
let currentApiKey = '<%= user.token || "" %>';
|
|
let isApiKeyVisible = false;
|
|
|
|
document.getElementById('showApiKey').addEventListener('click', function() {
|
|
const apiKeyInput = document.getElementById('apiKeyDisplay');
|
|
const button = this;
|
|
|
|
if (isApiKeyVisible) {
|
|
apiKeyInput.value = 'sk-****************************';
|
|
button.innerHTML = '<i class="fas fa-eye"></i> Afficher';
|
|
isApiKeyVisible = false;
|
|
} else {
|
|
if (currentApiKey) {
|
|
apiKeyInput.value = currentApiKey;
|
|
button.innerHTML = '<i class="fas fa-eye-slash"></i> Masquer';
|
|
isApiKeyVisible = true;
|
|
} else {
|
|
apiKeyInput.value = 'Aucune clé API trouvée';
|
|
showToast('warning', 'Aucune clé API trouvée pour cet utilisateur');
|
|
}
|
|
}
|
|
});
|
|
|
|
document.getElementById('copyApiKey').addEventListener('click', function() {
|
|
const apiKeyInput = document.getElementById('apiKeyDisplay');
|
|
|
|
if (isApiKeyVisible) {
|
|
navigator.clipboard.writeText(currentApiKey).then(() => {
|
|
showToast('success', 'Clé API copiée dans le presse-papiers');
|
|
}).catch(() => {
|
|
showToast('error', 'Impossible de copier la clé API');
|
|
});
|
|
} else {
|
|
showToast('warning', 'Veuillez d\'abord afficher la clé API');
|
|
}
|
|
}); document.getElementById('generateApiKey').addEventListener('click', async function() {
|
|
const result = await Swal.fire({
|
|
title: 'Générer une nouvelle clé API ?',
|
|
text: 'Cette action invalidera votre clé API actuelle.',
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonText: 'Oui, générer',
|
|
cancelButtonText: 'Annuler',
|
|
confirmButtonColor: 'hsl(var(--primary))'
|
|
});
|
|
|
|
if (result.isConfirmed) {
|
|
try {
|
|
const response = await fetch('/api/dpanel/generate-token', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
name: '<%= user.name %>',
|
|
id: '<%= user.id %>'
|
|
})
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
currentApiKey = data.token;
|
|
|
|
if (isApiKeyVisible) {
|
|
document.getElementById('apiKeyDisplay').value = data.token;
|
|
} else {
|
|
document.getElementById('apiKeyDisplay').value = '•'.repeat(32);
|
|
}
|
|
|
|
showToast('success', 'Nouvelle clé API générée avec succès');
|
|
} else {
|
|
const errorData = await response.json();
|
|
showToast('error', errorData.error || 'Erreur lors de la génération de la clé API');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur:', error);
|
|
showToast('error', 'Erreur lors de la génération de la clé API');
|
|
}
|
|
}
|
|
}); document.getElementById('revokeApiKey').addEventListener('click', async function() {
|
|
const result = await Swal.fire({
|
|
title: 'Révoquer la clé API ?',
|
|
text: 'Cette action supprimera définitivement votre clé API.',
|
|
icon: 'error',
|
|
showCancelButton: true,
|
|
confirmButtonText: 'Oui, révoquer',
|
|
cancelButtonText: 'Annuler',
|
|
confirmButtonColor: 'hsl(var(--destructive))'
|
|
});
|
|
|
|
if (result.isConfirmed) {
|
|
try {
|
|
const response = await fetch('/api/dpanel/revoke-token', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
userId: '<%= user.id %>'
|
|
})
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
currentApiKey = '';
|
|
document.getElementById('apiKeyDisplay').value = 'Aucune clé API';
|
|
isApiKeyVisible = false;
|
|
document.getElementById('showApiKey').innerHTML = '<i class="fas fa-eye"></i> Afficher';
|
|
|
|
showToast('success', data.message || 'Clé API révoquée avec succès');
|
|
} else {
|
|
const errorData = await response.json();
|
|
showToast('error', errorData.error || 'Erreur lors de la révocation de la clé API');
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur:', error);
|
|
showToast('error', 'Erreur lors de la révocation de la clé API');
|
|
}
|
|
}
|
|
});
|
|
|
|
// =================== GESTION DES IMAGES ===================
|
|
document.getElementById('profileImage').addEventListener('error', function() {
|
|
this.src = 'https://api.dicebear.com/7.x/initials/svg?seed=<%= user.name %>';
|
|
});
|
|
|
|
// =================== INITIALISATION ===================
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Animation d'entrée
|
|
setTimeout(() => {
|
|
document.querySelector('.profile-card').style.opacity = '1';
|
|
document.querySelector('.profile-card').style.transform = 'translateY(0) scale(1)';
|
|
}, 100);
|
|
|
|
console.log('🎨 Interface moderne du profil chargée !');
|
|
console.log('✨ Cette interface est en phase de test');
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|