feat: first version

This commit is contained in:
2025-09-05 09:25:01 +02:00
commit 393469d55e
4 changed files with 790 additions and 0 deletions

422
script.js Normal file
View File

@@ -0,0 +1,422 @@
// 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 = `
<strong>Configuration actuelle:</strong><br>
URL de base: ${config.baseUrl}<br>
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 = `
<td>
<span class="icon-parent"></span>
<a href="#" class="folder-link parent-dir" data-path="${parentPath}">..</a>
</td>
<td>-</td>
<td>-</td>
<td>-</td>
`;
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 = `
<td>
<span class="icon-folder"></span>
<a href="#" class="folder-link" data-path="${folder.path}">${folder.name}/</a>
</td>
<td>-</td>
<td>-</td>
<td>-</td>
`;
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 = `
<td>
<span class="icon-file"></span>
<a href="${fileUrl}" class="file-link" target="_blank">${file.name}</a>
</td>
<td class="file-size">${formatFileSize(file.size)}</td>
<td class="file-date">${formatDate(file.modified)}</td>
<td>
<button class="copy-btn" data-url="${fileUrl}">Copier lien</button>
</td>
`;
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);
}
}