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
1554 lines
60 KiB
JavaScript
1554 lines
60 KiB
JavaScript
// Dashboard JavaScript - Version corrigée
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Initialisation générale
|
|
initializeDashboard();
|
|
initializeContextMenu();
|
|
initializeDropdowns();
|
|
initializeGridView();
|
|
initializeCollaboration();
|
|
initializeFileHandlers();
|
|
});
|
|
|
|
// =================== INITIALISATION ===================
|
|
function initializeDashboard() {
|
|
// Formatage des tailles de fichiers
|
|
document.querySelectorAll('.file-size').forEach(function(element) {
|
|
const size = parseInt(element.getAttribute('data-size'));
|
|
element.textContent = formatFileSize(size);
|
|
});
|
|
|
|
// Événements de base
|
|
document.getElementById('searchButton')?.addEventListener('click', searchFiles);
|
|
document.getElementById('newFolderBtn')?.addEventListener('click', showNewFolderModal);
|
|
document.getElementById('themeSwitcher')?.addEventListener('click', toggleDarkMode);
|
|
|
|
// Thème
|
|
initTheme();
|
|
|
|
// Version
|
|
loadVersion();
|
|
|
|
// Double-clic sur les dossiers
|
|
document.querySelectorAll('tr[data-type="folder"] td:first-child, tr[data-type="shared-folder"] td:first-child').forEach(cell => {
|
|
cell.style.userSelect = 'none';
|
|
cell.addEventListener('dblclick', (e) => {
|
|
e.preventDefault();
|
|
const row = cell.closest('tr');
|
|
const url = row.dataset.url;
|
|
if (url) window.location.href = url;
|
|
});
|
|
});
|
|
}
|
|
|
|
// =================== MENU CONTEXTUEL ===================
|
|
function initializeContextMenu() {
|
|
const contextMenu = document.querySelector('.context-menu');
|
|
let selectedItem = null;
|
|
|
|
// Gestionnaire du clic droit
|
|
document.addEventListener('contextmenu', function(e) {
|
|
const row = e.target.closest('tr[data-type]');
|
|
if (!row) return;
|
|
|
|
e.preventDefault();
|
|
|
|
selectedItem = {
|
|
type: row.dataset.type,
|
|
name: row.dataset.name,
|
|
url: row.dataset.url,
|
|
element: row
|
|
};
|
|
|
|
adjustMenuOptions(selectedItem);
|
|
showContextMenu(e.pageX, e.pageY);
|
|
}); // Fermer le menu au clic extérieur
|
|
document.addEventListener('click', function(e) {
|
|
const contextMenu = document.getElementById('contextMenu');
|
|
if (!contextMenu?.contains(e.target)) {
|
|
hideContextMenu();
|
|
}
|
|
});// Actions du menu contextuel
|
|
document.addEventListener('click', function(e) {
|
|
if (!e.target.closest('#contextMenu')) return;
|
|
|
|
e.preventDefault();
|
|
const target = e.target.closest('button, a');
|
|
if (!target || !selectedItem) return;
|
|
|
|
let action = '';
|
|
if (target.classList.contains('context-item-open')) action = 'open';
|
|
else if (target.classList.contains('context-item-rename')) action = 'rename';
|
|
else if (target.classList.contains('context-item-collaborate')) action = 'collaborate';
|
|
else if (target.classList.contains('context-item-share')) action = 'copy-link';
|
|
else if (target.classList.contains('context-item-move')) action = 'move';
|
|
else if (target.classList.contains('context-item-leave')) action = 'leave';
|
|
else if (target.classList.contains('context-item-delete')) action = 'delete';
|
|
|
|
if (action) {
|
|
handleMenuAction(action, selectedItem);
|
|
hideContextMenu();
|
|
}
|
|
}); function adjustMenuOptions(item) {
|
|
const contextMenu = document.getElementById('contextMenu');
|
|
if (!contextMenu) return;
|
|
|
|
const isFile = item.type === 'file';
|
|
const isFolder = item.type === 'folder';
|
|
const isSharedFolder = item.type === 'shared-folder';
|
|
|
|
// DEBUG
|
|
console.log('🔍 Context Menu Debug:', {
|
|
itemType: item.type,
|
|
itemName: item.name,
|
|
isFile,
|
|
isFolder,
|
|
isSharedFolder
|
|
});
|
|
|
|
// Vérifier si l'utilisateur est propriétaire (pour dossiers partagés)
|
|
let isOwner = false;
|
|
if (isSharedFolder) {
|
|
const ownerCell = item.element.querySelector('td:nth-child(3) .text-muted');
|
|
isOwner = ownerCell && ownerCell.textContent.trim() === 'moi';
|
|
console.log('🔍 Shared Folder - isOwner:', isOwner);
|
|
}
|
|
|
|
// Récupérer tous les éléments du menu
|
|
const openBtn = contextMenu.querySelector('.context-item-open');
|
|
const renameBtn = contextMenu.querySelector('.context-item-rename');
|
|
const collaborateBtn = contextMenu.querySelector('.context-item-collaborate');
|
|
const shareBtn = contextMenu.querySelector('.context-item-share');
|
|
const moveBtn = contextMenu.querySelector('.context-item-move');
|
|
const leaveBtn = contextMenu.querySelector('.context-item-leave');
|
|
const separator = contextMenu.querySelector('.menu-separator');
|
|
const deleteBtn = contextMenu.querySelector('.context-item-delete');
|
|
|
|
// MASQUER TOUT PAR DÉFAUT
|
|
if (openBtn) openBtn.style.display = 'none';
|
|
if (renameBtn) renameBtn.style.display = 'none';
|
|
if (collaborateBtn) collaborateBtn.style.display = 'none';
|
|
if (shareBtn) shareBtn.style.display = 'none';
|
|
if (moveBtn) moveBtn.style.display = 'none';
|
|
if (leaveBtn) leaveBtn.style.display = 'none';
|
|
if (separator) separator.style.display = 'none';
|
|
if (deleteBtn) deleteBtn.style.display = 'none';
|
|
|
|
// AFFICHER SELON LE TYPE
|
|
if (isFile) {
|
|
// FICHIERS : Renommer, Copier le lien, Déplacer, Supprimer
|
|
console.log('✅ Affichage menu FICHIER');
|
|
if (renameBtn) renameBtn.style.display = 'flex';
|
|
if (shareBtn) shareBtn.style.display = 'flex';
|
|
if (moveBtn) moveBtn.style.display = 'flex';
|
|
if (separator) separator.style.display = 'block';
|
|
if (deleteBtn) deleteBtn.style.display = 'flex';
|
|
} else if (isFolder) {
|
|
// DOSSIERS PERSONNELS : Ouvrir, Renommer, Collaborer, Supprimer
|
|
console.log('✅ Affichage menu DOSSIER');
|
|
if (openBtn) openBtn.style.display = 'flex';
|
|
if (renameBtn) renameBtn.style.display = 'flex';
|
|
if (collaborateBtn) {
|
|
collaborateBtn.style.display = 'flex';
|
|
// Vérifier si déjà collaboratif
|
|
const collabBadge = item.element.querySelector('.collaboration-badge');
|
|
const isCollaborative = collabBadge !== null;
|
|
const span = collaborateBtn.querySelector('span');
|
|
if (span) {
|
|
span.textContent = isCollaborative ? 'Gérer la collaboration' : 'Activer la collaboration';
|
|
}
|
|
}
|
|
if (separator) separator.style.display = 'block';
|
|
if (deleteBtn) deleteBtn.style.display = 'flex';
|
|
} else if (isSharedFolder) {
|
|
// DOSSIERS PARTAGÉS
|
|
console.log('✅ Affichage menu DOSSIER PARTAGÉ (owner:', isOwner, ')');
|
|
if (openBtn) openBtn.style.display = 'flex';
|
|
|
|
if (isOwner) {
|
|
// PROPRIÉTAIRE : Renommer, Collaborer, Supprimer
|
|
if (renameBtn) renameBtn.style.display = 'flex';
|
|
if (collaborateBtn) {
|
|
collaborateBtn.style.display = 'flex';
|
|
const collabBadge = item.element.querySelector('.collaboration-badge');
|
|
const isCollaborative = collabBadge !== null;
|
|
const span = collaborateBtn.querySelector('span');
|
|
if (span) {
|
|
span.textContent = isCollaborative ? 'Gérer la collaboration' : 'Activer la collaboration';
|
|
}
|
|
}
|
|
if (separator) separator.style.display = 'block';
|
|
if (deleteBtn) deleteBtn.style.display = 'flex';
|
|
} else {
|
|
// INVITÉ : Quitter seulement
|
|
if (leaveBtn) leaveBtn.style.display = 'flex';
|
|
}
|
|
}
|
|
|
|
// DEBUG FINAL : afficher l'état de tous les boutons
|
|
console.log('📋 État final des boutons:', {
|
|
open: openBtn?.style.display,
|
|
rename: renameBtn?.style.display,
|
|
collaborate: collaborateBtn?.style.display,
|
|
share: shareBtn?.style.display,
|
|
move: moveBtn?.style.display,
|
|
leave: leaveBtn?.style.display,
|
|
delete: deleteBtn?.style.display
|
|
});
|
|
} function showContextMenu(x, y) {
|
|
const contextMenu = document.getElementById('contextMenu');
|
|
if (!contextMenu) return;
|
|
|
|
contextMenu.style.display = 'block';
|
|
|
|
// Ajuster la position
|
|
const menuRect = contextMenu.getBoundingClientRect();
|
|
const windowWidth = window.innerWidth;
|
|
const windowHeight = window.innerHeight;
|
|
|
|
if (x + menuRect.width > windowWidth) {
|
|
x = windowWidth - menuRect.width - 10;
|
|
}
|
|
if (y + menuRect.height > windowHeight) {
|
|
y = windowHeight - menuRect.height - 10;
|
|
}
|
|
|
|
contextMenu.style.left = `${x}px`;
|
|
contextMenu.style.top = `${y}px`;
|
|
}
|
|
|
|
function hideContextMenu() {
|
|
const contextMenu = document.getElementById('contextMenu');
|
|
if (contextMenu) {
|
|
contextMenu.style.display = 'none';
|
|
}
|
|
selectedItem = null;
|
|
}
|
|
|
|
function handleMenuAction(action, item) {
|
|
switch(action) {
|
|
case 'open':
|
|
if (item.type === 'folder') {
|
|
window.location.href = `/dpanel/dashboard/folder/${encodeURIComponent(item.name)}`;
|
|
} else if (item.type === 'shared-folder') {
|
|
window.location.href = item.url;
|
|
}
|
|
break;
|
|
|
|
case 'rename':
|
|
if (item.type === 'folder') {
|
|
renameFolder(item.name);
|
|
} else if (item.type === 'file') {
|
|
renameFile(item.name);
|
|
}
|
|
break;
|
|
|
|
case 'collaborate':
|
|
// Détecter si le dossier est déjà collaboratif en cherchant le badge
|
|
const collabBadge = item.element.querySelector('.collaboration-badge');
|
|
const isCollaborative = collabBadge !== null;
|
|
|
|
if (isCollaborative) {
|
|
// Si déjà collaboratif, afficher les détails
|
|
showCollaborationDetails(item.name, item.type);
|
|
} else {
|
|
// Sinon, activer la collaboration
|
|
toggleCollaboration(item.name, item.type, true);
|
|
}
|
|
break;
|
|
|
|
case 'copy-link':
|
|
if (item.url) {
|
|
navigator.clipboard.writeText(item.url).then(() => {
|
|
showToast('success', 'Lien copié');
|
|
});
|
|
}
|
|
break;
|
|
|
|
case 'move':
|
|
if (item.type === 'file') {
|
|
// Utiliser la modal Bootstrap pour le déplacement
|
|
showMoveFileBootstrapModal(item.name);
|
|
}
|
|
break;
|
|
|
|
case 'leave':
|
|
if (item.type === 'shared-folder') {
|
|
leaveSharedFolder(item.name, item.element);
|
|
}
|
|
break;
|
|
|
|
case 'delete':
|
|
if (item.type === 'folder') {
|
|
confirmDeleteFolder(item.name);
|
|
} else if (item.type === 'file') {
|
|
confirmDeleteFile(item.name);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// =================== DROPDOWNS ===================
|
|
function initializeDropdowns() {
|
|
// Gestionnaire pour les dropdowns Bootstrap
|
|
document.querySelectorAll('[data-bs-toggle="dropdown"]').forEach(toggle => {
|
|
toggle.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
// Fermer les autres dropdowns
|
|
document.querySelectorAll('.dropdown-menu.show').forEach(menu => {
|
|
if (menu !== this.nextElementSibling) {
|
|
menu.classList.remove('show');
|
|
}
|
|
});
|
|
|
|
// Toggle le dropdown actuel
|
|
const dropdown = this.nextElementSibling;
|
|
if (dropdown && dropdown.classList.contains('dropdown-menu')) {
|
|
// Correction du positionnement pour éviter le débordement en bas
|
|
dropdown.classList.toggle('show');
|
|
if (dropdown.classList.contains('show')) {
|
|
// Reset
|
|
dropdown.style.top = '';
|
|
dropdown.style.bottom = '';
|
|
dropdown.style.transform = '';
|
|
const rect = dropdown.getBoundingClientRect();
|
|
const windowHeight = window.innerHeight;
|
|
if (rect.bottom > windowHeight) {
|
|
// Afficher au-dessus si déborde
|
|
dropdown.style.top = 'auto';
|
|
dropdown.style.bottom = '100%';
|
|
dropdown.style.transform = 'translateY(-8px)';
|
|
} else {
|
|
dropdown.style.top = '';
|
|
dropdown.style.bottom = '';
|
|
dropdown.style.transform = '';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Gestion dynamique des options selon le type/propriétaire
|
|
const tr = this.closest('tr[data-type]');
|
|
if (tr && dropdown) {
|
|
const type = tr.getAttribute('data-type');
|
|
const owner = tr.querySelector('td:nth-child(3) .text-muted')?.textContent?.trim();
|
|
// Pour les dossiers partagés, vérifier si on est propriétaire
|
|
const isOwner = owner === 'moi';
|
|
dropdown.querySelectorAll('.dropdown-item').forEach(item => {
|
|
const action = item.textContent.trim();
|
|
// Déplacer : seulement pour les fichiers
|
|
if (action.includes('Déplacer')) {
|
|
item.style.display = (type === 'file') ? '' : 'none';
|
|
}
|
|
// Renommer/Supprimer : pas pour dossier partagé non propriétaire
|
|
if ((action.includes('Renommer') || action.includes('Supprimer')) && type === 'shared-folder' && !isOwner) {
|
|
item.style.display = 'none';
|
|
}
|
|
// Quitter : seulement pour dossier partagé non propriétaire
|
|
if (action.includes('Quitter ce dossier')) {
|
|
item.style.display = (type === 'shared-folder' && !isOwner) ? '' : 'none';
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
// Fermer les dropdowns au clic extérieur
|
|
document.addEventListener('click', function(e) {
|
|
if (!e.target.closest('.dropdown')) {
|
|
document.querySelectorAll('.dropdown-menu.show').forEach(menu => {
|
|
menu.classList.remove('show');
|
|
});
|
|
}
|
|
});
|
|
|
|
// Empêcher la fermeture du dropdown quand on clique à l'intérieur
|
|
document.querySelectorAll('.dropdown-menu').forEach(menu => {
|
|
menu.addEventListener('click', function(e) {
|
|
e.stopPropagation();
|
|
});
|
|
});
|
|
|
|
// Action pour quitter un dossier partagé
|
|
document.querySelectorAll('.leave-shared-folder-btn').forEach(btn => {
|
|
btn.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
const folderName = this.getAttribute('data-folder-name');
|
|
const folderOwner = this.getAttribute('data-folder-owner');
|
|
Swal.fire({
|
|
title: 'Quitter ce dossier partagé ?',
|
|
text: 'Vous ne verrez plus ce dossier dans votre dashboard.',
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonText: 'Quitter',
|
|
cancelButtonText: 'Annuler'
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
fetch(`/api/dpanel/sharedfolders/leave`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ folderName, folderOwner })
|
|
})
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showToast('success', 'Dossier quitté');
|
|
setTimeout(() => location.reload(), 800);
|
|
} else {
|
|
showToast('error', data.error || 'Erreur');
|
|
}
|
|
})
|
|
.catch(() => showToast('error', 'Erreur réseau'));
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
// =================== VUE GRILLE ===================
|
|
function initializeGridView() {
|
|
const tableBody = document.querySelector('#fileTable tbody');
|
|
const searchContainer = document.querySelector('.flex.justify-between.items-center');
|
|
|
|
if (!tableBody || !searchContainer) return;
|
|
|
|
// Créer le bouton de basculement
|
|
const viewToggleBtn = document.createElement('button');
|
|
viewToggleBtn.className = 'btn btn-secondary ml-2 view-toggle-btn';
|
|
viewToggleBtn.innerHTML = '<i class="fas fa-th-large"></i>';
|
|
viewToggleBtn.title = 'Basculer vers la vue grille';
|
|
searchContainer.appendChild(viewToggleBtn);
|
|
|
|
let isGridView = localStorage.getItem('isGridView') === 'true'; function buildGridLayout(tr) {
|
|
if (!tr) return;
|
|
|
|
const firstCell = tr.querySelector('td:first-child');
|
|
if (!firstCell) return;
|
|
|
|
const nameDiv = firstCell.querySelector('div');
|
|
if (!nameDiv) return;
|
|
|
|
const name = nameDiv.textContent.trim();
|
|
const icon = firstCell.querySelector('i')?.cloneNode(true);
|
|
const type = tr.querySelector('td:nth-child(2)')?.textContent.trim() || '';
|
|
const size = tr.querySelector('td:nth-child(4)')?.textContent.trim() || '';
|
|
const collab = firstCell.querySelector('.collaboration-badge');
|
|
|
|
if (!icon) return;
|
|
|
|
// Créer le conteneur principal
|
|
const content = document.createElement('div');
|
|
content.className = 'icon-container';
|
|
|
|
// Créer l'icône avec arrière-plan stylé
|
|
const iconWrapper = document.createElement('div');
|
|
iconWrapper.className = 'icon';
|
|
iconWrapper.appendChild(icon);
|
|
|
|
// Créer le label
|
|
const label = document.createElement('div');
|
|
label.className = 'label';
|
|
label.textContent = name;
|
|
|
|
// Créer les détails
|
|
const details = document.createElement('div');
|
|
details.className = 'details';
|
|
details.textContent = size !== '-' ? `${type} • ${size}` : type;
|
|
|
|
// Assembler le conteneur
|
|
content.appendChild(iconWrapper);
|
|
content.appendChild(label);
|
|
content.appendChild(details);
|
|
|
|
// Nettoyer et remplacer le contenu
|
|
firstCell.innerHTML = '';
|
|
firstCell.appendChild(content);
|
|
|
|
// Réajouter le badge de collaboration s'il existe
|
|
if (collab) {
|
|
const clonedCollab = collab.cloneNode(true);
|
|
firstCell.appendChild(clonedCollab);
|
|
}
|
|
|
|
// Ajouter le dropdown pour la vue grille
|
|
const dropdown = tr.querySelector('.dropdown');
|
|
if (dropdown) {
|
|
const gridDropdown = dropdown.cloneNode(true);
|
|
gridDropdown.className = 'grid-dropdown dropdown';
|
|
firstCell.appendChild(gridDropdown);
|
|
}
|
|
} function toggleView() {
|
|
const currentIsGrid = tableBody.classList.contains('grid-view');
|
|
isGridView = !currentIsGrid;
|
|
|
|
if (isGridView) {
|
|
// Passer à la vue grille
|
|
tableBody.classList.add('grid-view');
|
|
|
|
// Appliquer le layout grille avec un délai pour l'animation
|
|
setTimeout(() => {
|
|
document.querySelectorAll('#fileTable tbody tr').forEach((tr, index) => {
|
|
tr.style.animationDelay = `${index * 0.05}s`;
|
|
buildGridLayout(tr);
|
|
});
|
|
}, 50);
|
|
|
|
viewToggleBtn.innerHTML = '<i class="fas fa-list"></i>';
|
|
viewToggleBtn.title = 'Basculer vers la vue liste';
|
|
} else {
|
|
// Retourner à la vue liste
|
|
tableBody.classList.remove('grid-view');
|
|
|
|
// Rafraîchir pour restaurer la vue liste originale
|
|
setTimeout(() => location.reload(), 100);
|
|
}
|
|
|
|
localStorage.setItem('isGridView', isGridView);
|
|
}
|
|
|
|
viewToggleBtn.addEventListener('click', toggleView); // Initialiser la vue si nécessaire
|
|
if (isGridView) {
|
|
tableBody.classList.add('grid-view');
|
|
|
|
// Appliquer le layout avec animation délayée
|
|
setTimeout(() => {
|
|
document.querySelectorAll('#fileTable tbody tr').forEach((tr, index) => {
|
|
tr.style.animationDelay = `${index * 0.05}s`;
|
|
buildGridLayout(tr);
|
|
});
|
|
}, 100);
|
|
|
|
viewToggleBtn.innerHTML = '<i class="fas fa-list"></i>';
|
|
viewToggleBtn.title = 'Basculer vers la vue liste';
|
|
}
|
|
}
|
|
|
|
// =================== COLLABORATION ===================
|
|
function initializeCollaboration() {
|
|
// WebSocket pour la collaboration en temps réel
|
|
try {
|
|
const ws = new WebSocket(`ws://${window.location.host}`);
|
|
|
|
ws.onmessage = function(event) {
|
|
const data = JSON.parse(event.data);
|
|
if (data.type === 'collaborationStatus') {
|
|
updateCollaborationUI(data);
|
|
}
|
|
};
|
|
|
|
ws.onerror = function(error) {
|
|
console.warn('WebSocket error:', error);
|
|
};
|
|
} catch (error) {
|
|
console.warn('WebSocket not available:', error);
|
|
}
|
|
|
|
// Charger le statut initial
|
|
loadCollaborationStatus();
|
|
|
|
// Gestionnaires d'événements
|
|
document.querySelectorAll('.toggle-collaboration-btn').forEach(button => {
|
|
button.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
const itemName = this.getAttribute('data-item-name');
|
|
const itemType = this.getAttribute('data-item-type');
|
|
const isCollaborative = this.getAttribute('data-is-collaborative') === 'true';
|
|
|
|
if (isCollaborative) {
|
|
showCollaborationDetails(itemName, itemType);
|
|
} else {
|
|
toggleCollaboration(itemName, itemType, true);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
async function loadCollaborationStatus() {
|
|
try {
|
|
const response = await fetch('/api/dpanel/collaboration/status');
|
|
const data = await response.json();
|
|
|
|
if (data.items) {
|
|
Object.entries(data.items).forEach(([itemId, status]) => {
|
|
const [type, name] = itemId.split('-');
|
|
updateCollaborationUI({
|
|
itemName: name,
|
|
itemType: type,
|
|
isCollaborative: status.isCollaborative,
|
|
activeUsers: status.activeUsers
|
|
});
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading collaboration status:', error);
|
|
}
|
|
}
|
|
|
|
function toggleCollaboration(itemName, itemType, enable) {
|
|
const itemLabel = itemType === 'folder' ? 'dossier' : 'fichier';
|
|
|
|
const performToggle = () => {
|
|
fetch('/api/dpanel/collaboration/toggle', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ itemName, itemType, enable })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
updateCollaborationUI({
|
|
itemName,
|
|
itemType,
|
|
isCollaborative: enable,
|
|
activeUsers: data.activeUsers || []
|
|
});
|
|
showToast('success', `Collaboration ${enable ? 'activée' : 'désactivée'}`);
|
|
} else {
|
|
throw new Error(data.error || 'Erreur inconnue');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
showToast('error', 'Erreur lors de la modification de la collaboration');
|
|
});
|
|
};
|
|
|
|
if (!enable) {
|
|
performToggle();
|
|
} else {
|
|
Swal.fire({
|
|
title: 'Activer la collaboration ?',
|
|
text: `D'autres utilisateurs pourront accéder à ce ${itemLabel} en temps réel.`,
|
|
icon: 'question',
|
|
showCancelButton: true,
|
|
confirmButtonText: 'Activer',
|
|
cancelButtonText: 'Annuler'
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
performToggle();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function showCollaborationDetails(itemName, itemType) {
|
|
fetch(`/api/dpanel/collaboration/details/${itemType}/${itemName}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
// Supprimer toute modal existante
|
|
document.querySelectorAll('.collaboration-modal').forEach(modal => modal.remove());
|
|
|
|
const modal = createCollaborationModal(itemName, itemType, data);
|
|
document.body.appendChild(modal);
|
|
|
|
// Afficher la modal avec les classes CSS modernes
|
|
modal.classList.add('show');
|
|
modal.style.display = 'flex';
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
showToast('error', 'Impossible de charger les détails de la collaboration');
|
|
});
|
|
}
|
|
|
|
function createCollaborationModal(itemName, itemType, data) {
|
|
const modal = document.createElement('div');
|
|
modal.className = 'collaboration-modal';
|
|
modal.style.display = 'flex';
|
|
modal.innerHTML = `
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<div class="modal-title-wrapper">
|
|
<i class="fas fa-users modal-icon"></i>
|
|
<h5 class="modal-title">Collaboration - ${itemName}</h5>
|
|
</div>
|
|
<button type="button" class="close" onclick="this.closest('.collaboration-modal').remove()">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="collaboration-section">
|
|
<div class="collaboration-section-header">
|
|
<div class="section-title-group">
|
|
<i class="fas fa-user-friends section-icon"></i>
|
|
<h6 class="collaboration-section-title">Collaborateurs</h6>
|
|
</div>
|
|
<span class="collaboration-count-badge">${data.activeUsers ? data.activeUsers.length : 0}</span>
|
|
</div>
|
|
<div class="collaboration-users-list">
|
|
${data.activeUsers && data.activeUsers.length > 0
|
|
? data.activeUsers.map(user => `
|
|
<div class="collaboration-user-item">
|
|
<div class="user-avatar-wrapper">
|
|
<img src="${user.profilePicture || getDefaultAvatar(user.name)}"
|
|
alt="${user.name}"
|
|
class="user-avatar"
|
|
onerror="this.src='${getDefaultAvatar(user.name)}'">
|
|
<div class="user-status-indicator"></div>
|
|
</div>
|
|
<div class="user-details">
|
|
<div class="user-name">${user.name}</div>
|
|
<div class="user-role">
|
|
<i class="fas fa-shield-alt"></i>
|
|
Collaborateur
|
|
</div>
|
|
</div>
|
|
<div class="user-actions">
|
|
<button class="action-btn remove-btn"
|
|
onclick="removeCollaborator('${itemName}', '${itemType}', '${user.id}')"
|
|
title="Retirer ce collaborateur">
|
|
<i class="fas fa-user-minus"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`).join('')
|
|
: `<div class="collaboration-empty-state">
|
|
<i class="fas fa-user-plus empty-icon"></i>
|
|
<p>Aucun collaborateur actif</p>
|
|
<span>Ajoutez des utilisateurs pour commencer à collaborer</span>
|
|
</div>`
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="collaboration-section">
|
|
<div class="collaboration-section-header">
|
|
<div class="section-title-group">
|
|
<i class="fas fa-user-plus section-icon"></i>
|
|
<h6 class="collaboration-section-title">Ajouter un collaborateur</h6>
|
|
</div>
|
|
</div>
|
|
<div class="collaboration-search-container">
|
|
<div class="search-input-group">
|
|
<div class="search-input-wrapper">
|
|
<i class="fas fa-search search-icon"></i>
|
|
<input type="text" class="search-input search-user-input"
|
|
placeholder="Nom d'utilisateur...">
|
|
</div>
|
|
<button class="search-btn search-user-btn" type="button">
|
|
<i class="fas fa-search"></i>
|
|
<span>Rechercher</span>
|
|
</button>
|
|
</div>
|
|
<div class="search-results"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-danger"
|
|
onclick="toggleCollaboration('${itemName}', '${itemType}', false); this.closest('.collaboration-modal').remove();"
|
|
title="Désactiver complètement la collaboration">
|
|
<i class="fas fa-ban"></i>
|
|
<span>Désactiver collaboration</span>
|
|
</button>
|
|
<button type="button" class="btn btn-secondary"
|
|
onclick="this.closest('.collaboration-modal').remove()"
|
|
title="Fermer cette fenêtre">
|
|
<i class="fas fa-times"></i>
|
|
<span>Fermer</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Gestionnaire pour fermer en cliquant à l'extérieur
|
|
modal.addEventListener('click', (e) => {
|
|
if (e.target === modal) {
|
|
modal.remove();
|
|
}
|
|
});
|
|
|
|
// Ajouter les gestionnaires d'événements
|
|
const searchInput = modal.querySelector('.search-user-input');
|
|
const searchBtn = modal.querySelector('.search-user-btn');
|
|
|
|
const search = () => searchCollabUser(searchInput.value.trim(), itemName, itemType, modal);
|
|
|
|
searchBtn.addEventListener('click', search);
|
|
searchInput.addEventListener('keypress', (e) => {
|
|
if (e.key === 'Enter') search();
|
|
});
|
|
|
|
return modal;
|
|
}
|
|
|
|
function searchCollabUser(username, itemName, itemType, modal) {
|
|
if (!username) return;
|
|
|
|
const resultsDiv = modal.querySelector('.search-results');
|
|
resultsDiv.innerHTML = `
|
|
<div class="search-loading">
|
|
<i class="fas fa-spinner fa-spin"></i>
|
|
<span>Recherche en cours...</span>
|
|
</div>
|
|
`;
|
|
|
|
fetch(`/api/dpanel/collaboration/searchuser?username=${encodeURIComponent(username)}`)
|
|
.then(response => response.json())
|
|
.then(result => {
|
|
if (result.found) {
|
|
resultsDiv.innerHTML = `
|
|
<div class="search-result-item">
|
|
<div class="result-user-info">
|
|
<div class="result-avatar-wrapper">
|
|
<img src="${result.user.profilePicture || getDefaultAvatar(result.user.name)}"
|
|
alt="${result.user.name}"
|
|
class="result-avatar"
|
|
onerror="this.src='${getDefaultAvatar(result.user.name)}'">
|
|
</div>
|
|
<div class="result-details">
|
|
<div class="result-name">${result.user.name}</div>
|
|
<div class="result-status">
|
|
<i class="fas fa-user-check"></i>
|
|
Utilisateur trouvé
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button class="btn btn-primary add-user-btn"
|
|
onclick="addCollaborator('${itemName}', '${itemType}', '${result.user.id}')"
|
|
title="Ajouter ce collaborateur">
|
|
<i class="fas fa-user-plus"></i>
|
|
<span>Ajouter</span>
|
|
</button>
|
|
</div>
|
|
`;
|
|
} else {
|
|
resultsDiv.innerHTML = `
|
|
<div class="search-no-result">
|
|
<i class="fas fa-user-slash"></i>
|
|
<span>Utilisateur non trouvé</span>
|
|
</div>
|
|
`;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
resultsDiv.innerHTML = `
|
|
<div class="search-error">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
<span>Erreur lors de la recherche</span>
|
|
</div>
|
|
`;
|
|
});
|
|
}
|
|
|
|
function addCollaborator(itemName, itemType, userId) {
|
|
fetch('/api/dpanel/collaboration/add', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ itemName, itemType, userId })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showToast('success', 'Collaborateur ajouté');
|
|
// Rafraîchir la modal
|
|
document.querySelector('.collaboration-modal')?.remove();
|
|
showCollaborationDetails(itemName, itemType);
|
|
} else {
|
|
throw new Error(data.error || 'Erreur lors de l\'ajout');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
showToast('error', 'Erreur lors de l\'ajout du collaborateur');
|
|
});
|
|
}
|
|
|
|
function removeCollaborator(itemName, itemType, userId) {
|
|
fetch('/api/dpanel/collaboration/remove', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ itemName, itemType, userId })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showToast('success', 'Collaborateur retiré');
|
|
// Rafraîchir la modal
|
|
document.querySelector('.collaboration-modal')?.remove();
|
|
showCollaborationDetails(itemName, itemType);
|
|
} else {
|
|
throw new Error(data.error || 'Erreur lors du retrait');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
showToast('error', 'Erreur lors du retrait du collaborateur');
|
|
});
|
|
}
|
|
|
|
function updateCollaborationUI(data) {
|
|
const row = document.querySelector(`tr[data-name="${data.itemName}"]`);
|
|
if (!row) return;
|
|
|
|
const button = row.querySelector('.toggle-collaboration-btn');
|
|
const nameCell = row.querySelector('td:first-child');
|
|
|
|
if (button) {
|
|
button.setAttribute('data-is-collaborative', data.isCollaborative);
|
|
button.title = data.isCollaborative ? 'Voir les collaborateurs' : 'Activer la collaboration';
|
|
}
|
|
|
|
let badge = nameCell.querySelector('.collaboration-badge');
|
|
if (data.isCollaborative) {
|
|
if (!badge) {
|
|
badge = document.createElement('span');
|
|
badge.className = 'ml-2 badge badge-info collaboration-badge';
|
|
badge.innerHTML = '<i class="fas fa-users"></i> <span class="active-users-count"></span>';
|
|
nameCell.querySelector('div').appendChild(badge);
|
|
}
|
|
|
|
const countSpan = badge.querySelector('.active-users-count');
|
|
if (data.activeUsers && data.activeUsers.length > 0) {
|
|
badge.classList.remove('badge-secondary');
|
|
badge.classList.add('badge-info');
|
|
countSpan.textContent = ` ${data.activeUsers.length}`;
|
|
badge.title = `Collaborateurs actifs : ${data.activeUsers.map(u => u.name).join(', ')}`;
|
|
} else {
|
|
badge.classList.remove('badge-info');
|
|
badge.classList.add('badge-secondary');
|
|
countSpan.textContent = '';
|
|
badge.title = 'Aucun collaborateur actif';
|
|
}
|
|
} else if (badge) {
|
|
badge.remove();
|
|
}
|
|
}
|
|
|
|
// =================== GESTIONNAIRES DE FICHIERS ===================
|
|
function initializeFileHandlers() {
|
|
// Boutons de suppression
|
|
document.querySelectorAll('.delete-file-button, .delete-folder-btn').forEach(button => {
|
|
button.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
const fileName = this.dataset.fileName;
|
|
const folderName = this.dataset.folderName;
|
|
|
|
if (fileName) {
|
|
confirmDeleteFile(fileName);
|
|
} else if (folderName) {
|
|
confirmDeleteFolder(folderName);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Boutons de copie
|
|
document.querySelectorAll('.copy-button').forEach(button => {
|
|
button.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
const fileUrl = this.dataset.fileUrl;
|
|
copyFileLink(fileUrl);
|
|
});
|
|
});
|
|
|
|
// Boutons de renommage
|
|
document.querySelectorAll('.rename-file-btn, .rename-folder-btn').forEach(button => {
|
|
button.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
const fileName = this.dataset.fileName;
|
|
const folderName = this.dataset.folderName;
|
|
|
|
if (fileName) {
|
|
renameFile(fileName);
|
|
} else if (folderName) {
|
|
renameFolder(folderName);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Boutons de déplacement
|
|
document.querySelectorAll('.move-file-btn').forEach(button => {
|
|
button.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
const fileName = this.dataset.fileName;
|
|
// Utiliser la modal Bootstrap pour le déplacement
|
|
showMoveFileBootstrapModal(fileName);
|
|
});
|
|
}); // Gestionnaire pour le bouton confirmMoveFile dans la modal Bootstrap
|
|
const confirmMoveFileBtn = document.getElementById('confirmMoveFile');
|
|
if (confirmMoveFileBtn) {
|
|
confirmMoveFileBtn.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
|
|
const fileNameElement = document.getElementById('moveFileName');
|
|
const folderSelectElement = document.getElementById('moveFolderSelect');
|
|
|
|
console.log('Elements found:', {
|
|
fileNameElement: !!fileNameElement,
|
|
folderSelectElement: !!folderSelectElement,
|
|
fileNameElementType: fileNameElement?.tagName,
|
|
folderSelectElementType: folderSelectElement?.tagName
|
|
});
|
|
|
|
if (!fileNameElement) {
|
|
showToast('error', 'Élément fileName non trouvé');
|
|
return;
|
|
}
|
|
|
|
if (!folderSelectElement) {
|
|
showToast('error', 'Élément folderSelect non trouvé');
|
|
return;
|
|
}
|
|
// Récupérer les valeurs brutes
|
|
const rawFileName = fileNameElement.value;
|
|
const rawFolderName = folderSelectElement.value;
|
|
|
|
console.log('Raw values:', {
|
|
rawFileName: rawFileName,
|
|
rawFolderName: rawFolderName,
|
|
rawFileNameType: typeof rawFileName,
|
|
rawFolderNameType: typeof rawFolderName,
|
|
fileNameIsString: typeof rawFileName === 'string',
|
|
folderNameIsString: typeof rawFolderName === 'string'
|
|
});
|
|
|
|
// Convertir explicitement en string et nettoyer
|
|
// Utiliser une méthode plus robuste de conversion
|
|
let fileName, folderName;
|
|
|
|
try {
|
|
fileName = (rawFileName + '').trim(); // Force conversion to string
|
|
folderName = (rawFolderName + '').trim();
|
|
} catch (e) {
|
|
console.error('Error converting values:', e);
|
|
fileName = String(rawFileName || '').trim();
|
|
folderName = String(rawFolderName || '').trim();
|
|
}
|
|
|
|
console.log('Processed values:', {
|
|
fileName: fileName,
|
|
folderName: folderName,
|
|
fileNameType: typeof fileName,
|
|
folderNameType: typeof folderName,
|
|
fileNameLength: fileName.length,
|
|
folderNameLength: folderName.length,
|
|
fileNameConstructor: fileName.constructor.name,
|
|
folderNameConstructor: folderName.constructor.name
|
|
});
|
|
|
|
if (!fileName || fileName === '') {
|
|
showToast('error', 'Aucun fichier sélectionné');
|
|
return;
|
|
}
|
|
|
|
if (!folderName || folderName === '') {
|
|
showToast('error', 'Veuillez sélectionner un dossier');
|
|
return;
|
|
}
|
|
|
|
// Fermer la modal
|
|
$('#moveFileModal').modal('hide');
|
|
|
|
// Appeler la fonction moveFile
|
|
console.log('Calling moveFile with:', { fileName, folderName });
|
|
moveFile(fileName, folderName);
|
|
});
|
|
}
|
|
}
|
|
|
|
// =================== FONCTIONS UTILITAIRES ===================
|
|
function formatFileSize(fileSizeInBytes) {
|
|
if (!fileSizeInBytes || fileSizeInBytes === 0) return '0 octets';
|
|
if (fileSizeInBytes < 1024) return fileSizeInBytes + ' octets';
|
|
else if (fileSizeInBytes < 1048576) return (fileSizeInBytes / 1024).toFixed(2) + ' Ko';
|
|
else if (fileSizeInBytes < 1073741824) return (fileSizeInBytes / 1048576).toFixed(2) + ' Mo';
|
|
else return (fileSizeInBytes / 1073741824).toFixed(2) + ' Go';
|
|
}
|
|
|
|
function getDefaultAvatar(username) {
|
|
const encodedName = encodeURIComponent(username);
|
|
return `https://api.dicebear.com/7.x/initials/svg?seed=${encodedName}&background=%234e54c8&radius=50`;
|
|
}
|
|
|
|
function showToast(type, message) {
|
|
Swal.fire({
|
|
position: 'top-end',
|
|
icon: type,
|
|
title: message,
|
|
showConfirmButton: false,
|
|
timer: 1500,
|
|
toast: true
|
|
});
|
|
}
|
|
|
|
function searchFiles() {
|
|
const input = document.getElementById('searchInput');
|
|
const filter = input.value.toUpperCase();
|
|
const table = document.getElementById('fileTable');
|
|
const tr = table.getElementsByTagName('tr');
|
|
|
|
for (let i = 1; i < tr.length; i++) {
|
|
const td = tr[i].getElementsByTagName('td')[0];
|
|
if (td) {
|
|
const txtValue = td.textContent || td.innerText;
|
|
if (txtValue.toUpperCase().indexOf(filter) > -1) {
|
|
tr[i].style.display = "";
|
|
} else {
|
|
tr[i].style.display = "none";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// =================== MODALES ET ACTIONS ===================
|
|
function showNewFolderModal() {
|
|
Swal.fire({
|
|
title: 'Nouveau dossier',
|
|
input: 'text',
|
|
inputPlaceholder: 'Entrer le nom du nouveau dossier',
|
|
confirmButtonText: 'Créer',
|
|
showCancelButton: true,
|
|
cancelButtonText: 'Annuler',
|
|
preConfirm: (folderName) => {
|
|
if (!folderName) {
|
|
Swal.showValidationMessage('Le nom du dossier ne peut pas être vide.');
|
|
}
|
|
return folderName;
|
|
}
|
|
}).then(result => {
|
|
if (result.isConfirmed) {
|
|
createNewFolder(result.value);
|
|
}
|
|
});
|
|
}
|
|
|
|
function createNewFolder(folderName) {
|
|
fetch('/api/dpanel/dashboard/newfolder', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ folderName })
|
|
})
|
|
.then(async response => {
|
|
const result = await response.json();
|
|
if (response.ok && (result.success || result.message === 'Folder created successfully.')) {
|
|
showToast('success', 'Dossier créé avec succès');
|
|
setTimeout(() => location.reload(), 1500);
|
|
} else {
|
|
throw new Error(result.message || 'Erreur lors de la création');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
showToast('error', error.message || 'Erreur lors de la création du dossier');
|
|
});
|
|
}
|
|
|
|
function confirmDeleteFolder(folderName) {
|
|
Swal.fire({
|
|
title: 'Êtes-vous sûr?',
|
|
text: `La suppression du dossier "${folderName}" est irréversible!`,
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#d33',
|
|
cancelButtonColor: '#3085d6',
|
|
confirmButtonText: 'Supprimer',
|
|
cancelButtonText: 'Annuler',
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
deleteFolder(folderName);
|
|
}
|
|
});
|
|
}
|
|
|
|
function deleteFolder(folderName) {
|
|
fetch(`/api/dpanel/dashboard/deletefolder/${folderName}`, {
|
|
method: 'DELETE'
|
|
})
|
|
.then(response => {
|
|
if (response.ok) {
|
|
showToast('success', 'Dossier supprimé avec succès');
|
|
setTimeout(() => location.reload(), 1500);
|
|
} else {
|
|
throw new Error('La suppression du dossier a échoué');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
showToast('error', 'Erreur lors de la suppression du dossier');
|
|
});
|
|
}
|
|
|
|
function confirmDeleteFile(filename) {
|
|
Swal.fire({
|
|
title: 'Êtes-vous sûr de vouloir supprimer ce fichier?',
|
|
text: 'Cette action est irréversible!',
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#d33',
|
|
cancelButtonColor: '#3085d6',
|
|
confirmButtonText: 'Supprimer'
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
deleteFile(filename);
|
|
}
|
|
});
|
|
}
|
|
|
|
function deleteFile(filename) {
|
|
fetch('/api/dpanel/dashboard/delete', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ filename: filename })
|
|
})
|
|
.then(response => {
|
|
if (response.ok) {
|
|
showToast('success', 'Fichier supprimé avec succès');
|
|
setTimeout(() => location.reload(), 1500);
|
|
} else {
|
|
throw new Error('La suppression du fichier a échoué');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
showToast('error', 'Erreur lors de la suppression du fichier');
|
|
});
|
|
}
|
|
|
|
function copyFileLink(fileUrl) {
|
|
navigator.clipboard.writeText(fileUrl).then(() => {
|
|
showToast('success', 'Lien copié !');
|
|
}, (err) => {
|
|
console.error('Erreur lors de la copie: ', err);
|
|
showToast('error', 'Erreur lors de la copie');
|
|
});
|
|
}
|
|
|
|
function renameFile(currentName) {
|
|
Swal.fire({
|
|
title: 'Renommer le fichier',
|
|
input: 'text',
|
|
inputValue: currentName,
|
|
inputPlaceholder: 'Nouveau nom',
|
|
showCancelButton: true,
|
|
confirmButtonText: 'Renommer',
|
|
cancelButtonText: 'Annuler',
|
|
inputValidator: (value) => {
|
|
if (!value) {
|
|
return 'Vous devez entrer un nom de fichier';
|
|
}
|
|
}
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
const newName = result.value;
|
|
fetch(`/api/dpanel/dashboard/rename/`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ currentName: currentName, newName: newName })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success || response.ok) {
|
|
showToast('success', 'Fichier renommé avec succès');
|
|
setTimeout(() => location.reload(), 1500);
|
|
} else {
|
|
throw new Error(data.message || 'Erreur lors du renommage');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
showToast('error', 'Erreur lors du renommage du fichier');
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function renameFolder(currentName) {
|
|
Swal.fire({
|
|
title: 'Renommer le dossier',
|
|
input: 'text',
|
|
inputValue: currentName,
|
|
showCancelButton: true,
|
|
confirmButtonText: 'Renommer',
|
|
cancelButtonText: 'Annuler',
|
|
inputValidator: (value) => {
|
|
if (!value) {
|
|
return 'Veuillez entrer un nom de dossier';
|
|
}
|
|
}
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
const newName = result.value;
|
|
fetch('/api/dpanel/folders/rename', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ oldName: currentName, newName: newName })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showToast('success', 'Dossier renommé avec succès');
|
|
setTimeout(() => location.reload(), 1500);
|
|
} else {
|
|
throw new Error(data.message || 'Erreur lors du renommage');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
showToast('error', 'Erreur lors du renommage du dossier');
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function showMoveFileModal(fileName) {
|
|
const folders = Array.from(document.querySelectorAll('tr[data-type="folder"]'))
|
|
.map(folderRow => ({
|
|
name: folderRow.dataset.name,
|
|
value: folderRow.dataset.name
|
|
}));
|
|
|
|
Swal.fire({
|
|
title: 'Déplacer le fichier',
|
|
html: `
|
|
<select id="moveFolderSelect" class="form-control">
|
|
<option value="">Choisir un dossier...</option>
|
|
${folders.map(folder => `
|
|
<option value="${folder.value}">${folder.name}</option>
|
|
`).join('')}
|
|
</select>
|
|
`,
|
|
showCancelButton: true,
|
|
confirmButtonText: 'Déplacer',
|
|
cancelButtonText: 'Annuler',
|
|
preConfirm: () => {
|
|
const select = document.getElementById('moveFolderSelect');
|
|
const folderName = select.value;
|
|
if (!folderName) {
|
|
Swal.showValidationMessage('Veuillez sélectionner un dossier');
|
|
return false;
|
|
}
|
|
return folderName;
|
|
}
|
|
}).then((result) => {
|
|
if (result.isConfirmed && result.value) {
|
|
moveFile(fileName, result.value);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Fonction alternative utilisant la modal Bootstrap
|
|
function showMoveFileBootstrapModal(fileName) {
|
|
// S'assurer que fileName est une chaîne de caractères
|
|
const fileNameStr = String(fileName).trim();
|
|
|
|
console.log('showMoveFileBootstrapModal called with:', {
|
|
original: fileName,
|
|
converted: fileNameStr,
|
|
type: typeof fileName
|
|
});
|
|
|
|
if (!fileNameStr) {
|
|
showToast('error', 'Nom de fichier invalide');
|
|
return;
|
|
}
|
|
|
|
// Définir le nom du fichier dans le champ caché
|
|
const moveFileNameElement = document.getElementById('moveFileName');
|
|
if (moveFileNameElement) {
|
|
moveFileNameElement.value = fileNameStr;
|
|
console.log('Set moveFileName value to:', moveFileNameElement.value);
|
|
} else {
|
|
console.error('Element moveFileName not found');
|
|
showToast('error', 'Erreur: Élément de formulaire manquant');
|
|
return;
|
|
}
|
|
|
|
// Afficher la modal Bootstrap
|
|
$('#moveFileModal').modal('show');
|
|
}
|
|
|
|
function moveFile(fileName, folderName) {
|
|
// S'assurer que les paramètres sont des chaînes de caractères
|
|
const fileNameStr = String(fileName).trim();
|
|
const folderNameStr = String(folderName).trim();
|
|
|
|
console.log('moveFile called with:', {
|
|
original: { fileName, folderName },
|
|
converted: { fileNameStr, folderNameStr },
|
|
types: {
|
|
originalFileName: typeof fileName,
|
|
originalFolderName: typeof folderName,
|
|
convertedFileName: typeof fileNameStr,
|
|
convertedFolderName: typeof folderNameStr
|
|
}
|
|
});
|
|
|
|
if (!fileNameStr) {
|
|
showToast('error', 'Nom de fichier invalide');
|
|
return;
|
|
}
|
|
|
|
if (!folderNameStr) {
|
|
showToast('error', 'Nom de dossier invalide');
|
|
return;
|
|
}
|
|
// Créer explicitement l'objet à envoyer
|
|
const requestBody = {};
|
|
requestBody.fileName = fileNameStr;
|
|
requestBody.folderName = folderNameStr;
|
|
|
|
// Vérification finale
|
|
console.log('Request body before sending:', {
|
|
requestBody: requestBody,
|
|
stringify: JSON.stringify(requestBody),
|
|
fileNameInBody: requestBody.fileName,
|
|
folderNameInBody: requestBody.folderName,
|
|
fileNameType: typeof requestBody.fileName,
|
|
folderNameType: typeof requestBody.folderName,
|
|
fileNameConstructor: requestBody.fileName.constructor.name,
|
|
folderNameConstructor: requestBody.folderName.constructor.name
|
|
});
|
|
|
|
const jsonPayload = JSON.stringify(requestBody);
|
|
console.log('JSON payload:', jsonPayload);
|
|
|
|
fetch('/api/dpanel/dashboard/movefile', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: jsonPayload
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.message === "File moved successfully" || data.success) {
|
|
showToast('success', 'Fichier déplacé avec succès');
|
|
setTimeout(() => location.reload(), 1500);
|
|
} else {
|
|
throw new Error(data.error || 'Une erreur est survenue');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
showToast('error', 'Erreur lors du déplacement du fichier');
|
|
});
|
|
}
|
|
|
|
// =================== THÈME ===================
|
|
function initTheme() {
|
|
const body = document.body;
|
|
const savedTheme = localStorage.getItem('theme');
|
|
|
|
if (savedTheme) {
|
|
setTheme(savedTheme);
|
|
} else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
setTheme('dark');
|
|
}
|
|
}
|
|
|
|
function setTheme(theme) {
|
|
const body = document.body;
|
|
if (theme === 'dark') {
|
|
body.classList.add('dark');
|
|
} else {
|
|
body.classList.remove('dark');
|
|
}
|
|
localStorage.setItem('theme', theme);
|
|
}
|
|
|
|
function toggleDarkMode() {
|
|
const body = document.body;
|
|
if (body.classList.contains('dark')) {
|
|
setTheme('light');
|
|
} else {
|
|
setTheme('dark');
|
|
}
|
|
}
|
|
|
|
// =================== VERSION ===================
|
|
function loadVersion() {
|
|
fetch('/build-metadata')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
const versionElement = document.getElementById('version-number');
|
|
if (versionElement) {
|
|
versionElement.textContent = data.build_version;
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error fetching version:', error);
|
|
const versionElement = document.getElementById('version-number');
|
|
if (versionElement) {
|
|
versionElement.textContent = 'Version indisponible';
|
|
}
|
|
});
|
|
}
|
|
|
|
// =================== METADATA ===================
|
|
function displayMetadata() {
|
|
fetch('/build-metadata')
|
|
.then(response => response.json())
|
|
.then(metadata => {
|
|
document.getElementById('buildVersion').textContent = metadata.build_version;
|
|
document.getElementById('nodeVersion').textContent = metadata.node_version;
|
|
document.getElementById('expressVersion').textContent = metadata.express_version;
|
|
document.getElementById('buildSha').textContent = metadata.build_sha;
|
|
document.getElementById('osType').textContent = metadata.os_type;
|
|
document.getElementById('osRelease').textContent = metadata.os_release;
|
|
|
|
// Utiliser Bootstrap modal si disponible, sinon créer une modal simple
|
|
const modalElement = document.getElementById('metadataModal');
|
|
if (modalElement && typeof $ !== 'undefined' && $.fn.modal) {
|
|
$('#metadataModal').modal('show');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Failed to fetch metadata:', error);
|
|
showToast('error', 'Impossible de récupérer les métadonnées');
|
|
});
|
|
}
|
|
|
|
// Expose les fonctions globalement si nécessaire
|
|
window.displayMetadata = displayMetadata;
|
|
window.addCollaborator = addCollaborator;
|
|
window.removeCollaborator = removeCollaborator;
|
|
window.toggleCollaboration = toggleCollaboration;
|
|
|
|
// =================== FONCTIONS COLLABORATIVES ===================
|
|
function leaveSharedFolder(folderName, element) {
|
|
Swal.fire({
|
|
title: 'Quitter le dossier partagé',
|
|
text: `Êtes-vous sûr de vouloir quitter le dossier "${folderName}" ? Vous perdrez l'accès à ce dossier et ses fichiers.`,
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonColor: '#f59e0b',
|
|
cancelButtonColor: '#6b7280',
|
|
confirmButtonText: 'Oui, quitter',
|
|
cancelButtonText: 'Annuler'
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
// Extraire le propriétaire depuis l'URL du dossier partagé
|
|
const url = element.dataset.url;
|
|
const urlParts = url.split('/');
|
|
const owner = urlParts[urlParts.indexOf('shared') + 1];
|
|
|
|
fetch('/api/dpanel/sharedfolders/leave', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
folderName: folderName,
|
|
folderOwner: owner
|
|
})
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showToast('success', 'Vous avez quitté le dossier partagé');
|
|
// Supprimer la ligne du tableau
|
|
element.remove();
|
|
} else {
|
|
showToast('error', data.error || 'Erreur lors de la sortie du dossier');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
showToast('error', 'Erreur de connexion');
|
|
});
|
|
}
|
|
});
|
|
}
|