// Configuration globale let config = { baseUrl: '', currentPath: '' }; // Initialisation de l'application document.addEventListener('DOMContentLoaded', function() { initializeApp(); setupEventListeners(); }); function initializeApp() { // Tentative de configuration automatique depuis l'URL courante autoConfigureFromURL(); // Affichage du formulaire de développement showDevForm(); } function setupEventListeners() { // Formulaire de développement document.getElementById('url-form').addEventListener('submit', handleUrlFormSubmit); // Gestion de la navigation arrière/avant du navigateur window.addEventListener('popstate', handlePopState); } function handlePopState(event) { console.log('Navigation arrière/avant détectée:', event.state); if (event.state && event.state.baseUrl) { // Restaurer la configuration config.baseUrl = event.state.baseUrl; config.currentPath = event.state.path || ''; updateConfigDisplay(); // Charger le contenu sans ajouter à l'historique (pour éviter les boucles) loadBucketContents(config.currentPath, false); } } function autoConfigureFromURL() { const currentUrl = window.location.href; const url = new URL(currentUrl); // Configuration simplifiée : on prend juste le base URL sans le bucket config.baseUrl = `${url.protocol}//${url.host}${url.pathname.replace('/index.html', '')}`; // Vérifier s'il y a un fragment d'URL pour définir le chemin initial if (url.hash) { const hashPath = url.hash.replace('#/', ''); config.currentPath = hashPath; } updateConfigDisplay(); console.log('Configuration automatique:', config); } function handleUrlFormSubmit(event) { event.preventDefault(); const urlInput = document.getElementById('minio-url'); const minioUrl = urlInput.value.trim(); if (!minioUrl) { showError('Veuillez saisir une URL MinIO'); return; } parseMinioURL(minioUrl); } function parseMinioURL(url) { try { const urlObj = new URL(url); // Configuration simplifiée : on prend l'URL complète comme base config.baseUrl = url.replace(/\/$/, ''); // Supprimer le slash final si présent config.currentPath = ''; updateConfigDisplay(); // Ajouter l'état initial à l'historique const initialState = { path: '', baseUrl: config.baseUrl }; history.replaceState(initialState, '', window.location.href); loadBucketContents(); } catch (error) { showError('URL invalide: ' + error.message); console.error('Erreur de parsing URL:', error); } } function updateConfigDisplay() { const configInfo = document.getElementById('config-info'); configInfo.innerHTML = ` Configuration actuelle:
URL de base: ${config.baseUrl}
Chemin actuel: /${config.currentPath} `; } function showDevForm() { const devForm = document.getElementById('dev-form'); devForm.style.display = 'block'; } function showError(message) { const errorSection = document.getElementById('error-section'); const errorMessage = document.getElementById('error-message'); errorMessage.textContent = message; errorSection.style.display = 'block'; // Masquer l'erreur après 5 secondes setTimeout(() => { errorSection.style.display = 'none'; }, 5000); } function showLoading(show = true) { const loading = document.getElementById('loading'); const table = document.getElementById('files-table'); if (show) { loading.style.display = 'block'; table.style.display = 'none'; } else { loading.style.display = 'none'; table.style.display = 'table'; } } function updateCurrentPathDisplay() { const pathDisplay = document.getElementById('current-path'); pathDisplay.textContent = '/' + config.currentPath; } async function loadBucketContents(path = '', pushToHistory = true) { console.log('Debug loadBucketContents - path received:', JSON.stringify(path)); if (!config.baseUrl) { showError('Configuration URL manquante'); return; } showLoading(true); config.currentPath = path; updateCurrentPathDisplay(); // Ajouter à l'historique du navigateur si demandé if (pushToHistory) { const state = { path: path, baseUrl: config.baseUrl }; const url = path ? `#/${path}` : '#/'; history.pushState(state, '', url); } try { // Construction de l'URL API avec delimiter let apiUrl = `${config.baseUrl}?list-type=2&delimiter=/`; // Construire le prefix pour l'API MinIO if (path) { // Pour un sous-dossier const cleanPath = path.startsWith('/') ? path.substring(1) : path; const fullPrefix = cleanPath + '/'; apiUrl += `&prefix=${encodeURIComponent(fullPrefix)}`; } console.log('Appel API MinIO:', apiUrl); const response = await fetch(apiUrl); if (!response.ok) { throw new Error(`Erreur HTTP: ${response.status}`); } const xmlText = await response.text(); console.log('Réponse XML reçue:', xmlText); const contents = parseMinioXML(xmlText); displayContents(contents); } catch (error) { console.error('Erreur lors du chargement:', error); showError('Erreur de chargement: ' + error.message); } finally { showLoading(false); } } function parseMinioXML(xmlText) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(xmlText, 'text/xml'); const contents = { folders: [], files: [] }; // Déterminer le préfixe attendu basé sur la navigation actuelle let expectedPrefix = ''; if (config.currentPath) { expectedPrefix = config.currentPath + '/'; } // Parse CommonPrefixes (dossiers) const prefixes = xmlDoc.getElementsByTagName('CommonPrefixes'); for (let prefix of prefixes) { const prefixElement = prefix.getElementsByTagName('Prefix')[0]; if (prefixElement) { const fullPrefix = prefixElement.textContent; // Extraire le nom du dossier (dernière partie avant le /) const folderName = fullPrefix.replace(/\/$/, '').split('/').pop(); if (folderName) { // Construire le chemin pour la navigation let folderPath; if (config.currentPath) { folderPath = config.currentPath + '/' + folderName; } else { folderPath = folderName; } console.log('Debug parsing - fullPrefix:', fullPrefix, 'folderName:', folderName, 'currentPath:', config.currentPath, 'folderPath:', folderPath); contents.folders.push({ name: folderName, path: folderPath }); } } } // Parse Contents (fichiers) const objects = xmlDoc.getElementsByTagName('Contents'); for (let obj of objects) { const keyElement = obj.getElementsByTagName('Key')[0]; const sizeElement = obj.getElementsByTagName('Size')[0]; const modifiedElement = obj.getElementsByTagName('LastModified')[0]; if (keyElement) { const fullKey = keyElement.textContent; const fileName = fullKey.split('/').pop(); // Vérifier que le fichier correspond au niveau de navigation actuel let shouldInclude = false; if (config.currentPath) { // Dans un sous-dossier : vérifier que la clé commence par le bon préfixe const currentPrefix = config.currentPath + '/'; if (fullKey.startsWith(currentPrefix)) { // S'assurer qu'on n'affiche que les fichiers directs (pas dans des sous-sous-dossiers) const relativePath = fullKey.replace(currentPrefix, ''); if (!relativePath.includes('/')) { shouldInclude = true; } } } else { // À la racine : afficher tous les fichiers qui ne sont pas dans des dossiers if (!fullKey.includes('/')) { shouldInclude = true; } } // Exclure les fichiers de l'application if (shouldInclude && fileName && !['index.html', 'style.css', 'script.js'].includes(fileName)) { contents.files.push({ name: fileName, size: sizeElement ? parseInt(sizeElement.textContent) : 0, modified: modifiedElement ? new Date(modifiedElement.textContent) : new Date(), fullKey: fullKey }); } } } return contents; } function displayContents(contents) { const tbody = document.getElementById('files-tbody'); tbody.innerHTML = ''; // Bouton parent directory si on n'est pas à la racine if (config.currentPath) { const parentPath = config.currentPath.split('/').slice(0, -1).join('/'); const parentRow = createParentRow(parentPath); tbody.appendChild(parentRow); } // Afficher les dossiers contents.folders.forEach(folder => { const row = createFolderRow(folder); tbody.appendChild(row); }); // Afficher les fichiers contents.files.forEach(file => { const row = createFileRow(file); tbody.appendChild(row); }); // Afficher le tableau document.getElementById('files-table').style.display = 'table'; } function createParentRow(parentPath) { const row = document.createElement('tr'); row.innerHTML = ` .. - - - `; const link = row.querySelector('.folder-link'); link.addEventListener('click', (e) => { e.preventDefault(); loadBucketContents(parentPath, true); }); return row; } function createFolderRow(folder) { const row = document.createElement('tr'); row.innerHTML = ` ${folder.name}/ - - - `; const link = row.querySelector('.folder-link'); link.addEventListener('click', (e) => { e.preventDefault(); loadBucketContents(folder.path, true); }); return row; } function createFileRow(file) { const row = document.createElement('tr'); const fileUrl = `${config.baseUrl}/${file.fullKey}`; row.innerHTML = ` ${file.name} ${formatFileSize(file.size)} ${formatDate(file.modified)} `; const copyBtn = row.querySelector('.copy-btn'); copyBtn.addEventListener('click', () => copyToClipboard(fileUrl)); return row; } function formatFileSize(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; } function formatDate(date) { return date.toLocaleString('fr-FR', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); } async function copyToClipboard(text) { try { await navigator.clipboard.writeText(text); console.log('Lien copié:', text); // Feedback visuel temporaire const btn = event.target; const originalText = btn.textContent; btn.textContent = 'Copié !'; setTimeout(() => { btn.textContent = originalText; }, 1000); } catch (error) { console.error('Erreur copie clipboard:', error); // Fallback pour les navigateurs plus anciens const textArea = document.createElement('textarea'); textArea.value = text; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); } }