Update v1.2.0-beta - Dynamic context menu & permissions
All checks were successful
continuous-integration/drone/push Build is passing
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
This commit is contained in:
@@ -9,7 +9,46 @@
|
||||
<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@10"></script>
|
||||
<link rel="stylesheet" href="../public/css/dashboard.styles.css">
|
||||
<style>
|
||||
<link rel="stylesheet" href="../public/css/dropdown-fixes.css">
|
||||
<style>
|
||||
/* Changelog Modal Styles */
|
||||
#changelogModal .modal-content {
|
||||
border: none;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
#changelogModal .modal-header {
|
||||
border-top-left-radius: 15px;
|
||||
border-top-right-radius: 15px;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
#changelogModal .modal-body ul li {
|
||||
padding: 8px 0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
#changelogModal .modal-body ul li:hover {
|
||||
padding-left: 10px;
|
||||
background-color: rgba(102, 126, 234, 0.05);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#changelogModal .modal-footer {
|
||||
border-top: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
@@ -29,33 +68,7 @@
|
||||
</style>
|
||||
</head>
|
||||
<body class="animate">
|
||||
<div class="context-menu" style="display: none;">
|
||||
<button class="menu-item" data-action="open">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
<span>Ouvrir</span>
|
||||
</button>
|
||||
<button class="menu-item" data-action="rename">
|
||||
<i class="fas fa-edit"></i>
|
||||
<span>Renommer</span>
|
||||
</button>
|
||||
<button class="menu-item" data-action="collaborate">
|
||||
<i class="fas fa-users"></i>
|
||||
<span>Collaborer</span>
|
||||
</button>
|
||||
<button class="menu-item" data-action="copy-link">
|
||||
<i class="fas fa-link"></i>
|
||||
<span>Copier le lien</span>
|
||||
</button>
|
||||
<button class="menu-item" data-action="move">
|
||||
<i class="fas fa-file-export"></i>
|
||||
<span>Déplacer</span>
|
||||
</button>
|
||||
<div class="menu-separator"></div>
|
||||
<button class="menu-item destructive" data-action="delete">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
<span>Supprimer</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav class="navbar navbar-expand-md navbar-light bg-light header">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/dpanel/dashboard">
|
||||
@@ -69,22 +82,15 @@
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a href="/dpanel/upload" class="btn btn-primary">
|
||||
<button type="button" class="btn btn-primary" id="uploadToDashboardBtn">
|
||||
<i class="fas fa-cloud-upload-alt"></i> Téléverser
|
||||
</a>
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button type="button" class="btn btn-success" id="newFolderBtn">
|
||||
<i class="fas fa-folder-open"></i> Nouveau
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button id="themeSwitcher" class="btn btn-secondary p-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<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>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<button class="btn dropdown-toggle nav-btn" id="accountDropdownBtn" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<img
|
||||
@@ -111,6 +117,14 @@
|
||||
</a>
|
||||
<% } %>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="#" id="showChangelogBtn">
|
||||
<span style="display: inline-block; width: 20px; text-align: center;">
|
||||
<i class="fas fa-rocket"></i>
|
||||
</span>
|
||||
Nouveautés v1.2.0-beta
|
||||
<span class="badge badge-danger ml-2" style="animation: pulse 2s infinite;">Nouveau</span>
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="/auth/logout">
|
||||
<span style="display: inline-block; width: 20px; text-align: center;">
|
||||
<i class="fas fa-sign-out-alt"></i>
|
||||
@@ -128,27 +142,28 @@
|
||||
<div class="container mt-4 animate">
|
||||
<!-- Menu contextuel -->
|
||||
<div id="contextMenu" class="context-menu" style="display: none; position: fixed; z-index: 1000;">
|
||||
<div class="bg-white rounded-lg shadow-lg py-2 w-48">
|
||||
<a href="#" class="context-item-open w-full text-left px-4 py-2 hover:bg-gray-100 flex items-center">
|
||||
<i class="fas fa-folder-open mr-2"></i> Ouvrir
|
||||
</a>
|
||||
<button class="context-item-rename w-full text-left px-4 py-2 hover:bg-gray-100 flex items-center">
|
||||
<i class="fas fa-edit mr-2"></i> Renommer
|
||||
</button>
|
||||
<button class="context-item-collaborate w-full text-left px-4 py-2 hover:bg-gray-100 flex items-center">
|
||||
<i class="fas fa-users mr-2"></i> Collaborer
|
||||
</button>
|
||||
<button class="context-item-share w-full text-left px-4 py-2 hover:bg-gray-100 flex items-center">
|
||||
<i class="fas fa-share-alt mr-2"></i> Copier le lien
|
||||
</button>
|
||||
<button class="context-item-move w-full text-left px-4 py-2 hover:bg-gray-100 flex items-center">
|
||||
<i class="fas fa-file-export mr-2"></i> Déplacer
|
||||
</button>
|
||||
<div class="border-t border-gray-200 my-2"></div>
|
||||
<button class="context-item-delete w-full text-left px-4 py-2 hover:bg-gray-100 flex items-center text-red-600">
|
||||
<i class="fas fa-trash-alt mr-2"></i> Supprimer
|
||||
</button>
|
||||
</div>
|
||||
<a href="#" class="context-item-open menu-item">
|
||||
<i class="fas fa-folder-open"></i> <span>Ouvrir</span>
|
||||
</a>
|
||||
<button class="context-item-rename menu-item">
|
||||
<i class="fas fa-edit"></i> <span>Renommer</span>
|
||||
</button>
|
||||
<button class="context-item-collaborate menu-item">
|
||||
<i class="fas fa-users"></i> <span>Collaborer</span>
|
||||
</button>
|
||||
<button class="context-item-share menu-item">
|
||||
<i class="fas fa-share-alt"></i> <span>Copier le lien</span>
|
||||
</button>
|
||||
<button class="context-item-move menu-item">
|
||||
<i class="fas fa-file-export"></i> <span>Déplacer</span>
|
||||
</button>
|
||||
<button class="context-item-leave menu-item" style="color: #f59e0b;">
|
||||
<i class="fas fa-sign-out-alt"></i> <span>Quitter ce dossier</span>
|
||||
</button>
|
||||
<div class="menu-separator"></div>
|
||||
<button class="context-item-delete menu-item destructive" style="color: #ef4444;">
|
||||
<i class="fas fa-trash-alt"></i> <span>Supprimer</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="form-container">
|
||||
@@ -165,7 +180,6 @@
|
||||
<th class="text-center">Type</th>
|
||||
<th class="text-center">Propriétaire</th>
|
||||
<th class="text-center">Taille</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -194,32 +208,6 @@
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-center">-</td>
|
||||
<td class="text-right">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-link btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<a href="/dpanel/dashboard/folder/<%= encodeURIComponent(folder.name) %>"
|
||||
class="dropdown-item">
|
||||
<i class="fas fa-folder-open mr-2"></i> Ouvrir
|
||||
</a>
|
||||
<button class="dropdown-item rename-folder-btn" data-folder-name="<%= folder.name %>">
|
||||
<i class="fas fa-edit mr-2"></i> Renommer
|
||||
</button>
|
||||
<button class="dropdown-item text-primary toggle-collaboration-btn"
|
||||
data-item-name="<%= folder.name %>"
|
||||
data-item-type="folder"
|
||||
data-is-collaborative="<%= folder.isCollaborative ? 'true' : 'false' %>">
|
||||
<i class="fas fa-users mr-2"></i> Collaborer
|
||||
</button>
|
||||
<div class="dropdown-divider"></div>
|
||||
<button class="dropdown-item text-danger delete-folder-btn" data-folder-name="<%= folder.name %>">
|
||||
<i class="fas fa-trash-alt mr-2"></i> Supprimer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
|
||||
@@ -247,22 +235,6 @@
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-center">-</td>
|
||||
<td class="text-right">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-link btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<a href="/dpanel/dashboard/folder/shared/<%= folder.owner %>/<%= encodeURIComponent(folder.folderName) %>"
|
||||
class="dropdown-item">
|
||||
<i class="fas fa-folder-open mr-2"></i> Ouvrir
|
||||
</a>
|
||||
<button class="dropdown-item leave-folder-btn">
|
||||
<i class="fas fa-user-minus mr-2"></i> Quitter
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
<% } %>
|
||||
@@ -290,28 +262,6 @@
|
||||
<%= file.size %> octets
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-link btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<button class="dropdown-item rename-file-btn" data-file-name="<%= file.name %>">
|
||||
<i class="fas fa-edit mr-2"></i> Renommer
|
||||
</button>
|
||||
<button class="dropdown-item copy-button" data-file-url="<%= file.url %>">
|
||||
<i class="fas fa-copy mr-2"></i> Copier le lien
|
||||
</button>
|
||||
<button class="dropdown-item move-file-btn" data-file-name="<%= file.name %>">
|
||||
<i class="fas fa-file-export mr-2"></i> Déplacer
|
||||
</button>
|
||||
<div class="dropdown-divider"></div>
|
||||
<button class="dropdown-item text-danger delete-file-button" data-file-name="<%= file.name %>">
|
||||
<i class="fas fa-trash-alt mr-2"></i> Supprimer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
</tbody>
|
||||
@@ -422,9 +372,351 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Changelog Modal -->
|
||||
<div class="modal fade" id="changelogModal" tabindex="-1" role="dialog" aria-labelledby="changelogModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||||
<h5 class="modal-title" id="changelogModalLabel">
|
||||
<i class="fas fa-rocket"></i> Nouveautés - Version 1.2.0-beta
|
||||
</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Fermer" style="color: white; opacity: 1;">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body" style="max-height: 70vh; overflow-y: auto;">
|
||||
<!-- Nouvelles fonctionnalités -->
|
||||
<div class="mb-4">
|
||||
<h6 class="text-primary font-weight-bold mb-3">
|
||||
<i class="fas fa-star text-warning"></i> Nouvelles Fonctionnalités
|
||||
</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-check-circle text-success mr-2"></i>
|
||||
<strong>Upload direct dans les dossiers</strong> - Téléversez vos fichiers directement dans n'importe quel dossier avec le bouton "Téléverser ici"
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-check-circle text-success mr-2"></i>
|
||||
<strong>Upload dans dossiers partagés</strong> - Collaborez en uploadant des fichiers dans les dossiers partagés avec vous
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-check-circle text-success mr-2"></i>
|
||||
<strong>Menu contextuel amélioré</strong> - Clic droit sur fichiers/dossiers pour actions rapides (Renommer, Copier, Déplacer, Supprimer)
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-check-circle text-success mr-2"></i>
|
||||
<strong>Double-clic pour ouvrir</strong> - Double-cliquez sur un fichier pour l'ouvrir ou un dossier pour naviguer
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-check-circle text-success mr-2"></i>
|
||||
<strong>Upload modal dans dashboard</strong> - Plus besoin de redirection, uploadez directement depuis le dashboard
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-check-circle text-success mr-2"></i>
|
||||
<strong>Dark mode synchronisé</strong> - Le thème sombre est maintenant synchronisé sur toutes les pages
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Améliorations -->
|
||||
<div class="mb-4">
|
||||
<h6 class="text-info font-weight-bold mb-3">
|
||||
<i class="fas fa-magic"></i> Améliorations
|
||||
</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-arrow-up text-info mr-2"></i>
|
||||
Interface simplifiée - Colonne "Actions" supprimée pour plus de clarté
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-arrow-up text-info mr-2"></i>
|
||||
SweetAlert2 intégré - Toutes les confirmations utilisent maintenant des modales élégantes
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-arrow-up text-info mr-2"></i>
|
||||
Breadcrumb corrigé - Navigation par fil d'Ariane améliorée
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-arrow-up text-info mr-2"></i>
|
||||
Upload par chunks optimisé - Meilleure gestion des gros fichiers (jusqu'à 1GB)
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Corrections de bugs -->
|
||||
<div class="mb-4">
|
||||
<h6 class="text-danger font-weight-bold mb-3">
|
||||
<i class="fas fa-bug"></i> Corrections de Bugs
|
||||
</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-wrench text-danger mr-2"></i>
|
||||
Résolution des erreurs de statistiques manquantes dans l'admin
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-wrench text-danger mr-2"></i>
|
||||
Correction CORS/COEP pour les avatars DiceBear
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-wrench text-danger mr-2"></i>
|
||||
Fichier dropdown-fixes.css créé (erreur 404 corrigée)
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-wrench text-danger mr-2"></i>
|
||||
Erreurs JavaScript dans folder.js corrigées
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-wrench text-danger mr-2"></i>
|
||||
Violation CSP pour CDN Tailwind corrigée
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-wrench text-danger mr-2"></i>
|
||||
Variable user manquante dans les routes admin ajoutée
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-wrench text-danger mr-2"></i>
|
||||
Fonction initForm manquante créée
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Sécurité -->
|
||||
<div class="mb-3">
|
||||
<h6 class="text-warning font-weight-bold mb-3">
|
||||
<i class="fas fa-shield-alt"></i> Sécurité
|
||||
</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-lock text-warning mr-2"></i>
|
||||
Vérification des permissions pour dossiers partagés
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-lock text-warning mr-2"></i>
|
||||
Génération automatique de noms de fichiers sécurisés
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<i class="fas fa-lock text-warning mr-2"></i>
|
||||
Headers de sécurité optimisés
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="text-center text-muted small">
|
||||
<p class="mb-1">Merci d'utiliser CDN-APP-INSIDER !</p>
|
||||
<p class="mb-0">
|
||||
<i class="fas fa-heart text-danger"></i>
|
||||
Développé par <strong>Dinawo - Group Myaxrin Labs</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-dismiss="modal">Fermer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="uploadModal" tabindex="-1" role="dialog" aria-labelledby="uploadModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Téléverser dans le dossier racine</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Fermer">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="dashboardUploadForm">
|
||||
<div class="form-group">
|
||||
<label for="uploadFileInput">Sélectionnez un fichier :</label>
|
||||
<input type="file" class="form-control" id="uploadFileInput" name="file" required>
|
||||
<small class="form-text text-muted">Taille maximale : 1 GB</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="uploadExpiryDate">Date d'expiration (optionnel) :</label>
|
||||
<input type="date" class="form-control" id="uploadExpiryDate" name="expiryDate">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="uploadPassword">Mot de passe (optionnel) :</label>
|
||||
<input type="password" class="form-control" id="uploadPassword" name="password" placeholder="Au moins 6 caractères">
|
||||
</div>
|
||||
<div class="progress" id="uploadProgress" style="display: none;">
|
||||
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
|
||||
<span class="sr-only">0% Complete</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>
|
||||
<button type="button" class="btn btn-primary" id="confirmUpload">
|
||||
<i class="fas fa-upload"></i> Téléverser
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
|
||||
<script src="/public/js/dashboard.js"></script>
|
||||
<script>
|
||||
// Upload functionality
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Afficher le changelog uniquement si l'utilisateur ne l'a pas encore vu pour cette version
|
||||
const changelogVersion = '1.2.0-beta';
|
||||
const seenChangelog = localStorage.getItem('changelog_seen_' + changelogVersion);
|
||||
|
||||
if (!seenChangelog) {
|
||||
// Délai de 1 seconde pour laisser la page se charger
|
||||
setTimeout(function() {
|
||||
$('#changelogModal').modal('show');
|
||||
}, 1000);
|
||||
|
||||
// Marquer comme vu quand la modal est fermée
|
||||
$('#changelogModal').on('hidden.bs.modal', function() {
|
||||
localStorage.setItem('changelog_seen_' + changelogVersion, 'true');
|
||||
});
|
||||
}
|
||||
|
||||
// Bouton pour voir le changelog manuellement
|
||||
const showChangelogBtn = document.getElementById('showChangelogBtn');
|
||||
if (showChangelogBtn) {
|
||||
showChangelogBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
$('#changelogModal').modal('show');
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('uploadToDashboardBtn').addEventListener('click', function() {
|
||||
$('#uploadModal').modal('show');
|
||||
});
|
||||
|
||||
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB chunks
|
||||
|
||||
async function generateSecurityCode() {
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
let code = '';
|
||||
for (let i = 0; i < 6; i++) {
|
||||
code += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
async function formatSecureFileName(originalFileName) {
|
||||
const now = new Date();
|
||||
const date = now.toISOString().slice(0,10).replace(/-/g, '');
|
||||
const securityCode = await generateSecurityCode();
|
||||
|
||||
const lastDot = originalFileName.lastIndexOf('.');
|
||||
const fileName = lastDot !== -1 ? originalFileName.substring(0, lastDot) : originalFileName;
|
||||
const fileExt = lastDot !== -1 ? originalFileName.substring(lastDot) : '';
|
||||
|
||||
return `${date}_${securityCode}_${fileName}${fileExt}`;
|
||||
}
|
||||
|
||||
document.getElementById('confirmUpload').addEventListener('click', async function() {
|
||||
const fileInput = document.getElementById('uploadFileInput');
|
||||
const file = fileInput.files[0];
|
||||
|
||||
if (!file) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Erreur',
|
||||
text: 'Veuillez sélectionner un fichier'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const MAX_FILE_SIZE = 1024 * 1024 * 1024; // 1GB
|
||||
if (file.size > MAX_FILE_SIZE) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Fichier trop volumineux',
|
||||
text: 'La taille maximale est de 1 GB'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const password = document.getElementById('uploadPassword').value;
|
||||
if (password && password.length < 6) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Erreur',
|
||||
text: 'Le mot de passe doit contenir au moins 6 caractères'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const expiryDate = document.getElementById('uploadExpiryDate').value;
|
||||
const secureFileName = await formatSecureFileName(file.name);
|
||||
|
||||
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
|
||||
let uploadedChunks = 0;
|
||||
|
||||
const progressBar = document.querySelector('#uploadProgress .progress-bar');
|
||||
document.getElementById('uploadProgress').style.display = 'block';
|
||||
document.getElementById('confirmUpload').disabled = true;
|
||||
|
||||
try {
|
||||
for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
|
||||
const start = chunkIndex * CHUNK_SIZE;
|
||||
const end = Math.min(start + CHUNK_SIZE, file.size);
|
||||
const chunk = file.slice(start, end);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', chunk);
|
||||
formData.append('chunkIndex', chunkIndex);
|
||||
formData.append('totalChunks', totalChunks);
|
||||
formData.append('filename', secureFileName);
|
||||
formData.append('originalFilename', file.name);
|
||||
formData.append('targetFolder', ''); // Dossier racine
|
||||
|
||||
if (expiryDate) formData.append('expiryDate', expiryDate);
|
||||
if (password) formData.append('password', password);
|
||||
|
||||
const response = await fetch('/api/dpanel/upload', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Upload failed: ${response.statusText}`);
|
||||
}
|
||||
|
||||
uploadedChunks++;
|
||||
const progress = (uploadedChunks / totalChunks) * 100;
|
||||
progressBar.style.width = progress + '%';
|
||||
progressBar.textContent = Math.round(progress) + '%';
|
||||
}
|
||||
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Fichier téléversé !',
|
||||
text: 'Le fichier a été téléversé avec succès'
|
||||
}).then(() => {
|
||||
location.reload();
|
||||
});
|
||||
|
||||
$('#uploadModal').modal('hide');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Upload error:', error);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Erreur',
|
||||
text: 'Une erreur est survenue lors du téléversement'
|
||||
});
|
||||
} finally {
|
||||
document.getElementById('confirmUpload').disabled = false;
|
||||
document.getElementById('uploadProgress').style.display = 'none';
|
||||
progressBar.style.width = '0%';
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
972
views/folder.ejs
972
views/folder.ejs
@@ -1,201 +1,807 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"
|
||||
integrity="sha384-GLhlTQ8iRABdZLl6O5oVMWSktQOp6b7In1Zl3/JiR3eZB1+nHN/8u8UqXj2l1tji" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
|
||||
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/toastify-js"></script>
|
||||
<link rel="stylesheet" href="/public/css/styles.css" />
|
||||
<script src="/public/js/folder.js"></script>
|
||||
<title>Dashboard</title>
|
||||
<title>Dashboard CDN</title>
|
||||
<link rel="icon" href="/public/assets/homelab_logo.png" />
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
|
||||
<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@10"></script>
|
||||
<link rel="stylesheet" href="/public/css/dashboard.styles.css">
|
||||
<link rel="stylesheet" href="/public/css/dropdown-fixes.css">
|
||||
<style>
|
||||
|
||||
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;
|
||||
background-image: url('<%= user.wallpaper %>');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="animate">
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-image: url('<%= user.wallpaper %>');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<body class="light-mode">
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light header">
|
||||
<a class="navbar-brand">
|
||||
Dashboard CDN
|
||||
<span class="badge badge-info ml-1">Beta</span>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav"
|
||||
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse ml-auto" id="navbarNav">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<button class="btn btn-warning btn-round mr-2 animated-button" onclick="window.location.href='/dpanel/dashboard';">
|
||||
<i class="fas fa-home"></i>Page principal</button>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<form action="/dpanel/upload" class="form-inline">
|
||||
<button class="btn btn-primary btn-round mr-2 animated-button">
|
||||
<i class="fas fa-cloud-upload-alt"></i> Téléverser un fichier
|
||||
<nav class="navbar navbar-expand-md navbar-light bg-light header">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/dpanel/dashboard">
|
||||
Dashboard CDN
|
||||
<span class="bg-purple-100 text-purple-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded-full dark:bg-purple-900 dark:text-purple-300">Beta</span>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav"
|
||||
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<button type="button" class="btn btn-primary" id="uploadToFolderBtn">
|
||||
<i class="fas fa-cloud-upload-alt"></i> Téléverser ici
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button id="styleSwitcher" class="btn btn-link btn-round animated-button">
|
||||
<span id="themeIcon" class="fas theme-icon"></span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button type="button" class="btn btn-success" id="newFolderBtn">
|
||||
<i class="fas fa-folder-open"></i> Nouveau
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<button class="btn dropdown-toggle nav-btn" id="accountDropdownBtn" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<img
|
||||
src="<%= user.profilePicture ? user.profilePicture : `https://api.dicebear.com/7.x/initials/svg?seed=${encodeURIComponent(user.name)}&background=%234e54c8&radius=50` %>"
|
||||
alt="<%= user.name %>"
|
||||
class="rounded-full user-avatar"
|
||||
style="width: 40px; height: 40px;"
|
||||
onerror="this.src=`https://api.dicebear.com/7.x/initials/svg?seed=${encodeURIComponent('<%= user.name %>')}&background=%234e54c8&radius=50`"
|
||||
/>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right" id="accountDropdownMenu">
|
||||
<a class="dropdown-item" href="/dpanel/dashboard/profil">
|
||||
<span style="display: inline-block; width: 20px; text-align: center;">
|
||||
<i class="fas fa-user"></i>
|
||||
</span>
|
||||
Mon profil
|
||||
</a>
|
||||
<% if (user.role === 'admin') { %>
|
||||
<a class="dropdown-item" href="/dpanel/dashboard/admin">
|
||||
<span style="display: inline-block; width: 20px; text-align: center;">
|
||||
<i class="fas fa-user-shield"></i>
|
||||
</span>
|
||||
Administration du site
|
||||
</a>
|
||||
<% } %>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="/dpanel/dashboard">
|
||||
<span style="display: inline-block; width: 20px; text-align: center;">
|
||||
<i class="fas fa-rocket"></i>
|
||||
</span>
|
||||
Nouveautés v1.2.0-beta
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="/auth/logout">
|
||||
<span style="display: inline-block; width: 20px; text-align: center;">
|
||||
<i class="fas fa-sign-out-alt"></i>
|
||||
</span>
|
||||
Déconnexion
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="alert alert-primary text-center" role="alert">
|
||||
La refonte est presque terminée ! Il ne reste qu'une seule page à finaliser. Myaxrin Labs s'excuse pour le délai et travaille activement à l'amélioration de l'application pour vous offrir une meilleure expérience très prochainement.
|
||||
|
||||
|
||||
<div class="container mt-4 animate">
|
||||
<!-- Menu contextuel -->
|
||||
<div id="contextMenu" class="context-menu" style="display: none; position: fixed; z-index: 1000;">
|
||||
<a href="#" class="context-item-open menu-item">
|
||||
<i class="fas fa-folder-open"></i> <span>Ouvrir</span>
|
||||
</a>
|
||||
<button class="context-item-rename menu-item">
|
||||
<i class="fas fa-edit"></i> <span>Renommer</span>
|
||||
</button>
|
||||
<button class="context-item-share menu-item">
|
||||
<i class="fas fa-share-alt"></i> <span>Copier le lien</span>
|
||||
</button>
|
||||
<button class="context-item-move menu-item">
|
||||
<i class="fas fa-file-export"></i> <span>Déplacer</span>
|
||||
</button>
|
||||
<div class="menu-separator"></div>
|
||||
<button class="context-item-delete menu-item destructive" style="color: #ef4444;">
|
||||
<i class="fas fa-trash-alt"></i> <span>Supprimer</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Breadcrumb -->
|
||||
<nav aria-label="breadcrumb" class="mb-3">
|
||||
<ol class="breadcrumb custom-breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/dpanel/dashboard">Accueil</a></li>
|
||||
<% if (currentFolder) { %>
|
||||
<% let pathSegments = currentFolder.split('/').filter(s => s); %>
|
||||
<% pathSegments.forEach((segment, index) => { %>
|
||||
<% let pathSoFar = pathSegments.slice(0, index + 1).join('/'); %>
|
||||
<li class="breadcrumb-item <%= (index === pathSegments.length - 1) ? 'active' : '' %>">
|
||||
<% if (index === pathSegments.length - 1) { %>
|
||||
<%= segment %>
|
||||
<% } else { %>
|
||||
<a href="/dpanel/dashboard/folder/<%= pathSoFar %>"><%= segment %></a>
|
||||
<% } %>
|
||||
</li>
|
||||
<% }); %>
|
||||
<% } %>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div class="form-container">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<input type="text" id="searchInput" class="form-control w-1/2" placeholder="Rechercher par nom de fichier">
|
||||
<button id="searchButton" class="btn btn-primary">Rechercher</button>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table" id="fileTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom</th>
|
||||
<th class="text-center">Type</th>
|
||||
<th class="text-center">Taille</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% files.forEach(file => { %>
|
||||
<tr data-type="<%= file.type %>" data-name="<%= file.name %>" data-url="<%= file.type === 'folder' ? '/dpanel/dashboard/folder/' + currentFolder + '/' + encodeURIComponent(file.name) : file.url %>" class="hover:bg-gray-50" style="cursor: pointer;">
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<% if (file.type === 'folder') { %>
|
||||
<i class="fas fa-folder text-warning mr-2"></i>
|
||||
<%= file.name %>
|
||||
<% } else { %>
|
||||
<i class="fas fa-file text-secondary mr-2"></i>
|
||||
<%= file.name %>
|
||||
<% } %>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<% if (file.type === 'folder') { %>
|
||||
<span class="badge badge-warning">Dossier</span>
|
||||
<% } else { %>
|
||||
<span class="badge badge-secondary">Fichier</span>
|
||||
<% } %>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<% if (file.type === 'folder') { %>
|
||||
<%
|
||||
function calculateFolderSize(contents) {
|
||||
let totalSize = 0;
|
||||
if (contents && Array.isArray(contents)) {
|
||||
contents.forEach(item => {
|
||||
if (item.type === 'file' && item.size) {
|
||||
totalSize += item.size;
|
||||
} else if (item.type === 'folder' && item.contents) {
|
||||
totalSize += calculateFolderSize(item.contents);
|
||||
}
|
||||
});
|
||||
}
|
||||
return totalSize;
|
||||
}
|
||||
const folderSize = calculateFolderSize(file.contents);
|
||||
%>
|
||||
<span class="file-size" data-size="<%= folderSize %>">
|
||||
<%= folderSize %> octets
|
||||
</span>
|
||||
<% } else { %>
|
||||
<span class="file-size" data-size="<%= file.size %>">
|
||||
<%= file.size %> octets
|
||||
</span>
|
||||
<% } %>
|
||||
</td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb custom-breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/dpanel/dashboard">Accueil</a></li>
|
||||
<% let pathSegments = currentFolder.split('/'); %>
|
||||
<% pathSegments.forEach((segment, index) => { %>
|
||||
<% let pathSoFar = pathSegments.slice(0, index + 1).join('/'); %>
|
||||
<li class="breadcrumb-item <%= (index === pathSegments.length - 1) ? 'active' : '' %>">
|
||||
<% if (index === pathSegments.length - 1) { %>
|
||||
<%= segment %>
|
||||
<% } else { %>
|
||||
<a href="/dpanel/dashboard/folder/<%= pathSoFar %>"><%= segment %></a>
|
||||
<% } %>
|
||||
</li>
|
||||
<% }); %>
|
||||
</ol>
|
||||
</nav>
|
||||
</body>
|
||||
|
||||
<% function formatSize(sizeInBytes) {
|
||||
if (sizeInBytes < 1024) {
|
||||
return `${sizeInBytes} octets`;
|
||||
} else if (sizeInBytes < 1024 * 1024) {
|
||||
return `${(sizeInBytes / 1024).toFixed(2)} Ko`;
|
||||
} else if (sizeInBytes < 1024 * 1024 * 1024) {
|
||||
return `${(sizeInBytes / (1024 * 1024)).toFixed(2)} Mo`;
|
||||
} else {
|
||||
return `${(sizeInBytes / (1024 * 1024 * 1024)).toFixed(2)} Go`;
|
||||
}
|
||||
}
|
||||
%>
|
||||
<footer class="footer mt-auto py-3 bg-light">
|
||||
<div class="container">
|
||||
<span class="text-muted">Version: <span id="version-number">...</span> | © <span id="current-year"></span> Myaxrin Labs</span>
|
||||
<a href="#" class="float-right" onclick="displayMetadata()">Metadata</a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<div class="container mt-4 table-container">
|
||||
<div class="table-responsive">
|
||||
<table class="table w-100">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nom du fichier</th>
|
||||
<th>Taille</th>
|
||||
<th class="text-right">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% files.forEach(file => { %>
|
||||
<tr data-extension="<%= file.extension %>" data-type="<%= file.type %>">
|
||||
<% if (fileInfoNames.includes(file.name)) { %>
|
||||
<td><a href="#" onclick="showFileInfo('<%= file.name %>')"><%= file.name %></a></td>
|
||||
<% } else { %>
|
||||
<td><%= file.name %></td>
|
||||
<% } %>
|
||||
<td>
|
||||
<% if (file.type === 'folder') { %>
|
||||
<% const folderSize = calculateFolderSize(file.contents); %>
|
||||
<%= (folderSize !== undefined && !isNaN(folderSize)) ? formatSize(folderSize) : 'Taille inconnue' %>
|
||||
<% } else { %>
|
||||
<%
|
||||
const fileSizeInBytes = file.size;
|
||||
let fileSize;
|
||||
if (fileSizeInBytes !== undefined && !isNaN(fileSizeInBytes) && fileSizeInBytes >= 0) {
|
||||
fileSize = formatSize(fileSizeInBytes);
|
||||
} else {
|
||||
console.error('Invalid file size:', fileSizeInBytes);
|
||||
fileSize = 'Taille inconnue';
|
||||
}
|
||||
%>
|
||||
<%= fileSize %>
|
||||
<% } %>
|
||||
</td>
|
||||
<td class="d-flex justify-content-end align-items-center">
|
||||
<% if (file.type === 'folder') { %>
|
||||
<a href="/dpanel/dashboard/folder/<%= file.name %>" class="btn btn-primary btn-round mb-2">
|
||||
<i class="fas fa-folder-open fa-xs btn-icon animated-button "></i> Accéder
|
||||
</a>
|
||||
<% } else { %>
|
||||
<button class="btn btn-primary btn-round animated-button" onclick="renameFile('<%= folderName %>', '<%= file.name %>')">
|
||||
<i class="fas fa-edit fa-xs btn-icon"></i> Renommer
|
||||
</button>
|
||||
<form class="file-actions mb-2" id="deleteForm" action="/api/dpanel/dashboard/delete" method="post">
|
||||
<input type="hidden" name="_method" value="DELETE">
|
||||
<input type="hidden" name="filename" value="<%= file.name %>">
|
||||
<button class="delete-button btn btn-danger btn-round animated-button" type="button" onclick="confirmDeleteFile('<%= currentFolder %>', '<%= file.name %>')">
|
||||
<i class="fas fa-trash-alt fa-xs btn-icon"></i>
|
||||
</button>
|
||||
</form>
|
||||
<form class="file-actions mb-2">
|
||||
<div class="copy-link-container d-flex align-items-center">
|
||||
<input type="text" class="file-link form-control rounded mr-2" value="<%= file.url %>" readonly style="display: none;">
|
||||
<button class="button copy-button btn btn-success btn-round animated-button" data-file="<%= file.name %>">
|
||||
<i class="fas fa-copy fa-xs btn-icon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<form id="moveFileForm" class="file-actions d-flex align-items-center mb-2">
|
||||
<input type="hidden" name="fileName" value="<%= file.name %>">
|
||||
<input type="hidden" name="userName" value="<%= userName %>">
|
||||
<input type="hidden" name="oldFolderName" value="<%= folderName %>">
|
||||
<select class="form-control rounded mr-2 custom-dropdown" name="newFolderName">
|
||||
<option value="" disabled selected>Déplacer vers...</option>
|
||||
<option value="root">Dossier Racine</option>
|
||||
<% allFolders.forEach(folder => { %>
|
||||
<option value="<%= folder %>"><%= folder %></option>
|
||||
<% }); %>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-success btn-round animated-button">Déplacer</button>
|
||||
</form>
|
||||
<% } %>
|
||||
</td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<script>
|
||||
document.getElementById('current-year').textContent = new Date().getFullYear();
|
||||
</script>
|
||||
|
||||
<div class="modal fade" id="metadataModal" tabindex="-1" role="dialog" aria-labelledby="metadataModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="metadataModalLabel">Metadata</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p><i class="fas fa-code-branch"></i> Version de Build: <span id="buildVersion"></span></p>
|
||||
<p><i class="fab fa-node"></i> Version de Node.js: <span id="nodeVersion"></span></p>
|
||||
<p><i class="fas fa-server"></i> Version de Express.js: <span id="expressVersion"></span></p>
|
||||
<p><i class="fas fa-hashtag"></i> SHA de Build: <span id="buildSha"></span></p>
|
||||
<p><i class="fas fa-windows"></i> Type d'OS: <span id="osType"></span></p>
|
||||
<p><i class="fas fa-laptop-code"></i> Version d'OS: <span id="osRelease"></span></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Fermer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<footer class="py-3 my-4">
|
||||
<ul class="nav justify-content-center border-bottom pb-3 mb-3">
|
||||
<li class="nav-item"><a class="nav-link px-2 text-muted">Version: <span id="version-number">...</span></a></li>
|
||||
</ul>
|
||||
<p class="text-center text-muted">© 2024 Myaxrin Labs</p>
|
||||
</footer>
|
||||
<div class="modal fade" id="moveFileModal" tabindex="-1" role="dialog" aria-labelledby="moveFileModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Déplacer le fichier</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Fermer">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="moveFileForm">
|
||||
<input type="hidden" id="moveFileName" name="fileName">
|
||||
<input type="hidden" id="moveUserName" name="userName" value="<%= userName %>">
|
||||
<input type="hidden" id="moveOldFolderName" name="oldFolderName" value="<%= folderName %>">
|
||||
<select class="form-control" id="moveFolderSelect" name="newFolderName">
|
||||
<option value="" disabled selected>Choisir un dossier...</option>
|
||||
<option value="root">Dossier Racine</option>
|
||||
<% allFolders.forEach(folder => { %>
|
||||
<option value="<%= folder %>"><%= folder %></option>
|
||||
<% }); %>
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>
|
||||
<button type="button" class="btn btn-primary" id="confirmMoveFile">Déplacer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="uploadModal" tabindex="-1" role="dialog" aria-labelledby="uploadModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Téléverser dans <%= currentFolder || 'ce dossier' %></h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Fermer">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="folderUploadForm">
|
||||
<div class="form-group">
|
||||
<label for="uploadFileInput">Sélectionnez un fichier :</label>
|
||||
<input type="file" class="form-control" id="uploadFileInput" name="file" required>
|
||||
<small class="form-text text-muted">Taille maximale : 1 GB</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="uploadExpiryDate">Date d'expiration (optionnel) :</label>
|
||||
<input type="date" class="form-control" id="uploadExpiryDate" name="expiryDate">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="uploadPassword">Mot de passe (optionnel) :</label>
|
||||
<input type="password" class="form-control" id="uploadPassword" name="password" placeholder="Au moins 6 caractères">
|
||||
</div>
|
||||
<div class="progress" id="uploadProgress" style="display: none;">
|
||||
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
|
||||
<span class="sr-only">0% Complete</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>
|
||||
<button type="button" class="btn btn-primary" id="confirmUpload">
|
||||
<i class="fas fa-upload"></i> Téléverser
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
|
||||
<script src="/public/js/folder.js"></script>
|
||||
<script>
|
||||
// Fonction pour formater la taille des fichiers
|
||||
function formatFileSize(bytes) {
|
||||
if (bytes === 0) return '0 octets';
|
||||
const k = 1024;
|
||||
const sizes = ['octets', 'Ko', 'Mo', 'Go', 'To'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
// Formater toutes les tailles de fichiers au chargement
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('.file-size').forEach(function(element) {
|
||||
const size = parseInt(element.getAttribute('data-size'));
|
||||
if (!isNaN(size)) {
|
||||
element.textContent = formatFileSize(size);
|
||||
}
|
||||
});
|
||||
|
||||
// Gestion du menu contextuel
|
||||
const contextMenu = document.getElementById('contextMenu');
|
||||
let currentContextRow = null;
|
||||
|
||||
// Clic droit sur les lignes du tableau
|
||||
document.querySelectorAll('#fileTable tbody tr').forEach(function(row) {
|
||||
row.addEventListener('contextmenu', function(e) {
|
||||
e.preventDefault();
|
||||
currentContextRow = row;
|
||||
|
||||
const type = row.getAttribute('data-type');
|
||||
const name = row.getAttribute('data-name');
|
||||
const url = row.getAttribute('data-url');
|
||||
|
||||
// Positionner le menu
|
||||
contextMenu.style.display = 'block';
|
||||
contextMenu.style.left = e.pageX + 'px';
|
||||
contextMenu.style.top = e.pageY + 'px';
|
||||
|
||||
// Configuration selon le type et les permissions
|
||||
const openItem = contextMenu.querySelector('.context-item-open');
|
||||
const moveItem = contextMenu.querySelector('.context-item-move');
|
||||
const renameItem = contextMenu.querySelector('.context-item-rename');
|
||||
const shareItem = contextMenu.querySelector('.context-item-share');
|
||||
const deleteItem = contextMenu.querySelector('.context-item-delete');
|
||||
const separator = contextMenu.querySelector('.menu-separator');
|
||||
|
||||
// Récupérer les données de collaboration depuis le serveur
|
||||
// isCollaborativeFolder fait référence au dossier ACTUEL (celui dans lequel on navigue)
|
||||
const isCollaborativeFolder = <%= typeof isCollaborativeFolder !== 'undefined' && isCollaborativeFolder ? 'true' : 'false' %>;
|
||||
const isOwner = <%= typeof isOwner !== 'undefined' && isOwner ? 'true' : 'false' %>;
|
||||
|
||||
// Masquer tous les éléments par défaut
|
||||
openItem.style.display = 'none';
|
||||
moveItem.style.display = 'none';
|
||||
renameItem.style.display = 'none';
|
||||
shareItem.style.display = 'none';
|
||||
deleteItem.style.display = 'none';
|
||||
if (separator) separator.style.display = 'none';
|
||||
|
||||
if (type === 'folder') {
|
||||
// Pour les sous-dossiers : uniquement "Ouvrir"
|
||||
openItem.style.display = 'block';
|
||||
openItem.href = url;
|
||||
} else {
|
||||
// Pour les fichiers
|
||||
// Permissions basées sur si on est dans un dossier partagé
|
||||
if (isCollaborativeFolder && !isOwner) {
|
||||
// Invité dans un dossier partagé : peut seulement copier le lien
|
||||
shareItem.style.display = 'block';
|
||||
} else {
|
||||
// Propriétaire ou dossier non partagé : tous les droits
|
||||
renameItem.style.display = 'block';
|
||||
shareItem.style.display = 'block';
|
||||
moveItem.style.display = 'block';
|
||||
if (separator) separator.style.display = 'block';
|
||||
deleteItem.style.display = 'block';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Fermer le menu contextuel
|
||||
document.addEventListener('click', function() {
|
||||
contextMenu.style.display = 'none';
|
||||
});
|
||||
|
||||
// Renommer
|
||||
contextMenu.querySelector('.context-item-rename').addEventListener('click', async function() {
|
||||
if (currentContextRow) {
|
||||
const name = currentContextRow.getAttribute('data-name');
|
||||
const type = currentContextRow.getAttribute('data-type');
|
||||
if (type === 'file') {
|
||||
const result = await Swal.fire({
|
||||
title: 'Renommer le fichier',
|
||||
input: 'text',
|
||||
inputLabel: 'Nouveau nom',
|
||||
inputValue: name,
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Renommer',
|
||||
cancelButtonText: 'Annuler',
|
||||
inputValidator: (value) => {
|
||||
if (!value) {
|
||||
return 'Vous devez entrer un nom !';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (result.isConfirmed && result.value !== name) {
|
||||
const newName = result.value;
|
||||
// Appel API pour renommer
|
||||
fetch('/api/dpanel/dashboard/rename', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
folderName: '<%= folderName %>',
|
||||
oldName: name,
|
||||
newName: newName
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Fichier renommé !',
|
||||
text: 'Le fichier a été renommé avec succès'
|
||||
}).then(() => location.reload());
|
||||
} else {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Erreur',
|
||||
text: data.message || 'Une erreur est survenue'
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Erreur',
|
||||
text: 'Une erreur est survenue lors du renommage'
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
contextMenu.style.display = 'none';
|
||||
});
|
||||
|
||||
// Copier le lien
|
||||
contextMenu.querySelector('.context-item-share').addEventListener('click', function() {
|
||||
if (currentContextRow) {
|
||||
const url = currentContextRow.getAttribute('data-url');
|
||||
navigator.clipboard.writeText(url).then(function() {
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Lien copié !',
|
||||
text: 'Le lien a été copié dans le presse-papiers',
|
||||
toast: true,
|
||||
position: 'top-end',
|
||||
showConfirmButton: false,
|
||||
timer: 3000
|
||||
});
|
||||
});
|
||||
}
|
||||
contextMenu.style.display = 'none';
|
||||
});
|
||||
|
||||
// Déplacer
|
||||
contextMenu.querySelector('.context-item-move').addEventListener('click', function() {
|
||||
if (currentContextRow) {
|
||||
const name = currentContextRow.getAttribute('data-name');
|
||||
document.getElementById('moveFileName').value = name;
|
||||
$('#moveFileModal').modal('show');
|
||||
}
|
||||
contextMenu.style.display = 'none';
|
||||
});
|
||||
|
||||
// Supprimer
|
||||
contextMenu.querySelector('.context-item-delete').addEventListener('click', async function() {
|
||||
if (currentContextRow) {
|
||||
const name = currentContextRow.getAttribute('data-name');
|
||||
const type = currentContextRow.getAttribute('data-type');
|
||||
if (type === 'file') {
|
||||
const result = await Swal.fire({
|
||||
title: 'Êtes-vous sûr ?',
|
||||
text: 'Cette action est irréversible !',
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#d33',
|
||||
cancelButtonColor: '#3085d6',
|
||||
confirmButtonText: 'Oui, supprimer !',
|
||||
cancelButtonText: 'Annuler'
|
||||
});
|
||||
|
||||
if (result.isConfirmed) {
|
||||
fetch('/api/dpanel/dashboard/delete', {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
filename: name
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Fichier supprimé !',
|
||||
text: 'Le fichier a été supprimé avec succès'
|
||||
}).then(() => location.reload());
|
||||
} else {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Erreur',
|
||||
text: data.message || 'Une erreur est survenue'
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Erreur',
|
||||
text: 'Une erreur est survenue lors de la suppression'
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
contextMenu.style.display = 'none';
|
||||
});
|
||||
|
||||
// Confirmation du déplacement
|
||||
document.getElementById('confirmMoveFile').addEventListener('click', function() {
|
||||
const fileName = document.getElementById('moveFileName').value;
|
||||
const newFolderName = document.getElementById('moveFolderSelect').value;
|
||||
const userName = document.getElementById('moveUserName').value;
|
||||
const oldFolderName = document.getElementById('moveOldFolderName').value;
|
||||
|
||||
if (!newFolderName) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Erreur',
|
||||
text: 'Veuillez sélectionner un dossier de destination'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Envoi de la requête de déplacement
|
||||
fetch('/api/dpanel/dashboard/move', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
fileName: fileName,
|
||||
userName: userName,
|
||||
oldFolderName: oldFolderName,
|
||||
newFolderName: newFolderName
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Fichier déplacé !',
|
||||
text: 'Le fichier a été déplacé avec succès'
|
||||
}).then(() => {
|
||||
location.reload();
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Erreur',
|
||||
text: data.message || 'Une erreur est survenue'
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Erreur',
|
||||
text: 'Une erreur est survenue lors du déplacement'
|
||||
});
|
||||
});
|
||||
|
||||
$('#moveFileModal').modal('hide');
|
||||
});
|
||||
|
||||
// Recherche
|
||||
document.getElementById('searchButton').addEventListener('click', function() {
|
||||
const searchValue = document.getElementById('searchInput').value.toLowerCase();
|
||||
const rows = document.querySelectorAll('#fileTable tbody tr');
|
||||
|
||||
rows.forEach(function(row) {
|
||||
const name = row.getAttribute('data-name').toLowerCase();
|
||||
if (name.includes(searchValue)) {
|
||||
row.style.display = '';
|
||||
} else {
|
||||
row.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Recherche en temps réel
|
||||
document.getElementById('searchInput').addEventListener('keyup', function() {
|
||||
const searchValue = this.value.toLowerCase();
|
||||
const rows = document.querySelectorAll('#fileTable tbody tr');
|
||||
|
||||
rows.forEach(function(row) {
|
||||
const name = row.getAttribute('data-name').toLowerCase();
|
||||
if (name.includes(searchValue)) {
|
||||
row.style.display = '';
|
||||
} else {
|
||||
row.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Dark mode synchronisé
|
||||
const savedTheme = localStorage.getItem('theme') || 'light';
|
||||
if (savedTheme === 'dark') {
|
||||
document.documentElement.classList.add('dark');
|
||||
document.body.classList.add('dark-mode');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
document.body.classList.remove('dark-mode');
|
||||
}
|
||||
|
||||
// Double-clic pour ouvrir
|
||||
document.querySelectorAll('#fileTable tbody tr').forEach(function(row) {
|
||||
row.addEventListener('dblclick', function() {
|
||||
const url = row.getAttribute('data-url');
|
||||
const type = row.getAttribute('data-type');
|
||||
|
||||
if (type === 'folder') {
|
||||
window.location.href = url;
|
||||
} else {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Gestion de l'upload dans le dossier
|
||||
document.getElementById('uploadToFolderBtn').addEventListener('click', function() {
|
||||
$('#uploadModal').modal('show');
|
||||
});
|
||||
|
||||
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB chunks
|
||||
|
||||
async function generateSecurityCode() {
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
let code = '';
|
||||
for (let i = 0; i < 6; i++) {
|
||||
code += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
async function formatSecureFileName(originalFileName) {
|
||||
const now = new Date();
|
||||
const date = now.toISOString().slice(0,10).replace(/-/g, '');
|
||||
const securityCode = await generateSecurityCode();
|
||||
|
||||
const lastDot = originalFileName.lastIndexOf('.');
|
||||
const fileName = lastDot !== -1 ? originalFileName.substring(0, lastDot) : originalFileName;
|
||||
const fileExt = lastDot !== -1 ? originalFileName.substring(lastDot) : '';
|
||||
|
||||
return `${date}_${securityCode}_${fileName}${fileExt}`;
|
||||
}
|
||||
|
||||
document.getElementById('confirmUpload').addEventListener('click', async function() {
|
||||
const fileInput = document.getElementById('uploadFileInput');
|
||||
const file = fileInput.files[0];
|
||||
|
||||
if (!file) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Erreur',
|
||||
text: 'Veuillez sélectionner un fichier'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const MAX_FILE_SIZE = 1024 * 1024 * 1024; // 1GB
|
||||
if (file.size > MAX_FILE_SIZE) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Fichier trop volumineux',
|
||||
text: 'La taille maximale est de 1 GB'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const password = document.getElementById('uploadPassword').value;
|
||||
if (password && password.length < 6) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Erreur',
|
||||
text: 'Le mot de passe doit contenir au moins 6 caractères'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const expiryDate = document.getElementById('uploadExpiryDate').value;
|
||||
const currentFolder = '<%= currentFolder %>';
|
||||
const isSharedFolder = <%= typeof isSharedFolder !== 'undefined' && isSharedFolder ? 'true' : 'false' %>;
|
||||
const ownerName = '<%= typeof ownerName !== "undefined" ? ownerName : "" %>';
|
||||
const secureFileName = await formatSecureFileName(file.name);
|
||||
|
||||
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
|
||||
let uploadedChunks = 0;
|
||||
|
||||
const progressBar = document.querySelector('#uploadProgress .progress-bar');
|
||||
document.getElementById('uploadProgress').style.display = 'block';
|
||||
document.getElementById('confirmUpload').disabled = true;
|
||||
|
||||
try {
|
||||
for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
|
||||
const start = chunkIndex * CHUNK_SIZE;
|
||||
const end = Math.min(start + CHUNK_SIZE, file.size);
|
||||
const chunk = file.slice(start, end);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', chunk);
|
||||
formData.append('chunkIndex', chunkIndex);
|
||||
formData.append('totalChunks', totalChunks);
|
||||
formData.append('filename', secureFileName);
|
||||
formData.append('originalFilename', file.name);
|
||||
formData.append('targetFolder', currentFolder);
|
||||
|
||||
// Pour les dossiers partagés
|
||||
if (isSharedFolder && ownerName) {
|
||||
formData.append('isSharedFolder', 'true');
|
||||
formData.append('ownerName', ownerName);
|
||||
}
|
||||
|
||||
if (expiryDate) formData.append('expiryDate', expiryDate);
|
||||
if (password) formData.append('password', password);
|
||||
|
||||
const response = await fetch('/api/dpanel/upload', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Upload failed: ${response.statusText}`);
|
||||
}
|
||||
|
||||
uploadedChunks++;
|
||||
const progress = (uploadedChunks / totalChunks) * 100;
|
||||
progressBar.style.width = progress + '%';
|
||||
progressBar.textContent = Math.round(progress) + '%';
|
||||
}
|
||||
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Fichier téléversé !',
|
||||
text: 'Le fichier a été téléversé avec succès'
|
||||
}).then(() => {
|
||||
location.reload();
|
||||
});
|
||||
|
||||
$('#uploadModal').modal('hide');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Upload error:', error);
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Erreur',
|
||||
text: 'Une erreur est survenue lors du téléversement'
|
||||
});
|
||||
} finally {
|
||||
document.getElementById('confirmUpload').disabled = false;
|
||||
document.getElementById('uploadProgress').style.display = 'none';
|
||||
progressBar.style.width = '0%';
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,24 +3,12 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Paramètres Admin</title>
|
||||
<title>Panneau d'Administration - CDN Insider</title>
|
||||
<link rel="icon" href="/public/assets/homelab_logo.png" />
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
|
||||
<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@10"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<style>
|
||||
body {
|
||||
background-image: url('<%= user.wallpaper %>'); /* Placeholder for dynamic background */
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
||||
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
@@ -67,170 +55,702 @@
|
||||
--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 {
|
||||
max-width: 960px;
|
||||
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;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
background-color: hsl(var(--card));
|
||||
.profile-card {
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: var(--radius);
|
||||
padding: 2rem;
|
||||
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: 900px;
|
||||
animation: slideIn 0.5s ease-out;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px) scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Header admin */
|
||||
.profile-header {
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%);
|
||||
color: white;
|
||||
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;
|
||||
}
|
||||
|
||||
.admin-icon {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
.admin-icon i {
|
||||
font-size: 4rem;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
filter: drop-shadow(0 10px 30px rgba(0, 0, 0, 0.3));
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { 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.2);
|
||||
}
|
||||
|
||||
.user-subtitle {
|
||||
font-size: 1rem;
|
||||
opacity: 0.9;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.role-badge {
|
||||
display: inline-block;
|
||||
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 25px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.4);
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
/* 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); }
|
||||
}
|
||||
|
||||
/* Stats cards */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: var(--radius);
|
||||
background-color: hsl(var(--background));
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stat-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, #3b82f6, #8b5cf6);
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.85rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
color: hsl(var(--foreground));
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
/* 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));
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.quick-action::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
|
||||
transition: left 0.5s;
|
||||
}
|
||||
|
||||
.quick-action:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.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: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
color: hsl(var(--primary));
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.quick-action:hover .quick-action-icon {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.quick-action-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.quick-action-desc {
|
||||
font-size: 0.85rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
/* System info */
|
||||
.system-info-grid {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.system-info-item {
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 8px;
|
||||
padding: 1rem 1.5rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.system-info-label {
|
||||
font-weight: 500;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.system-info-value {
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
/* Boutons */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius);
|
||||
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;
|
||||
width: 100%; /* Full width */
|
||||
padding: 0.75rem; /* Adjust padding as needed */
|
||||
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-color: hsl(var(--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 {
|
||||
opacity: 0.9;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px 0 rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: hsl(var(--secondary));
|
||||
background: hsl(var(--secondary));
|
||||
color: hsl(var(--secondary-foreground));
|
||||
border: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
opacity: 0.9;
|
||||
background: hsl(var(--accent));
|
||||
color: hsl(var(--accent-foreground));
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
#themeSwitcher {
|
||||
.btn-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Theme switcher */
|
||||
.theme-switcher {
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
z-index: 1000; /* Ensure it stays above other elements */
|
||||
background-color: hsl(var(--secondary));
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
padding: 0.5rem;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
#themeSwitcher:hover {
|
||||
background-color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
#themeSwitcher svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
color: hsl(var(--primary-foreground));
|
||||
}
|
||||
|
||||
.animate {
|
||||
animation: fadeIn 0.5s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.icon-spacing {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.swal2-toast {
|
||||
background-color: hsl(var(--card));
|
||||
color: hsl(var(--foreground));
|
||||
top: 2rem;
|
||||
right: 2rem;
|
||||
z-index: 10;
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: var(--radius);
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
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) rotate(180deg);
|
||||
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;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 1rem 1.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.theme-switcher {
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.quick-actions {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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: #000; /* Fond noir en mode sombre */
|
||||
color: #fff; /* Texte blanc */
|
||||
border: 1px solid #333; /* Bordure grise foncée */
|
||||
background-color: hsl(var(--card)) !important;
|
||||
color: hsl(var(--foreground)) !important;
|
||||
border: 1px solid hsl(var(--border)) !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="animate dark">
|
||||
<button id="themeSwitcher">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<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>
|
||||
<body class="dark">
|
||||
<div class="backdrop"></div>
|
||||
|
||||
<div class="min-h-screen flex items-center justify-center p-4">
|
||||
<div class="container mt-8">
|
||||
<h1 class="text-3xl font-semibold mb-6 text-center">Paramètres Admin</h1>
|
||||
<div class="container">
|
||||
<div class="profile-card">
|
||||
<!-- Header admin -->
|
||||
<div class="profile-header">
|
||||
<div class="admin-icon">
|
||||
<i class="fas fa-user-shield"></i>
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<h1 class="user-name">Panneau d'Administration</h1>
|
||||
<p class="user-subtitle">Centre de contrôle du système CDN Insider</p>
|
||||
<div class="role-badge">
|
||||
<i class="fas fa-crown"></i> Administrateur
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-container">
|
||||
<div class="flex flex-col gap-4">
|
||||
<a href="/dpanel/dashboard/admin/users?page=1&limit=10" class="btn btn-primary flex items-center justify-center space-x-2">
|
||||
<i class="fas fa-users icon-spacing"></i>
|
||||
<span>Gérer les utilisateurs</span>
|
||||
</a>
|
||||
|
||||
<a href="/dpanel/dashboard/admin/settingsetup" class="btn btn-primary flex items-center justify-center space-x-2">
|
||||
<i class="fas fa-cogs icon-spacing"></i>
|
||||
<span>Modifier les paramètres de configuration</span>
|
||||
</a>
|
||||
|
||||
<a href="/dpanel/dashboard/admin/stats-logs" class="btn btn-primary flex items-center justify-center space-x-2">
|
||||
<i class="fas fa-chart-bar icon-spacing"></i>
|
||||
<span>Afficher les statistiques & logs</span>
|
||||
</a>
|
||||
|
||||
<a href="/dpanel/dashboard/admin/Privacy-Security" class="btn btn-primary flex items-center justify-center space-x-2">
|
||||
<i class="fas fa-shield-alt icon-spacing"></i>
|
||||
<span>Confidentialité & Sécurité</span>
|
||||
</a>
|
||||
|
||||
<button onclick="window.location.href='/dpanel/dashboard';" class="btn btn-secondary w-full py-2 mt-4">
|
||||
<i class="fas fa-arrow-left icon-spacing"></i>
|
||||
Retour au Dashboard
|
||||
<!-- Onglets -->
|
||||
<div class="tabs-container">
|
||||
<div class="tabs">
|
||||
<button class="tab active" data-tab="overview">
|
||||
<i class="fas fa-chart-pie"></i>
|
||||
Aperçu
|
||||
</button>
|
||||
<button class="tab" data-tab="management">
|
||||
<i class="fas fa-tasks"></i>
|
||||
Gestion
|
||||
</button>
|
||||
<button class="tab" data-tab="system">
|
||||
<i class="fas fa-server"></i>
|
||||
Système
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contenu de l'onglet Aperçu -->
|
||||
<div class="tab-content active" id="overview">
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">
|
||||
<i class="fas fa-users"></i>
|
||||
</div>
|
||||
<div class="stat-label">Utilisateurs totaux</div>
|
||||
<div class="stat-value"><%= users.length %></div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">
|
||||
<i class="fas fa-folder"></i>
|
||||
</div>
|
||||
<div class="stat-label">Dossiers actifs</div>
|
||||
<div class="stat-value"><%= stats.foldersCount %></div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">
|
||||
<i class="fas fa-hdd"></i>
|
||||
</div>
|
||||
<div class="stat-label">Espace utilisé</div>
|
||||
<div class="stat-value">
|
||||
<%
|
||||
const sizeInGB = (stats.totalSize / (1024 * 1024 * 1024)).toFixed(2);
|
||||
const sizeInMB = (stats.totalSize / (1024 * 1024)).toFixed(2);
|
||||
%>
|
||||
<%= stats.totalSize > 1073741824 ? sizeInGB + ' GB' : sizeInMB + ' MB' %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; margin-top: 2rem;">
|
||||
<a href="/dpanel/dashboard" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
Retour au Dashboard
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contenu de l'onglet Gestion -->
|
||||
<div class="tab-content" id="management">
|
||||
<div class="quick-actions">
|
||||
<a href="/dpanel/dashboard/admin/users?page=1&limit=10" class="quick-action">
|
||||
<div class="quick-action-icon">
|
||||
<i class="fas fa-users-cog"></i>
|
||||
</div>
|
||||
<div class="quick-action-title">Utilisateurs</div>
|
||||
<div class="quick-action-desc">Gérer les comptes utilisateurs</div>
|
||||
</a>
|
||||
<a href="/dpanel/dashboard/admin/settingsetup" class="quick-action">
|
||||
<div class="quick-action-icon">
|
||||
<i class="fas fa-cogs"></i>
|
||||
</div>
|
||||
<div class="quick-action-title">Configuration</div>
|
||||
<div class="quick-action-desc">Paramètres du système</div>
|
||||
</a>
|
||||
<a href="/dpanel/dashboard/admin/stats-logs" class="quick-action">
|
||||
<div class="quick-action-icon">
|
||||
<i class="fas fa-chart-line"></i>
|
||||
</div>
|
||||
<div class="quick-action-title">Stats & Logs</div>
|
||||
<div class="quick-action-desc">Statistiques et journaux</div>
|
||||
</a>
|
||||
<a href="/dpanel/dashboard/admin/Privacy-Security" class="quick-action">
|
||||
<div class="quick-action-icon">
|
||||
<i class="fas fa-shield-alt"></i>
|
||||
</div>
|
||||
<div class="quick-action-title">Sécurité</div>
|
||||
<div class="quick-action-desc">Confidentialité et sécurité</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contenu de l'onglet Système -->
|
||||
<div class="tab-content" id="system">
|
||||
<div class="system-info-grid">
|
||||
<div class="system-info-item">
|
||||
<span class="system-info-label">
|
||||
<i class="fas fa-server"></i> Statut du serveur
|
||||
</span>
|
||||
<span class="system-info-value" style="color: #10b981;">
|
||||
<i class="fas fa-circle"></i> En ligne
|
||||
</span>
|
||||
</div>
|
||||
<div class="system-info-item">
|
||||
<span class="system-info-label">
|
||||
<i class="fas fa-code-branch"></i> Version
|
||||
</span>
|
||||
<span class="system-info-value">v1.2.0-beta</span>
|
||||
</div>
|
||||
<div class="system-info-item">
|
||||
<span class="system-info-label">
|
||||
<i class="fas fa-clock"></i> Uptime
|
||||
</span>
|
||||
<span class="system-info-value">
|
||||
<%
|
||||
const days = Math.floor(stats.uptime / 86400);
|
||||
const hours = Math.floor((stats.uptime % 86400) / 3600);
|
||||
const minutes = Math.floor((stats.uptime % 3600) / 60);
|
||||
const uptimeText = days > 0 ? `${days}j ${hours}h ${minutes}m` :
|
||||
hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
|
||||
%>
|
||||
<%= uptimeText %>
|
||||
</span>
|
||||
</div>
|
||||
<div class="system-info-item">
|
||||
<span class="system-info-label">
|
||||
<i class="fas fa-database"></i> Base de données
|
||||
</span>
|
||||
<span class="system-info-value" style="color: #10b981;">
|
||||
<i class="fas fa-check-circle"></i> Connectée
|
||||
</span>
|
||||
</div>
|
||||
<div class="system-info-item">
|
||||
<span class="system-info-label">
|
||||
<i class="fas fa-memory"></i> Utilisation mémoire
|
||||
</span>
|
||||
<span class="system-info-value"><%= stats.memoryUsage %> MB</span>
|
||||
</div>
|
||||
<div class="system-info-item">
|
||||
<span class="system-info-label">
|
||||
<i class="fas fa-microchip"></i> Charge CPU
|
||||
</span>
|
||||
<span class="system-info-value"><%= stats.cpuUsage %> ms</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 2rem; padding: 1.5rem; background: hsl(var(--muted)); border-radius: 12px; border-left: 4px solid #3b82f6;">
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.75rem;">
|
||||
<i class="fas fa-info-circle" style="color: #3b82f6; font-size: 1.25rem;"></i>
|
||||
<strong style="color: hsl(var(--foreground));">Informations système</strong>
|
||||
</div>
|
||||
<p style="margin: 0; font-size: 0.9rem; color: hsl(var(--muted-foreground)); line-height: 1.6;">
|
||||
Le système CDN Insider fonctionne de manière optimale. Toutes les connexions sont sécurisées et les services sont opérationnels.
|
||||
Surveillez régulièrement les statistiques pour maintenir les performances du système.
|
||||
</p>
|
||||
</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');
|
||||
@@ -239,14 +759,15 @@
|
||||
}
|
||||
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');
|
||||
@@ -254,13 +775,39 @@
|
||||
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: 1800,
|
||||
timer: 3000,
|
||||
toast: true,
|
||||
position: 'top-end',
|
||||
customClass: {
|
||||
@@ -268,6 +815,18 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// =================== 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('🎨 Panneau d\'administration chargé !');
|
||||
console.log('✨ Interface moderne activée');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,25 +3,12 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Confidentialité et Sécurité</title>
|
||||
<title>Confidentialité & Sécurité - Interface Admin</title>
|
||||
<link rel="icon" href="/public/assets/homelab_logo.png" />
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
|
||||
<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@10"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<style>
|
||||
body {
|
||||
background-image: url('<%= user.wallpaper %>');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
||||
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
@@ -68,153 +55,541 @@
|
||||
--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 {
|
||||
max-width: 960px;
|
||||
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;
|
||||
}
|
||||
|
||||
.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%;
|
||||
max-width: 1000px;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
.admin-header {
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
color: white;
|
||||
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-header h1 {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.admin-content {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
background-color: hsl(var(--card));
|
||||
.section-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.section-title i {
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.info-banner {
|
||||
background: linear-gradient(135deg, rgba(16, 185, 129, 0.1) 0%, rgba(5, 150, 105, 0.1) 100%);
|
||||
border: 1px solid rgba(16, 185, 129, 0.3);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
display: flex;
|
||||
align-items: start;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.info-banner-icon {
|
||||
font-size: 1.5rem;
|
||||
color: #10b981;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-banner-content h3 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.info-banner-content p {
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.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: var(--radius);
|
||||
padding: 2rem;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
color: hsl(var(--foreground));
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.quick-action::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, #10b981, #059669);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.quick-action:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.quick-action:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 10px 25px rgba(16, 185, 129, 0.2);
|
||||
text-decoration: none;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.quick-action-icon {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.quick-action-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.quick-action-desc {
|
||||
font-size: 0.85rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius);
|
||||
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-color: hsl(var(--primary));
|
||||
color: hsl(var(--primary-foreground));
|
||||
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
color: white;
|
||||
box-shadow: 0 4px 14px 0 rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
opacity: 0.9;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px 0 rgba(16, 185, 129, 0.4);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: hsl(var(--secondary));
|
||||
background: hsl(var(--secondary));
|
||||
color: hsl(var(--secondary-foreground));
|
||||
border: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
opacity: 0.9;
|
||||
background: hsl(var(--accent));
|
||||
color: hsl(var(--accent-foreground));
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
#themeSwitcher {
|
||||
.btn-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.theme-switcher {
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
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);
|
||||
}
|
||||
|
||||
.animate {
|
||||
animation: fadeIn 0.5s ease-out;
|
||||
.theme-switcher:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
.theme-switcher svg {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: hsl(var(--background));
|
||||
margin: 15% auto;
|
||||
padding: 20px;
|
||||
background: hsl(var(--card));
|
||||
margin: 5% auto;
|
||||
padding: 0;
|
||||
border: 1px solid hsl(var(--border));
|
||||
width: 80%;
|
||||
border-radius: var(--radius);
|
||||
width: 90%;
|
||||
max-width: 900px;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 1.5rem 2rem;
|
||||
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
color: white;
|
||||
border-radius: 16px 16px 0 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.close {
|
||||
color: hsl(var(--muted-foreground));
|
||||
float: right;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
font-size: 2rem;
|
||||
font-weight: 300;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
line-height: 1;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.close:hover,
|
||||
.close:focus {
|
||||
.close:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 2rem;
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-body pre {
|
||||
background: hsl(var(--muted));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
overflow-x: auto;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.6;
|
||||
color: hsl(var(--foreground));
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 3rem 2rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.empty-state-icon {
|
||||
font-size: 4rem;
|
||||
margin-bottom: 1.5rem;
|
||||
opacity: 0.3;
|
||||
color: #10b981;
|
||||
}
|
||||
|
||||
.empty-state h3 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
font-size: 0.95rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.admin-header {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.admin-header h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.admin-content {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.theme-switcher {
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
width: 95%;
|
||||
margin: 10% auto;
|
||||
}
|
||||
|
||||
.quick-actions {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="animate">
|
||||
<div id="app" class="min-h-screen">
|
||||
<div class="container mt-8">
|
||||
<h1 class="text-3xl font-semibold mb-6 text-center animate">Confidentialité et Sécurité</h1>
|
||||
<body class="dark">
|
||||
<div class="backdrop"></div>
|
||||
|
||||
<div class="container">
|
||||
<div class="admin-card">
|
||||
<div class="admin-header">
|
||||
<h1><i class="fas fa-shield-alt"></i> Confidentialité & Sécurité</h1>
|
||||
</div>
|
||||
|
||||
<div class="admin-content">
|
||||
<!-- Banner d'information -->
|
||||
<div class="info-banner">
|
||||
<div class="info-banner-icon">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
</div>
|
||||
<div class="info-banner-content">
|
||||
<h3>Centre de Confidentialité</h3>
|
||||
<p>
|
||||
Consultez les rapports de confidentialité et de sécurité de votre système.
|
||||
Ces données sont collectées de manière anonyme et permettent d'améliorer la sécurité de l'application.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rapports -->
|
||||
<div class="section-title">
|
||||
<i class="fas fa-file-shield"></i>
|
||||
Rapports de Sécurité
|
||||
</div>
|
||||
|
||||
<div class="form-container">
|
||||
<h2 class="text-2xl font-semibold mb-4">Données d'analyse</h2>
|
||||
|
||||
<% if (reports && reports.length > 0) { %>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="quick-actions">
|
||||
<% reports.forEach((report, index) => { %>
|
||||
<% if (report) { %>
|
||||
<div class="text-center">
|
||||
<button class="btn btn-primary w-full reportName" data-index="<%= index %>"><%= report.name %></button>
|
||||
</div>
|
||||
<div id="myReportModal<%= index %>" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close" data-index="<%= index %>">×</span>
|
||||
<pre class="whitespace-pre-wrap"><%= report.content %></pre>
|
||||
<div class="quick-action" data-report-index="<%= index %>">
|
||||
<div class="quick-action-icon">
|
||||
<i class="fas fa-file-contract"></i>
|
||||
</div>
|
||||
<div class="quick-action-title"><%= report.name %></div>
|
||||
<div class="quick-action-desc">Cliquer pour voir le détail</div>
|
||||
</div>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<p class="text-center text-lg text-gray-500">Aucun rapport disponible pour le moment.</p>
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
</div>
|
||||
<h3>Aucun rapport disponible</h3>
|
||||
<p>Les rapports de sécurité apparaîtront ici lorsqu'ils seront générés par le système.</p>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="text-center">
|
||||
<br><button onclick="window.location.href='/dpanel/dashboard/admin';" class="btn btn-secondary w-full py-2 mt-4">
|
||||
<i class="fas fa-arrow-left icon-spacing"></i>
|
||||
Retour au Dashboard Admin
|
||||
</button>
|
||||
<!-- Bouton retour -->
|
||||
<div style="margin-top: 2rem;">
|
||||
<a href="/dpanel/dashboard/admin/" class="btn btn-secondary btn-full">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
Retourner au dashboard admin
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="themeSwitcher" class="btn btn-secondary p-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<!-- Modals pour les rapports -->
|
||||
<% if (reports && reports.length > 0) { %>
|
||||
<% reports.forEach((report, index) => { %>
|
||||
<% if (report) { %>
|
||||
<div id="reportModal<%= index %>" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2><i class="fas fa-file-contract"></i> <%= report.name %></h2>
|
||||
<span class="close" data-modal-index="<%= index %>">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<pre><%= report.content %></pre>
|
||||
</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');
|
||||
@@ -239,30 +614,45 @@
|
||||
}
|
||||
});
|
||||
|
||||
var reportNames = document.getElementsByClassName("reportName");
|
||||
var reportCloseButtons = document.getElementsByClassName("close");
|
||||
// =================== GESTION DES MODALS ===================
|
||||
const reportButtons = document.querySelectorAll('.quick-action[data-report-index]');
|
||||
const closeButtons = document.querySelectorAll('.close[data-modal-index]');
|
||||
|
||||
for (var i = 0; i < reportNames.length; i++) {
|
||||
reportNames[i].addEventListener("click", function(event) {
|
||||
var index = event.target.getAttribute("data-index");
|
||||
var modal = document.getElementById("myReportModal" + index);
|
||||
modal.style.display = "block";
|
||||
reportButtons.forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const index = this.getAttribute('data-report-index');
|
||||
const modal = document.getElementById('reportModal' + index);
|
||||
if (modal) {
|
||||
modal.style.display = 'block';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
for (var i = 0; i < reportCloseButtons.length; i++) {
|
||||
reportCloseButtons[i].addEventListener("click", function(event) {
|
||||
var index = event.target.getAttribute("data-index");
|
||||
var modal = document.getElementById("myReportModal" + index);
|
||||
modal.style.display = "none";
|
||||
closeButtons.forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const index = this.getAttribute('data-modal-index');
|
||||
const modal = document.getElementById('reportModal' + index);
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
window.onclick = function(event) {
|
||||
if (event.target.className === "modal") {
|
||||
event.target.style.display = "none";
|
||||
if (event.target.classList.contains('modal')) {
|
||||
event.target.style.display = 'none';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// =================== ANIMATIONS ===================
|
||||
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 Confidentialité & Sécurité chargée !');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,27 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Statistiques du serveur</title>
|
||||
<title>Statistiques & Logs - Interface Admin</title>
|
||||
<link rel="icon" href="/public/assets/homelab_logo.png" />
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
|
||||
<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@10"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<style>
|
||||
body {
|
||||
background-image: url('<%= user.wallpaper %>');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
||||
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
@@ -68,172 +56,556 @@
|
||||
--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 {
|
||||
max-width: 1250px;
|
||||
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;
|
||||
}
|
||||
|
||||
.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%;
|
||||
max-width: 1000px;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
.admin-header {
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
color: white;
|
||||
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-header h1 {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.admin-content {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: hsl(var(--card));
|
||||
.section-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.section-title i {
|
||||
color: #6366f1;
|
||||
}
|
||||
|
||||
.stat-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: var(--radius);
|
||||
padding: 2rem;
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stat-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, #6366f1, #8b5cf6);
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stat-card-icon {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #6366f1;
|
||||
}
|
||||
|
||||
.stat-card-title {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: hsl(var(--muted-foreground));
|
||||
margin-bottom: 0.5rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.stat-card-value {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.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: #6366f1;
|
||||
}
|
||||
|
||||
.quick-action-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.quick-action-desc {
|
||||
font-size: 0.85rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius);
|
||||
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-color: hsl(var(--primary));
|
||||
color: hsl(var(--primary-foreground));
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
color: white;
|
||||
box-shadow: 0 4px 14px 0 rgba(99, 102, 241, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
opacity: 0.9;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px 0 rgba(99, 102, 241, 0.4);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: hsl(var(--secondary));
|
||||
background: hsl(var(--secondary));
|
||||
color: hsl(var(--secondary-foreground));
|
||||
border: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
opacity: 0.9;
|
||||
background: hsl(var(--accent));
|
||||
color: hsl(var(--accent-foreground));
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
#themeSwitcher {
|
||||
.btn-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.theme-switcher {
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
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);
|
||||
}
|
||||
|
||||
.animate {
|
||||
animation: fadeIn 0.5s ease-out;
|
||||
.theme-switcher:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
.theme-switcher svg {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: hsl(var(--background));
|
||||
margin: 15% auto;
|
||||
padding: 20px;
|
||||
background: hsl(var(--card));
|
||||
margin: 5% auto;
|
||||
padding: 0;
|
||||
border: 1px solid hsl(var(--border));
|
||||
width: 80%;
|
||||
border-radius: var(--radius);
|
||||
width: 90%;
|
||||
max-width: 900px;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 1.5rem 2rem;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
color: white;
|
||||
border-radius: 16px 16px 0 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.close {
|
||||
color: hsl(var(--muted-foreground));
|
||||
float: right;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
font-size: 2rem;
|
||||
font-weight: 300;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
line-height: 1;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.close:hover,
|
||||
.close:focus {
|
||||
.close:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 2rem;
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-body pre {
|
||||
background: hsl(var(--muted));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
overflow-x: auto;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.6;
|
||||
color: hsl(var(--foreground));
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.chart-container canvas {
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.admin-header {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.admin-header h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.admin-content {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.theme-switcher {
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
.stat-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
width: 95%;
|
||||
margin: 10% auto;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="animate">
|
||||
<div id="app" class="min-h-screen flex items-center justify-center">
|
||||
<div class="container mt-8">
|
||||
<h1 class="text-3xl font-semibold mb-6 text-center animate">Statistiques du serveur</h1>
|
||||
<body class="dark">
|
||||
<div class="backdrop"></div>
|
||||
|
||||
<div class="card mb-8">
|
||||
<h2 class="text-2xl font-semibold mb-4">Informations générales</h2>
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">Paramètre</th>
|
||||
<th class="text-left">Valeur</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Temps de fonctionnement</td>
|
||||
<td><%= Math.floor(uptime / 86400) %> jours, <%= Math.floor(uptime % 86400 / 3600) %> heures, <%= Math.floor(uptime % 3600 / 60) %> minutes, <%= uptime % 60 %> secondes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Utilisation de la mémoire</td>
|
||||
<td><%= memoryUsage.toFixed(2) %> Mo</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Utilisation du processeur</td>
|
||||
<td><%= (cpuUsage * 100).toFixed(2) %> %</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="container">
|
||||
<div class="admin-card">
|
||||
<div class="admin-header">
|
||||
<h1><i class="fas fa-chart-line"></i> Statistiques & Logs</h1>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2 class="text-2xl font-semibold mb-4">Journaux</h2>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<% logs && logs.forEach((log, index) => { %>
|
||||
<% if (log) { %>
|
||||
<button class="btn btn-primary logName" data-index="<%= index %>"><%= log.name %></button>
|
||||
<div id="myModal<%= index %>" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close" data-index="<%= index %>">×</span>
|
||||
<pre class="whitespace-pre-wrap"><%= log.content %></pre>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
<div class="admin-content">
|
||||
<!-- Statistiques Système -->
|
||||
<div class="section-title">
|
||||
<i class="fas fa-server"></i>
|
||||
Statistiques Système
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 flex justify-center">
|
||||
<a href="/dpanel/dashboard/admin/" class="btn btn-secondary w-full py-2 mt-4 text-center">
|
||||
<i class="fas fa-arrow-left mr-2"></i>
|
||||
Retourner au dashboard admin
|
||||
</a>
|
||||
<div class="stat-cards">
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-icon">
|
||||
<i class="fas fa-clock"></i>
|
||||
</div>
|
||||
<div class="stat-card-title">Uptime</div>
|
||||
<div class="stat-card-value" id="uptimeDisplay">
|
||||
<%= Math.floor(uptime / 86400) %>j <%= Math.floor(uptime % 86400 / 3600) %>h <%= Math.floor(uptime % 3600 / 60) %>m
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-icon">
|
||||
<i class="fas fa-memory"></i>
|
||||
</div>
|
||||
<div class="stat-card-title">Mémoire</div>
|
||||
<div class="stat-card-value">
|
||||
<%= memoryUsage.toFixed(2) %> Mo
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-card-icon">
|
||||
<i class="fas fa-microchip"></i>
|
||||
</div>
|
||||
<div class="stat-card-title">CPU</div>
|
||||
<div class="stat-card-value">
|
||||
<%= (cpuUsage * 100).toFixed(2) %>%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Graphiques -->
|
||||
<div class="chart-container">
|
||||
<canvas id="systemChart"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- Journaux -->
|
||||
<div class="section-title">
|
||||
<i class="fas fa-file-alt"></i>
|
||||
Journaux du Système
|
||||
</div>
|
||||
|
||||
<div class="quick-actions">
|
||||
<% if (logs && logs.length > 0) { %>
|
||||
<% logs.forEach((log, index) => { %>
|
||||
<% if (log) { %>
|
||||
<div class="quick-action" data-log-index="<%= index %>">
|
||||
<div class="quick-action-icon">
|
||||
<i class="fas fa-file-code"></i>
|
||||
</div>
|
||||
<div class="quick-action-title"><%= log.name %></div>
|
||||
<div class="quick-action-desc">Cliquer pour voir</div>
|
||||
</div>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
<% } else { %>
|
||||
<div style="grid-column: 1 / -1; text-align: center; padding: 2rem; color: hsl(var(--muted-foreground));">
|
||||
<i class="fas fa-info-circle" style="font-size: 3rem; margin-bottom: 1rem; opacity: 0.5;"></i>
|
||||
<p>Aucun log disponible pour le moment.</p>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<!-- Bouton retour -->
|
||||
<div style="margin-top: 2rem;">
|
||||
<a href="/dpanel/dashboard/admin/" class="btn btn-secondary btn-full">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
Retourner au dashboard admin
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="themeSwitcher" class="btn btn-secondary p-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<!-- Modals pour les logs -->
|
||||
<% if (logs && logs.length > 0) { %>
|
||||
<% logs.forEach((log, index) => { %>
|
||||
<% if (log) { %>
|
||||
<div id="logModal<%= index %>" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2><i class="fas fa-file-code"></i> <%= log.name %></h2>
|
||||
<span class="close" data-modal-index="<%= index %>">×</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<pre><%= log.content %></pre>
|
||||
</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');
|
||||
@@ -258,30 +630,127 @@
|
||||
}
|
||||
});
|
||||
|
||||
var logNames = document.getElementsByClassName("logName");
|
||||
var closeButtons = document.getElementsByClassName("close");
|
||||
// =================== GESTION DES MODALS ===================
|
||||
const logButtons = document.querySelectorAll('.quick-action[data-log-index]');
|
||||
const closeButtons = document.querySelectorAll('.close[data-modal-index]');
|
||||
|
||||
for (var i = 0; i < logNames.length; i++) {
|
||||
logNames[i].addEventListener("click", function(event) {
|
||||
var index = event.target.getAttribute("data-index");
|
||||
var modal = document.getElementById("myModal" + index);
|
||||
modal.style.display = "block";
|
||||
logButtons.forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const index = this.getAttribute('data-log-index');
|
||||
const modal = document.getElementById('logModal' + index);
|
||||
if (modal) {
|
||||
modal.style.display = 'block';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
for (var i = 0; i < closeButtons.length; i++) {
|
||||
closeButtons[i].addEventListener("click", function(event) {
|
||||
var index = event.target.getAttribute("data-index");
|
||||
var modal = document.getElementById("myModal" + index);
|
||||
modal.style.display = "none";
|
||||
closeButtons.forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const index = this.getAttribute('data-modal-index');
|
||||
const modal = document.getElementById('logModal' + index);
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
window.onclick = function(event) {
|
||||
if (event.target.className === "modal") {
|
||||
event.target.style.display = "none";
|
||||
if (event.target.classList.contains('modal')) {
|
||||
event.target.style.display = 'none';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// =================== GRAPHIQUE CHART.JS ===================
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const ctx = document.getElementById('systemChart');
|
||||
if (ctx) {
|
||||
const isDark = body.classList.contains('dark');
|
||||
const textColor = isDark ? 'rgba(210, 214, 220, 0.8)' : 'rgba(34, 41, 47, 0.8)';
|
||||
const gridColor = isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
|
||||
|
||||
new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['Uptime (jours)', 'Mémoire (Mo)', 'CPU (%)'],
|
||||
datasets: [{
|
||||
label: 'Statistiques Système',
|
||||
data: [
|
||||
<%= Math.floor(uptime / 86400) %>,
|
||||
<%= memoryUsage.toFixed(2) %>,
|
||||
<%= (cpuUsage * 100).toFixed(2) %>
|
||||
],
|
||||
backgroundColor: [
|
||||
'rgba(99, 102, 241, 0.6)',
|
||||
'rgba(139, 92, 246, 0.6)',
|
||||
'rgba(168, 85, 247, 0.6)'
|
||||
],
|
||||
borderColor: [
|
||||
'rgba(99, 102, 241, 1)',
|
||||
'rgba(139, 92, 246, 1)',
|
||||
'rgba(168, 85, 247, 1)'
|
||||
],
|
||||
borderWidth: 2,
|
||||
borderRadius: 8
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
labels: {
|
||||
color: textColor,
|
||||
font: {
|
||||
family: 'Inter',
|
||||
size: 12
|
||||
}
|
||||
}
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Aperçu des Ressources Système',
|
||||
color: textColor,
|
||||
font: {
|
||||
family: 'Inter',
|
||||
size: 16,
|
||||
weight: '600'
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
color: textColor
|
||||
},
|
||||
grid: {
|
||||
color: gridColor
|
||||
}
|
||||
},
|
||||
x: {
|
||||
ticks: {
|
||||
color: textColor
|
||||
},
|
||||
grid: {
|
||||
color: gridColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// =================== ANIMATIONS ===================
|
||||
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 Statistiques & Logs chargée !');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -592,12 +592,6 @@
|
||||
<div class="backdrop"></div>
|
||||
|
||||
<div class="container">
|
||||
<!-- Notice Interface en Test -->
|
||||
<div class="beta-notice">
|
||||
<i class="fas fa-flask"></i>
|
||||
<strong>Interface en cours de test</strong> - Cette nouvelle interface moderne est actuellement en phase de test.
|
||||
Selon les retours des utilisateurs, elle sera déployée sur toute l'application (gestion admin, téléversement, etc.).
|
||||
</div>
|
||||
|
||||
<div class="profile-card">
|
||||
<!-- Header du profil -->
|
||||
|
||||
Reference in New Issue
Block a user