Files
CDN-APP-INSIDER/views/folder.ejs
Dinawo 2df1b28962
All checks were successful
continuous-integration/drone/push Build is passing
Update v1.2.0-beta - Dynamic context menu & permissions
 New Features:
- Dynamic permission-based context menus for files and folders
- Support for collaborative folder access control
- Upload to specific folders including shared folders
- Changelog modal for version updates
- Improved dark mode synchronization

🐛 Bug Fixes:
- Fixed context menu displaying incorrect options
- Fixed CSS !important override preventing dynamic menu behavior
- Fixed folder collaboration permission checks
- Fixed breadcrumb navigation with empty segments
- Fixed "Premature close" error loop in attachments
- Fixed missing user variable in admin routes
- Fixed avatar loading COEP policy issues

🔒 Security:
- Added security middleware (CSRF, rate limiting, input validation)
- Fixed collaboration folder access validation
- Improved shared folder permission handling

🎨 UI/UX Improvements:
- Removed Actions column from folder view
- Context menu now properly hides/shows based on permissions
- Better visual feedback for collaborative folders
- Improved upload flow with inline modals

🧹 Code Quality:
- Added collaboration data to folder routes
- Refactored context menu logic for better maintainability
- Added debug logging for troubleshooting
- Improved file upload handling with chunking support
2025-10-25 23:55:51 +02:00

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