let selectedFiles = []; let scanResults = []; let selectedIndex = -1; const fileInput = document.getElementById('fileInput'); const uploadInfo = document.getElementById('uploadInfo'); const resultsBody = document.getElementById('resultsBody'); const detailsContent = document.getElementById('detailsContent'); const emptyState = document.getElementById('emptyState'); const btnScan = document.getElementById('btnScan'); const btnSanitize = document.getElementById('btnSanitize'); const statusText = document.getElementById('statusText'); const dropZone = document.getElementById('dropZone'); fileInput.addEventListener('change', handleFileSelect); dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('drag-over'); }); dropZone.addEventListener('dragleave', () => { dropZone.classList.remove('drag-over'); }); dropZone.addEventListener('drop', (e) => { e.preventDefault(); dropZone.classList.remove('drag-over'); if (e.dataTransfer.files.length) { fileInput.files = e.dataTransfer.files; handleFileSelect(); } }); function handleFileSelect() { selectedFiles = Array.from(fileInput.files); if (selectedFiles.length === 0) { uploadInfo.textContent = 'No files selected'; } else if (selectedFiles.length === 1) { uploadInfo.textContent = selectedFiles[0].name; } else { uploadInfo.textContent = `${selectedFiles.length} files selected`; } } async function uploadFiles() { if (!selectedFiles.length) return false; setStatus('Uploading files...'); const formData = new FormData(); for (const file of selectedFiles) { formData.append('files', file); } try { const resp = await fetch('/api/upload', { method: 'POST', body: formData }); const data = await resp.json(); if (data.error) { setStatus('Upload failed: ' + data.error); return false; } setStatus(`Uploaded ${data.count} file(s)`); return true; } catch (e) { setStatus('Upload failed: ' + e.message); return false; } } async function startScan() { const context = document.getElementById('context').value; const recursive = document.getElementById('recursive').checked; const uploaded = await uploadFiles(); if (!uploaded && !selectedFiles.length) { setStatus('No files to scan. Select files first.'); return; } btnScan.disabled = true; btnScan.textContent = 'Scanning...'; setStatus('Scanning...'); try { const resp = await fetch('/api/scan', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ target: '', recursive, context }) }); const data = await resp.json(); if (data.error) { setStatus('Scan failed: ' + data.error); return; } scanResults = data.results; selectedIndex = -1; renderTable(); setStatus(`Scan complete: ${data.count} file(s) analyzed`); } catch (e) { setStatus('Scan failed: ' + e.message); } finally { btnScan.disabled = false; btnScan.textContent = 'Scan'; } } function renderTable() { resultsBody.innerHTML = ''; emptyState.style.display = scanResults.length ? 'none' : 'block'; scanResults.forEach((r, i) => { const tr = document.createElement('tr'); tr.dataset.index = i; const author = r.metadata.author || r.metadata.last_modified_by || ''; tr.innerHTML = ` ${r.level} ${r.total_risk} ${r.file_type} ${escapeHtml(r.path)} ${escapeHtml(author)} `; tr.addEventListener('click', () => selectRow(i)); resultsBody.appendChild(tr); }); } function selectRow(index) { selectedIndex = index; const r = scanResults[index]; document.querySelectorAll('#resultsBody tr').forEach(tr => { tr.classList.toggle('selected', parseInt(tr.dataset.index) === index); }); btnSanitize.disabled = r.file_type !== 'image'; let html = ''; html += `${r.level} (Score: ${r.total_risk})\n\n`; html += `Path: ${escapeHtml(r.path)}\n`; html += `Type: ${r.file_type}\n`; html += `Size: ${formatSize(r.size_bytes)}\n`; html += `SHA-256: ${r.sha256}\n`; html += `Context: ${r.context}\n\n`; html += `Metadata:\n`; for (const [k, v] of Object.entries(r.metadata)) { if (k === 'all_exif_tags') continue; if (typeof v === 'object' && v !== null) { html += ` ${k}: ${JSON.stringify(v)}\n`; } else { html += ` ${k}: ${v}\n`; } } if (r.findings && r.findings.length) { html += `\nFindings:\n`; r.findings.forEach(f => { html += ` ${f.severity} | ${f.name} | ${f.evidence}\n`; }); } detailsContent.innerHTML = html; } async function exportReport(fmt) { if (!scanResults.length) { setStatus('No results to export. Run scan first.'); return; } setStatus(`Exporting ${fmt.toUpperCase()}...`); try { const resp = await fetch(`/api/export/${fmt}`); if (!resp.ok) { const data = await resp.json(); setStatus('Export failed: ' + (data.error || resp.statusText)); return; } const blob = await resp.blob(); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `report.${fmt}`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); setStatus(`Exported ${fmt.toUpperCase()} successfully`); } catch (e) { setStatus('Export failed: ' + e.message); } } async function sanitizeFile() { if (selectedIndex < 0) { setStatus('Select a file first.'); return; } const r = scanResults[selectedIndex]; if (r.file_type !== 'image') { setStatus('Sanitization supported only for image files.'); return; } setStatus('Sanitizing...'); try { const resp = await fetch(`/api/sanitize/${selectedIndex}`, { method: 'POST' }); const data = await resp.json(); if (data.error) { setStatus('Sanitization failed: ' + data.error); } else { setStatus(`Sanitized copy created: ${data.sanitized_path}`); } } catch (e) { setStatus('Sanitization failed: ' + e.message); } } function setStatus(msg) { statusText.textContent = msg; } function formatSize(bytes) { if (bytes < 1024) return bytes + ' B'; if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'; return (bytes / 1048576).toFixed(1) + ' MB'; } function escapeHtml(s) { if (!s) return ''; return s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); }