| <!DOCTYPE html>
|
| <html lang="en">
|
| <head>
|
| <meta charset="UTF-8">
|
| <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| <title>Batch Document Processing</title>
|
| <style>
|
| body { font-family: Arial, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
|
| .upload-area { border: 2px dashed #ccc; padding: 40px; text-align: center; margin: 20px 0; }
|
| .upload-area.dragover { border-color: #007cba; background-color: #f0f8ff; }
|
| .file-list { margin: 20px 0; }
|
| .file-item { padding: 10px; border: 1px solid #ddd; margin: 5px 0; display: flex; justify-content: space-between; align-items: center; }
|
| .file-item.processing { background-color: #fff3cd; }
|
| .file-item.success { background-color: #d4edda; }
|
| .file-item.error { background-color: #f8d7da; }
|
| .progress-bar { width: 100%; height: 20px; background-color: #f0f0f0; border-radius: 10px; overflow: hidden; margin: 10px 0; }
|
| .progress-fill { height: 100%; background-color: #007cba; transition: width 0.3s ease; }
|
| .results { margin: 20px 0; }
|
| .batch-history { margin: 30px 0; }
|
| .batch-item { padding: 15px; border: 1px solid #ddd; margin: 10px 0; border-radius: 5px; }
|
| button { padding: 10px 20px; margin: 5px; cursor: pointer; }
|
| .btn-primary { background-color: #007cba; color: white; border: none; }
|
| .btn-secondary { background-color: #6c757d; color: white; border: none; }
|
| .btn-danger { background-color: #dc3545; color: white; border: none; }
|
| </style>
|
| </head>
|
| <body>
|
| <h1>Accessibility Checker - Batch Processing</h1>
|
|
|
| <div class="upload-section">
|
| <h2>Upload Multiple Documents</h2>
|
| <div id="uploadArea" class="upload-area">
|
| <p>Drop up to 10 DOCX files here, or click to select</p>
|
| <input type="file" id="fileInput" multiple accept=".docx" style="display: none;">
|
| <button onclick="document.getElementById('fileInput').click()" class="btn-primary">Select Files</button>
|
| </div>
|
|
|
| <div id="fileList" class="file-list"></div>
|
|
|
| <div id="progressSection" style="display: none;">
|
| <h3>Processing Files...</h3>
|
| <div class="progress-bar">
|
| <div id="progressFill" class="progress-fill" style="width: 0%;"></div>
|
| </div>
|
| <div id="progressText">Preparing upload...</div>
|
| </div>
|
|
|
| <button id="uploadBtn" onclick="uploadFiles()" class="btn-primary" style="display: none;">
|
| Upload and Process Files
|
| </button>
|
| </div>
|
|
|
| <div id="results" class="results" style="display: none;">
|
| <h2>Processing Results</h2>
|
| <div id="resultsContent"></div>
|
| </div>
|
|
|
| <div class="batch-history">
|
| <h2>Previous Batches</h2>
|
| <button onclick="loadBatchHistory()" class="btn-secondary">Load Batch History</button>
|
| <div id="batchHistory"></div>
|
| </div>
|
|
|
| <script>
|
| let selectedFiles = [];
|
| let sessionId = null;
|
| const API_BASE = window.location.origin;
|
|
|
|
|
| async function initializeSession() {
|
| try {
|
| const response = await fetch(`${API_BASE}/api/session`, {
|
| method: 'POST',
|
| headers: { 'Content-Type': 'application/json' }
|
| });
|
| const data = await response.json();
|
| sessionId = data.sessionId;
|
| console.log('Session initialized:', sessionId);
|
|
|
|
|
| startHeartbeat();
|
|
|
|
|
| loadSessionData();
|
|
|
| } catch (error) {
|
| console.error('Failed to initialize session:', error);
|
| }
|
| }
|
|
|
| function startHeartbeat() {
|
|
|
| setInterval(async () => {
|
| if (sessionId) {
|
| try {
|
| await fetch(`${API_BASE}/api/session`, {
|
| method: 'POST',
|
| headers: {
|
| 'Content-Type': 'application/json',
|
| 'X-Session-ID': sessionId
|
| }
|
| });
|
| } catch (error) {
|
| console.warn('Heartbeat failed:', error);
|
| }
|
| }
|
| }, 5 * 60 * 1000);
|
| }
|
|
|
| async function loadSessionData() {
|
| if (!sessionId) return;
|
|
|
| try {
|
| const response = await fetch(`${API_BASE}/api/session?sessionId=${sessionId}`);
|
| const sessionData = await response.json();
|
|
|
|
|
| displaySessionHistory(sessionData);
|
|
|
| } catch (error) {
|
| console.warn('Failed to load session data:', error);
|
| }
|
| }
|
|
|
| function displaySessionHistory(sessionData) {
|
| const historyDiv = document.getElementById('batchHistory');
|
|
|
| if (sessionData.batches.length === 0) {
|
| historyDiv.innerHTML = '<p>No batches in this session yet.</p>';
|
| return;
|
| }
|
|
|
| historyDiv.innerHTML = '<h3>This Session:</h3>' +
|
| sessionData.batches.map(batch => `
|
| <div class="batch-item">
|
| <h4>Batch ${batch.batchId}</h4>
|
| <p><strong>Files:</strong> ${batch.totalFiles} (${batch.successful} successful, ${batch.failed} failed)</p>
|
| <p><strong>Processed:</strong> ${new Date(batch.timestamp).toLocaleString()}</p>
|
| <button onclick="downloadBatch('${batch.batchId}')" class="btn-primary">Download</button>
|
| </div>
|
| `).join('');
|
| }
|
|
|
|
|
| window.addEventListener('beforeunload', () => {
|
|
|
|
|
| });
|
|
|
|
|
| document.getElementById('fileInput').addEventListener('change', handleFileSelect);
|
|
|
| const uploadArea = document.getElementById('uploadArea');
|
| uploadArea.addEventListener('dragover', (e) => {
|
| e.preventDefault();
|
| uploadArea.classList.add('dragover');
|
| });
|
|
|
| uploadArea.addEventListener('dragleave', () => {
|
| uploadArea.classList.remove('dragover');
|
| });
|
|
|
| uploadArea.addEventListener('drop', (e) => {
|
| e.preventDefault();
|
| uploadArea.classList.remove('dragover');
|
| const files = Array.from(e.dataTransfer.files).filter(f => f.name.endsWith('.docx'));
|
| handleFiles(files);
|
| });
|
|
|
| function handleFileSelect(e) {
|
| const files = Array.from(e.target.files);
|
| handleFiles(files);
|
| }
|
|
|
| function handleFiles(files) {
|
| selectedFiles = files.slice(0, 10);
|
| displayFileList();
|
| document.getElementById('uploadBtn').style.display = selectedFiles.length > 0 ? 'block' : 'none';
|
| }
|
|
|
| function displayFileList() {
|
| const fileList = document.getElementById('fileList');
|
| if (selectedFiles.length === 0) {
|
| fileList.innerHTML = '';
|
| return;
|
| }
|
|
|
| fileList.innerHTML = `<h3>Selected Files (${selectedFiles.length}):</h3>`;
|
| selectedFiles.forEach((file, index) => {
|
| const fileItem = document.createElement('div');
|
| fileItem.className = 'file-item';
|
| fileItem.innerHTML = `
|
| <div>
|
| <strong>${file.name}</strong>
|
| <br><small>${(file.size / 1024).toFixed(1)} KB</small>
|
| </div>
|
| <button onclick="removeFile(${index})" class="btn-danger">Remove</button>
|
| `;
|
| fileList.appendChild(fileItem);
|
| });
|
| }
|
|
|
| function removeFile(index) {
|
| selectedFiles.splice(index, 1);
|
| displayFileList();
|
| document.getElementById('uploadBtn').style.display = selectedFiles.length > 0 ? 'block' : 'none';
|
| }
|
|
|
| async function uploadFiles() {
|
| if (selectedFiles.length === 0 || !sessionId) return;
|
|
|
| document.getElementById('progressSection').style.display = 'block';
|
| document.getElementById('uploadBtn').disabled = true;
|
|
|
| const formData = new FormData();
|
| selectedFiles.forEach((file, index) => {
|
| formData.append(`file${index}`, file);
|
| });
|
|
|
| try {
|
| updateProgress(10, 'Uploading files...');
|
|
|
| const response = await fetch(`${API_BASE}/api/batch-upload`, {
|
| method: 'POST',
|
| headers: {
|
| 'X-Session-ID': sessionId
|
| },
|
| body: formData
|
| });
|
|
|
| updateProgress(90, 'Processing files...');
|
|
|
| if (!response.ok) {
|
| throw new Error(`Upload failed: ${response.statusText}`);
|
| }
|
|
|
| const result = await response.json();
|
| updateProgress(100, 'Complete!');
|
|
|
| displayResults(result);
|
|
|
|
|
| loadSessionData();
|
|
|
|
|
| selectedFiles = [];
|
| displayFileList();
|
| document.getElementById('uploadBtn').style.display = 'none';
|
|
|
| } catch (error) {
|
| console.error('Upload error:', error);
|
| updateProgress(0, `Error: ${error.message}`);
|
| } finally {
|
| document.getElementById('uploadBtn').disabled = false;
|
| setTimeout(() => {
|
| document.getElementById('progressSection').style.display = 'none';
|
| }, 2000);
|
| }
|
| }
|
|
|
| function updateProgress(percent, text) {
|
| document.getElementById('progressFill').style.width = percent + '%';
|
| document.getElementById('progressText').textContent = text;
|
| }
|
|
|
| function displayResults(result) {
|
| const resultsDiv = document.getElementById('results');
|
| const resultsContent = document.getElementById('resultsContent');
|
|
|
| resultsContent.innerHTML = `
|
| <div class="batch-item">
|
| <h3>Batch ${result.batchId}</h3>
|
| <p><strong>Total Files:</strong> ${result.summary.totalFiles}</p>
|
| <p><strong>Successful:</strong> ${result.summary.successful}</p>
|
| <p><strong>Failed:</strong> ${result.summary.failed}</p>
|
|
|
| <button onclick="downloadBatch('${result.batchId}')" class="btn-primary">
|
| Download Remediated Files
|
| </button>
|
|
|
| <h4>File Details:</h4>
|
| <div class="file-list">
|
| ${result.results.map(r => `
|
| <div class="file-item ${r.success ? 'success' : 'error'}">
|
| <div>
|
| <strong>${r.filename}</strong>
|
| ${r.success ?
|
| `<br><small>✓ Processed successfully</small>` :
|
| `<br><small>✗ Error: ${r.error}</small>`
|
| }
|
| </div>
|
| </div>
|
| `).join('')}
|
| </div>
|
| </div>
|
| `;
|
|
|
| resultsDiv.style.display = 'block';
|
| }
|
|
|
| async function downloadBatch(batchId) {
|
| if (sessionId) {
|
| window.open(`${API_BASE}/api/batch-download?batchId=${batchId}&sessionId=${sessionId}`, '_blank');
|
| }
|
| }
|
|
|
| async function deleteBatch(batchId) {
|
| if (!confirm(`Delete batch ${batchId}?`)) return;
|
|
|
| try {
|
| const response = await fetch(`${API_BASE}/api/reports?batchId=${batchId}`, {
|
| method: 'DELETE'
|
| });
|
|
|
| if (response.ok) {
|
| loadBatchHistory();
|
| } else {
|
| alert('Failed to delete batch');
|
| }
|
| } catch (error) {
|
| console.error('Error deleting batch:', error);
|
| }
|
| }
|
|
|
|
|
| initializeSession();
|
| </script>
|
| </body>
|
| </html> |