/** * Bulk Operations Module * Handles bulk file encryption/decryption, drag & drop, and file preview */ class BulkOperations { constructor() { this.files = []; this.results = []; this.isProcessing = false; this.setupEventListeners(); this.populateAlgorithmDropdown(); } setupEventListeners() { // Drag & Drop Zone const dropZone = document.getElementById('bulk-drop-zone'); const fileInput = document.getElementById('bulk-file-input'); const fileSelect = document.getElementById('bulk-file-select'); console.log('Bulk setup - dropZone:', dropZone, 'fileInput:', fileInput, 'fileSelect:', fileSelect); if (dropZone && fileInput) { console.log('Setting up bulk drag & drop events'); // Drag & Drop Events dropZone.addEventListener('dragover', this.handleDragOver.bind(this)); dropZone.addEventListener('dragleave', this.handleDragLeave.bind(this)); dropZone.addEventListener('drop', this.handleDrop.bind(this)); dropZone.addEventListener('click', () => { console.log('Bulk drop zone clicked, opening file input'); fileInput.click(); }); // File Input Events fileInput.addEventListener('change', this.handleFileSelect.bind(this)); } else { console.error('Bulk elements not found - dropZone:', dropZone, 'fileInput:', fileInput); } if (fileSelect) { console.log('Setting up bulk file select button'); fileSelect.addEventListener('click', (e) => { console.log('Bulk file select button clicked'); e.stopPropagation(); fileInput.click(); }); } else { console.error('Bulk file select button not found'); } // Control Buttons const processBtn = document.getElementById('bulk-process-btn'); const clearBtn = document.getElementById('bulk-clear-btn'); const downloadAllBtn = document.getElementById('bulk-download-all'); const resetBtn = document.getElementById('bulk-reset'); if (processBtn) processBtn.addEventListener('click', this.processFiles.bind(this)); if (clearBtn) clearBtn.addEventListener('click', this.clearFiles.bind(this)); if (downloadAllBtn) downloadAllBtn.addEventListener('click', this.downloadAllResults.bind(this)); if (resetBtn) resetBtn.addEventListener('click', this.reset.bind(this)); } handleDragOver(e) { e.preventDefault(); e.stopPropagation(); document.getElementById('bulk-drop-zone').classList.add('drag-over'); } handleDragLeave(e) { e.preventDefault(); e.stopPropagation(); document.getElementById('bulk-drop-zone').classList.remove('drag-over'); } handleDrop(e) { e.preventDefault(); e.stopPropagation(); document.getElementById('bulk-drop-zone').classList.remove('drag-over'); const files = Array.from(e.dataTransfer.files); this.addFiles(files); } handleFileSelect(e) { const files = Array.from(e.target.files); this.addFiles(files); } addFiles(newFiles) { // Filter out duplicates newFiles = newFiles.filter(newFile => !this.files.some(existingFile => existingFile.name === newFile.name && existingFile.size === newFile.size ) ); this.files.push(...newFiles); this.updateFileList(); this.showFilePreview(); } updateFileList() { const fileList = document.getElementById('bulk-file-list'); if (!fileList) return; fileList.innerHTML = ''; this.files.forEach((file, index) => { const fileItem = document.createElement('div'); fileItem.className = 'file-item'; fileItem.innerHTML = `
${file.name}
${this.formatFileSize(file.size)}
`; fileList.appendChild(fileItem); }); } showFilePreview() { const previewSection = document.getElementById('bulk-file-preview'); if (previewSection) { previewSection.style.display = this.files.length > 0 ? 'block' : 'none'; } } async previewFile(index) { const file = this.files[index]; if (!file) return; const previewContainer = document.createElement('div'); previewContainer.className = 'file-preview-container'; const header = document.createElement('div'); header.className = 'file-preview-header'; header.textContent = `Preview: ${file.name}`; const content = document.createElement('div'); content.className = 'file-preview-content'; // Handle different file types if (file.type.startsWith('text/') || this.isTextFile(file.name)) { try { const text = await this.readFileAsText(file); content.textContent = text.length > 2000 ? text.substring(0, 2000) + '...' : text; } catch (error) { content.textContent = 'Error reading file: ' + error.message; } } else if (file.type.startsWith('image/')) { const img = document.createElement('img'); img.className = 'image-preview'; img.src = URL.createObjectURL(file); img.onload = () => URL.revokeObjectURL(img.src); content.appendChild(img); } else { content.innerHTML = `
File Type: ${file.type || 'Unknown'}
Size: ${this.formatFileSize(file.size)}
Preview not available for this file type.
`; } previewContainer.appendChild(header); previewContainer.appendChild(content); // Remove existing preview const existingPreview = document.querySelector('.file-preview-container'); if (existingPreview) { existingPreview.remove(); } // Add new preview after the file list const fileList = document.getElementById('bulk-file-list'); if (fileList) { fileList.parentNode.insertBefore(previewContainer, fileList.nextSibling); } } isTextFile(filename) { const textExtensions = ['.txt', '.md', '.js', '.html', '.css', '.json', '.xml', '.csv', '.log', '.py', '.java', '.c', '.cpp', '.h']; return textExtensions.some(ext => filename.toLowerCase().endsWith(ext)); } readFileAsText(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = e => resolve(e.target.result); reader.onerror = e => reject(new Error('Failed to read file')); reader.readAsText(file); }); } removeFile(index) { this.files.splice(index, 1); this.updateFileList(); this.showFilePreview(); // Remove preview if it exists const existingPreview = document.querySelector('.file-preview-container'); if (existingPreview) { existingPreview.remove(); } } clearFiles() { this.files = []; this.updateFileList(); this.showFilePreview(); // Remove preview if it exists const existingPreview = document.querySelector('.file-preview-container'); if (existingPreview) { existingPreview.remove(); } // Clear file input const fileInput = document.getElementById('bulk-file-input'); if (fileInput) fileInput.value = ''; } async processFiles() { if (this.files.length === 0) { alert('Please select files to process'); return; } const password = document.getElementById('bulk-password')?.value; if (!password) { alert('Please enter a password'); return; } const algorithm = document.getElementById('bulk-algorithm')?.value; if (!algorithm) { alert('Please select an algorithm'); return; } const isDecrypt = document.getElementById('bulk-operation-toggle')?.checked; this.isProcessing = true; this.results = []; // Show progress section const progressSection = document.getElementById('bulk-progress-section'); if (progressSection) progressSection.style.display = 'block'; // Initialize progress this.updateOverallProgress(0, this.files.length); this.initializeFileProgress(); // Process files sequentially to avoid overwhelming the server for (let i = 0; i < this.files.length; i++) { const file = this.files[i]; this.updateFileProgress(i, 'processing'); try { const result = await this.processFile(file, password, algorithm, isDecrypt); this.results.push({ file, result, success: true }); this.updateFileProgress(i, 'completed'); } catch (error) { this.results.push({ file, error: error.message, success: false }); this.updateFileProgress(i, 'error'); } this.updateOverallProgress(i + 1, this.files.length); } this.isProcessing = false; this.showResults(); } async processFile(file, password, algorithm, isDecrypt) { const formData = new FormData(); formData.append('file', file); formData.append('enc_password', password); formData.append('algorithm', algorithm); const endpoint = isDecrypt ? '/api/decrypt' : '/api/encrypt'; const response = await fetch(endpoint, { method: 'POST', body: formData }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || 'Processing failed'); } // Return the blob for download return await response.blob(); } updateOverallProgress(completed, total) { const progressBar = document.getElementById('bulk-overall-bar'); const progressText = document.getElementById('bulk-overall-text'); if (progressBar) { const percentage = total > 0 ? (completed / total) * 100 : 0; progressBar.style.width = `${percentage}%`; } if (progressText) { progressText.textContent = `${completed} / ${total} files processed`; } } initializeFileProgress() { const progressList = document.getElementById('bulk-file-progress-list'); if (!progressList) return; progressList.innerHTML = ''; this.files.forEach((file, index) => { const progressItem = document.createElement('div'); progressItem.className = 'file-progress-item'; progressItem.innerHTML = `
${file.name}
Waiting
`; progressList.appendChild(progressItem); }); } updateFileProgress(index, status) { const statusElement = document.getElementById(`progress-status-${index}`); if (!statusElement) return; statusElement.className = `file-progress-status status-${status}`; switch (status) { case 'processing': statusElement.textContent = 'Processing...'; break; case 'completed': statusElement.textContent = 'Completed'; break; case 'error': statusElement.textContent = 'Error'; break; default: statusElement.textContent = 'Waiting'; } } showResults() { const resultsSection = document.getElementById('bulk-results-section'); if (!resultsSection) return; resultsSection.style.display = 'block'; const resultsList = document.getElementById('bulk-results-list'); if (!resultsList) return; resultsList.innerHTML = ''; this.results.forEach((result, index) => { const resultItem = document.createElement('div'); resultItem.className = 'result-item'; const successCount = this.results.filter(r => r.success).length; const totalCount = this.results.length; if (result.success) { resultItem.innerHTML = `
✅ ${result.file.name}
Successfully processed
`; } else { resultItem.innerHTML = `
❌ ${result.file.name}
${result.error}
`; } resultsList.appendChild(resultItem); }); // Add summary const summary = document.createElement('div'); summary.style.cssText = 'padding: 15px; border-bottom: 1px solid #333; background-color: #1a1a1a; font-weight: bold;'; summary.innerHTML = `
Processing Complete
${successCount} successful, ${totalCount - successCount} failed out of ${totalCount} files
`; resultsList.insertBefore(summary, resultsList.firstChild); } downloadResult(index) { const result = this.results[index]; if (!result.success) return; const isDecrypt = document.getElementById('bulk-operation-toggle')?.checked; const algorithm = document.getElementById('bulk-algorithm')?.value; let filename; if (isDecrypt) { // For decryption, try to restore original filename filename = result.file.name.replace(/\.(aes_cbc|aes_gcm|xchacha|rsa_hybrid)\.encrypted$/, ''); } else { // For encryption, add algorithm extension filename = `${result.file.name}.${algorithm}.encrypted`; } this.downloadBlob(result.result, filename); } downloadAllResults() { const successfulResults = this.results.filter(r => r.success); if (successfulResults.length === 0) { alert('No successful results to download'); return; } successfulResults.forEach((result, index) => { setTimeout(() => { this.downloadResult(this.results.indexOf(result)); }, index * 500); // Stagger downloads }); } downloadBlob(blob, filename) { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } reset() { this.clearFiles(); this.results = []; this.isProcessing = false; // Hide sections const sections = ['bulk-progress-section', 'bulk-results-section']; sections.forEach(id => { const section = document.getElementById(id); if (section) section.style.display = 'none'; }); // Clear password const passwordField = document.getElementById('bulk-password'); if (passwordField) passwordField.value = ''; } formatFileSize(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } async populateAlgorithmDropdown() { try { const response = await fetch('/api/algorithms'); const data = await response.json(); if (response.ok && data.algorithms) { const dropdown = document.getElementById('bulk-algorithm'); if (dropdown) { dropdown.innerHTML = ''; for (const [key, algo] of Object.entries(data.algorithms)) { if (algo.supports_file) { const option = document.createElement('option'); option.value = key; option.textContent = algo.name; dropdown.appendChild(option); } } } } } catch (error) { console.error('Failed to load algorithms for bulk operations:', error); } } } // Initialize bulk operations when DOM is loaded let bulkOps; document.addEventListener('DOMContentLoaded', () => { bulkOps = new BulkOperations(); // Make bulkOps available globally for onclick handlers window.bulkOps = bulkOps; });