Update v1.2.0-beta - Dynamic context menu & permissions
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:
2025-10-25 23:55:51 +02:00
parent 58b57fbb84
commit 2df1b28962
33 changed files with 6275 additions and 1462 deletions

View File

@@ -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">&times;</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">&times;</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>

View File

@@ -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> | &copy; <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">&times;</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">&copy; 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">&times;</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">&times;</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>

View File

@@ -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>

View File

@@ -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 %>">&times;</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 %>">&times;</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>

View File

@@ -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 %>">&times;</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 %>">&times;</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

View File

@@ -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 -->