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
808 lines
38 KiB
Plaintext
808 lines
38 KiB
Plaintext
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<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">
|
|
|
|
<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>
|
|
</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="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>
|
|
|
|
<footer class="footer mt-auto py-3 bg-light">
|
|
<div class="container">
|
|
<span class="text-muted">Version: <span id="version-number">...</span> | © <span id="current-year"></span> Myaxrin Labs</span>
|
|
<a href="#" class="float-right" onclick="displayMetadata()">Metadata</a>
|
|
</div>
|
|
</footer>
|
|
|
|
<script>
|
|
document.getElementById('current-year').textContent = new Date().getFullYear();
|
|
</script>
|
|
|
|
<div class="modal fade" id="metadataModal" tabindex="-1" role="dialog" aria-labelledby="metadataModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="metadataModalLabel">Metadata</h5>
|
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p><i class="fas fa-code-branch"></i> Version de Build: <span id="buildVersion"></span></p>
|
|
<p><i class="fab fa-node"></i> Version de Node.js: <span id="nodeVersion"></span></p>
|
|
<p><i class="fas fa-server"></i> Version de Express.js: <span id="expressVersion"></span></p>
|
|
<p><i class="fas fa-hashtag"></i> SHA de Build: <span id="buildSha"></span></p>
|
|
<p><i class="fas fa-windows"></i> Type d'OS: <span id="osType"></span></p>
|
|
<p><i class="fas fa-laptop-code"></i> Version d'OS: <span id="osRelease"></span></p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Fermer</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal fade" id="moveFileModal" tabindex="-1" role="dialog" aria-labelledby="moveFileModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Déplacer le fichier</h5>
|
|
<button type="button" class="close" data-dismiss="modal" aria-label="Fermer">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="moveFileForm">
|
|
<input type="hidden" id="moveFileName" name="fileName">
|
|
<input type="hidden" id="moveUserName" name="userName" value="<%= userName %>">
|
|
<input type="hidden" id="moveOldFolderName" name="oldFolderName" value="<%= folderName %>">
|
|
<select class="form-control" id="moveFolderSelect" name="newFolderName">
|
|
<option value="" disabled selected>Choisir un dossier...</option>
|
|
<option value="root">Dossier Racine</option>
|
|
<% allFolders.forEach(folder => { %>
|
|
<option value="<%= folder %>"><%= folder %></option>
|
|
<% }); %>
|
|
</select>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>
|
|
<button type="button" class="btn btn-primary" id="confirmMoveFile">Déplacer</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal fade" id="uploadModal" tabindex="-1" role="dialog" aria-labelledby="uploadModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Téléverser dans <%= currentFolder || 'ce dossier' %></h5>
|
|
<button type="button" class="close" data-dismiss="modal" aria-label="Fermer">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="folderUploadForm">
|
|
<div class="form-group">
|
|
<label for="uploadFileInput">Sélectionnez un fichier :</label>
|
|
<input type="file" class="form-control" id="uploadFileInput" name="file" required>
|
|
<small class="form-text text-muted">Taille maximale : 1 GB</small>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="uploadExpiryDate">Date d'expiration (optionnel) :</label>
|
|
<input type="date" class="form-control" id="uploadExpiryDate" name="expiryDate">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="uploadPassword">Mot de passe (optionnel) :</label>
|
|
<input type="password" class="form-control" id="uploadPassword" name="password" placeholder="Au moins 6 caractères">
|
|
</div>
|
|
<div class="progress" id="uploadProgress" style="display: none;">
|
|
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
|
|
<span class="sr-only">0% Complete</span>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>
|
|
<button type="button" class="btn btn-primary" id="confirmUpload">
|
|
<i class="fas fa-upload"></i> Téléverser
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
|
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
|
|
<script src="/public/js/folder.js"></script>
|
|
<script>
|
|
// Fonction pour formater la taille des fichiers
|
|
function formatFileSize(bytes) {
|
|
if (bytes === 0) return '0 octets';
|
|
const k = 1024;
|
|
const sizes = ['octets', 'Ko', 'Mo', 'Go', 'To'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
}
|
|
|
|
// Formater toutes les tailles de fichiers au chargement
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
document.querySelectorAll('.file-size').forEach(function(element) {
|
|
const size = parseInt(element.getAttribute('data-size'));
|
|
if (!isNaN(size)) {
|
|
element.textContent = formatFileSize(size);
|
|
}
|
|
});
|
|
|
|
// Gestion du menu contextuel
|
|
const contextMenu = document.getElementById('contextMenu');
|
|
let currentContextRow = null;
|
|
|
|
// Clic droit sur les lignes du tableau
|
|
document.querySelectorAll('#fileTable tbody tr').forEach(function(row) {
|
|
row.addEventListener('contextmenu', function(e) {
|
|
e.preventDefault();
|
|
currentContextRow = row;
|
|
|
|
const type = row.getAttribute('data-type');
|
|
const name = row.getAttribute('data-name');
|
|
const url = row.getAttribute('data-url');
|
|
|
|
// Positionner le menu
|
|
contextMenu.style.display = 'block';
|
|
contextMenu.style.left = e.pageX + 'px';
|
|
contextMenu.style.top = e.pageY + 'px';
|
|
|
|
// Configuration selon le type et les permissions
|
|
const openItem = contextMenu.querySelector('.context-item-open');
|
|
const moveItem = contextMenu.querySelector('.context-item-move');
|
|
const renameItem = contextMenu.querySelector('.context-item-rename');
|
|
const shareItem = contextMenu.querySelector('.context-item-share');
|
|
const deleteItem = contextMenu.querySelector('.context-item-delete');
|
|
const separator = contextMenu.querySelector('.menu-separator');
|
|
|
|
// Récupérer les données de collaboration depuis le serveur
|
|
// isCollaborativeFolder fait référence au dossier ACTUEL (celui dans lequel on navigue)
|
|
const isCollaborativeFolder = <%= typeof isCollaborativeFolder !== 'undefined' && isCollaborativeFolder ? 'true' : 'false' %>;
|
|
const isOwner = <%= typeof isOwner !== 'undefined' && isOwner ? 'true' : 'false' %>;
|
|
|
|
// Masquer tous les éléments par défaut
|
|
openItem.style.display = 'none';
|
|
moveItem.style.display = 'none';
|
|
renameItem.style.display = 'none';
|
|
shareItem.style.display = 'none';
|
|
deleteItem.style.display = 'none';
|
|
if (separator) separator.style.display = 'none';
|
|
|
|
if (type === 'folder') {
|
|
// Pour les sous-dossiers : uniquement "Ouvrir"
|
|
openItem.style.display = 'block';
|
|
openItem.href = url;
|
|
} else {
|
|
// Pour les fichiers
|
|
// Permissions basées sur si on est dans un dossier partagé
|
|
if (isCollaborativeFolder && !isOwner) {
|
|
// Invité dans un dossier partagé : peut seulement copier le lien
|
|
shareItem.style.display = 'block';
|
|
} else {
|
|
// Propriétaire ou dossier non partagé : tous les droits
|
|
renameItem.style.display = 'block';
|
|
shareItem.style.display = 'block';
|
|
moveItem.style.display = 'block';
|
|
if (separator) separator.style.display = 'block';
|
|
deleteItem.style.display = 'block';
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// Fermer le menu contextuel
|
|
document.addEventListener('click', function() {
|
|
contextMenu.style.display = 'none';
|
|
});
|
|
|
|
// Renommer
|
|
contextMenu.querySelector('.context-item-rename').addEventListener('click', async function() {
|
|
if (currentContextRow) {
|
|
const name = currentContextRow.getAttribute('data-name');
|
|
const type = currentContextRow.getAttribute('data-type');
|
|
if (type === 'file') {
|
|
const result = await Swal.fire({
|
|
title: 'Renommer le fichier',
|
|
input: 'text',
|
|
inputLabel: 'Nouveau nom',
|
|
inputValue: name,
|
|
showCancelButton: true,
|
|
confirmButtonText: 'Renommer',
|
|
cancelButtonText: 'Annuler',
|
|
inputValidator: (value) => {
|
|
if (!value) {
|
|
return 'Vous devez entrer un nom !';
|
|
}
|
|
}
|
|
});
|
|
|
|
if (result.isConfirmed && result.value !== name) {
|
|
const newName = result.value;
|
|
// Appel API pour renommer
|
|
fetch('/api/dpanel/dashboard/rename', {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
folderName: '<%= folderName %>',
|
|
oldName: name,
|
|
newName: newName
|
|
})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'Fichier renommé !',
|
|
text: 'Le fichier a été renommé avec succès'
|
|
}).then(() => location.reload());
|
|
} else {
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Erreur',
|
|
text: data.message || 'Une erreur est survenue'
|
|
});
|
|
}
|
|
})
|
|
.catch(error => {
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Erreur',
|
|
text: 'Une erreur est survenue lors du renommage'
|
|
});
|
|
});
|
|
}
|
|
}
|
|
}
|
|
contextMenu.style.display = 'none';
|
|
});
|
|
|
|
// Copier le lien
|
|
contextMenu.querySelector('.context-item-share').addEventListener('click', function() {
|
|
if (currentContextRow) {
|
|
const url = currentContextRow.getAttribute('data-url');
|
|
navigator.clipboard.writeText(url).then(function() {
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'Lien copié !',
|
|
text: 'Le lien a été copié dans le presse-papiers',
|
|
toast: true,
|
|
position: 'top-end',
|
|
showConfirmButton: false,
|
|
timer: 3000
|
|
});
|
|
});
|
|
}
|
|
contextMenu.style.display = 'none';
|
|
});
|
|
|
|
// Déplacer
|
|
contextMenu.querySelector('.context-item-move').addEventListener('click', function() {
|
|
if (currentContextRow) {
|
|
const name = currentContextRow.getAttribute('data-name');
|
|
document.getElementById('moveFileName').value = name;
|
|
$('#moveFileModal').modal('show');
|
|
}
|
|
contextMenu.style.display = 'none';
|
|
});
|
|
|
|
// Supprimer
|
|
contextMenu.querySelector('.context-item-delete').addEventListener('click', async function() {
|
|
if (currentContextRow) {
|
|
const name = currentContextRow.getAttribute('data-name');
|
|
const type = currentContextRow.getAttribute('data-type');
|
|
if (type === 'file') {
|
|
const result = await Swal.fire({
|
|
title: 'Êtes-vous sûr ?',
|
|
text: 'Cette action est irréversible !',
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#d33',
|
|
cancelButtonColor: '#3085d6',
|
|
confirmButtonText: 'Oui, supprimer !',
|
|
cancelButtonText: 'Annuler'
|
|
});
|
|
|
|
if (result.isConfirmed) {
|
|
fetch('/api/dpanel/dashboard/delete', {
|
|
method: 'DELETE',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
filename: name
|
|
})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'Fichier supprimé !',
|
|
text: 'Le fichier a été supprimé avec succès'
|
|
}).then(() => location.reload());
|
|
} else {
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Erreur',
|
|
text: data.message || 'Une erreur est survenue'
|
|
});
|
|
}
|
|
})
|
|
.catch(error => {
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Erreur',
|
|
text: 'Une erreur est survenue lors de la suppression'
|
|
});
|
|
});
|
|
}
|
|
}
|
|
}
|
|
contextMenu.style.display = 'none';
|
|
});
|
|
|
|
// Confirmation du déplacement
|
|
document.getElementById('confirmMoveFile').addEventListener('click', function() {
|
|
const fileName = document.getElementById('moveFileName').value;
|
|
const newFolderName = document.getElementById('moveFolderSelect').value;
|
|
const userName = document.getElementById('moveUserName').value;
|
|
const oldFolderName = document.getElementById('moveOldFolderName').value;
|
|
|
|
if (!newFolderName) {
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Erreur',
|
|
text: 'Veuillez sélectionner un dossier de destination'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Envoi de la requête de déplacement
|
|
fetch('/api/dpanel/dashboard/move', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
fileName: fileName,
|
|
userName: userName,
|
|
oldFolderName: oldFolderName,
|
|
newFolderName: newFolderName
|
|
})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'Fichier déplacé !',
|
|
text: 'Le fichier a été déplacé avec succès'
|
|
}).then(() => {
|
|
location.reload();
|
|
});
|
|
} else {
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Erreur',
|
|
text: data.message || 'Une erreur est survenue'
|
|
});
|
|
}
|
|
})
|
|
.catch(error => {
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Erreur',
|
|
text: 'Une erreur est survenue lors du déplacement'
|
|
});
|
|
});
|
|
|
|
$('#moveFileModal').modal('hide');
|
|
});
|
|
|
|
// Recherche
|
|
document.getElementById('searchButton').addEventListener('click', function() {
|
|
const searchValue = document.getElementById('searchInput').value.toLowerCase();
|
|
const rows = document.querySelectorAll('#fileTable tbody tr');
|
|
|
|
rows.forEach(function(row) {
|
|
const name = row.getAttribute('data-name').toLowerCase();
|
|
if (name.includes(searchValue)) {
|
|
row.style.display = '';
|
|
} else {
|
|
row.style.display = 'none';
|
|
}
|
|
});
|
|
});
|
|
|
|
// Recherche en temps réel
|
|
document.getElementById('searchInput').addEventListener('keyup', function() {
|
|
const searchValue = this.value.toLowerCase();
|
|
const rows = document.querySelectorAll('#fileTable tbody tr');
|
|
|
|
rows.forEach(function(row) {
|
|
const name = row.getAttribute('data-name').toLowerCase();
|
|
if (name.includes(searchValue)) {
|
|
row.style.display = '';
|
|
} else {
|
|
row.style.display = 'none';
|
|
}
|
|
});
|
|
});
|
|
|
|
// Dark mode synchronisé
|
|
const savedTheme = localStorage.getItem('theme') || 'light';
|
|
if (savedTheme === 'dark') {
|
|
document.documentElement.classList.add('dark');
|
|
document.body.classList.add('dark-mode');
|
|
} else {
|
|
document.documentElement.classList.remove('dark');
|
|
document.body.classList.remove('dark-mode');
|
|
}
|
|
|
|
// Double-clic pour ouvrir
|
|
document.querySelectorAll('#fileTable tbody tr').forEach(function(row) {
|
|
row.addEventListener('dblclick', function() {
|
|
const url = row.getAttribute('data-url');
|
|
const type = row.getAttribute('data-type');
|
|
|
|
if (type === 'folder') {
|
|
window.location.href = url;
|
|
} else {
|
|
window.open(url, '_blank');
|
|
}
|
|
});
|
|
});
|
|
|
|
// Gestion de l'upload dans le dossier
|
|
document.getElementById('uploadToFolderBtn').addEventListener('click', function() {
|
|
$('#uploadModal').modal('show');
|
|
});
|
|
|
|
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB chunks
|
|
|
|
async function generateSecurityCode() {
|
|
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
let code = '';
|
|
for (let i = 0; i < 6; i++) {
|
|
code += characters.charAt(Math.floor(Math.random() * characters.length));
|
|
}
|
|
return code;
|
|
}
|
|
|
|
async function formatSecureFileName(originalFileName) {
|
|
const now = new Date();
|
|
const date = now.toISOString().slice(0,10).replace(/-/g, '');
|
|
const securityCode = await generateSecurityCode();
|
|
|
|
const lastDot = originalFileName.lastIndexOf('.');
|
|
const fileName = lastDot !== -1 ? originalFileName.substring(0, lastDot) : originalFileName;
|
|
const fileExt = lastDot !== -1 ? originalFileName.substring(lastDot) : '';
|
|
|
|
return `${date}_${securityCode}_${fileName}${fileExt}`;
|
|
}
|
|
|
|
document.getElementById('confirmUpload').addEventListener('click', async function() {
|
|
const fileInput = document.getElementById('uploadFileInput');
|
|
const file = fileInput.files[0];
|
|
|
|
if (!file) {
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Erreur',
|
|
text: 'Veuillez sélectionner un fichier'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const MAX_FILE_SIZE = 1024 * 1024 * 1024; // 1GB
|
|
if (file.size > MAX_FILE_SIZE) {
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Fichier trop volumineux',
|
|
text: 'La taille maximale est de 1 GB'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const password = document.getElementById('uploadPassword').value;
|
|
if (password && password.length < 6) {
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Erreur',
|
|
text: 'Le mot de passe doit contenir au moins 6 caractères'
|
|
});
|
|
return;
|
|
}
|
|
|
|
const expiryDate = document.getElementById('uploadExpiryDate').value;
|
|
const currentFolder = '<%= currentFolder %>';
|
|
const isSharedFolder = <%= typeof isSharedFolder !== 'undefined' && isSharedFolder ? 'true' : 'false' %>;
|
|
const ownerName = '<%= typeof ownerName !== "undefined" ? ownerName : "" %>';
|
|
const secureFileName = await formatSecureFileName(file.name);
|
|
|
|
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
|
|
let uploadedChunks = 0;
|
|
|
|
const progressBar = document.querySelector('#uploadProgress .progress-bar');
|
|
document.getElementById('uploadProgress').style.display = 'block';
|
|
document.getElementById('confirmUpload').disabled = true;
|
|
|
|
try {
|
|
for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
|
|
const start = chunkIndex * CHUNK_SIZE;
|
|
const end = Math.min(start + CHUNK_SIZE, file.size);
|
|
const chunk = file.slice(start, end);
|
|
|
|
const formData = new FormData();
|
|
formData.append('file', chunk);
|
|
formData.append('chunkIndex', chunkIndex);
|
|
formData.append('totalChunks', totalChunks);
|
|
formData.append('filename', secureFileName);
|
|
formData.append('originalFilename', file.name);
|
|
formData.append('targetFolder', currentFolder);
|
|
|
|
// Pour les dossiers partagés
|
|
if (isSharedFolder && ownerName) {
|
|
formData.append('isSharedFolder', 'true');
|
|
formData.append('ownerName', ownerName);
|
|
}
|
|
|
|
if (expiryDate) formData.append('expiryDate', expiryDate);
|
|
if (password) formData.append('password', password);
|
|
|
|
const response = await fetch('/api/dpanel/upload', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Upload failed: ${response.statusText}`);
|
|
}
|
|
|
|
uploadedChunks++;
|
|
const progress = (uploadedChunks / totalChunks) * 100;
|
|
progressBar.style.width = progress + '%';
|
|
progressBar.textContent = Math.round(progress) + '%';
|
|
}
|
|
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'Fichier téléversé !',
|
|
text: 'Le fichier a été téléversé avec succès'
|
|
}).then(() => {
|
|
location.reload();
|
|
});
|
|
|
|
$('#uploadModal').modal('hide');
|
|
|
|
} catch (error) {
|
|
console.error('Upload error:', error);
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Erreur',
|
|
text: 'Une erreur est survenue lors du téléversement'
|
|
});
|
|
} finally {
|
|
document.getElementById('confirmUpload').disabled = false;
|
|
document.getElementById('uploadProgress').style.display = 'none';
|
|
progressBar.style.width = '0%';
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|