feat: first version
This commit is contained in:
422
script.js
Normal file
422
script.js
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user