// Dashboard functionality let jobs = []; let currentJob = null; // Initialize dashboard document.addEventListener('DOMContentLoaded', function() { initFileUpload(); initJobsTable(); loadSampleJobs(); }); // File upload functionality function initFileUpload() { const dropZone = document.getElementById('drop-zone'); const fileInput = document.getElementById('file-input'); // Handle drag and drop dropZone.addEventListener('dragover', function(e) { e.preventDefault(); dropZone.classList.add('dragover'); }); dropZone.addEventListener('dragleave', function(e) { e.preventDefault(); dropZone.classList.remove('dragover'); }); dropZone.addEventListener('drop', function(e) { e.preventDefault(); dropZone.classList.remove('dragover'); const files = Array.from(e.dataTransfer.files); handleFileUpload(files); }); // Handle file input fileInput.addEventListener('change', function(e) { const files = Array.from(e.target.files); handleFileUpload(files); }); // Handle click to upload dropZone.addEventListener('click', function() { fileInput.click(); }); } // Handle file upload async function handleFileUpload(files) { if (files.length === 0) return; // Hide drop zone when files are uploaded document.getElementById('drop-zone').style.display = 'none'; for (const file of files) { const job = createJob(file); jobs.unshift(job); try { // Show processing updateJobStatus(job.id, 'processing'); // Simulate API call const result = await Wash.mockAPI.cleanDocument(file); // Update job with results updateJobWithResult(job.id, result); } catch (error) { updateJobStatus(job.id, 'failed', error.message); } } renderJobsTable(); } // Create new job function createJob(file) { return { id: Date.now() + Math.random(), fileName: file.name, fileSize: Wash.formatBytes(file.size), status: 'queued', threats: [], processingTime: 0, createdAt: new Date(), output: {} }; } // Update job status function updateJobStatus(jobId, status, error = null) { const job = jobs.find(j => j.id === jobId); if (job) { job.status = status; if (error) { job.error = error; } renderJobsTable(); } } // Update job with API result function updateJobWithResult(jobId, result) { const job = jobs.find(j => j.id === jobId); if (job) { job.status = 'completed'; job.threats = result.threats; job.processingTime = result.processingTime; job.output = { text: result.cleanText, pdf: result.cleanPDF, json: JSON.stringify(result.metadata, null, 2) }; job.metadata = result.metadata; renderJobsTable(); } } // Initialize jobs table function initJobsTable() { // Add click handlers for job rows document.addEventListener('click', function(e) { const row = e.target.closest('tr[data-job-id]'); if (row) { const jobId = parseFloat(row.dataset.jobId); showJobDetails(jobId); } }); } // Load sample jobs function loadSampleJobs() { const sampleJobs = [ { id: 1, fileName: 'contract.pdf', fileSize: '245 KB', status: 'completed', threats: ['zero-width characters', 'embedded JavaScript', 'LSB steganography'], processingTime: 4.2, createdAt: new Date(Date.now() - 3600000), output: { text: 'Clean contract text...', pdf: 'blob:contract-clean', json: '{"originalSize": 250880, "cleanedSize": 213248, "threatsRemoved": 3}' }, metadata: { originalSize: 250880, cleanedSize: 213248, threatsRemoved: 3 } }, { id: 2, fileName: 'resume.docx', fileSize: '89 KB', status: 'completed', threats: ['VBA macro'], processingTime: 3.1, createdAt: new Date(Date.now() - 7200000), output: { text: 'Clean resume text...', pdf: 'blob:resume-clean', json: '{"originalSize": 91136, "cleanedSize": 87432, "threatsRemoved": 1}' }, metadata: { originalSize: 91136, cleanedSize: 87432, threatsRemoved: 1 } }, { id: 3, fileName: 'image.png', fileSize: '1.2 MB', status: 'completed', threats: ['LSB stego in blue channel'], processingTime: 1.9, createdAt: new Date(Date.now() - 10800000), output: { text: 'Extracted image text...', pdf: 'blob:image-clean', json: '{"originalSize": 1258291, "cleanedSize": 1193862, "threatsRemoved": 1}' }, metadata: { originalSize: 1258291, cleanedSize: 1193862, threatsRemoved: 1 } }, { id: 4, fileName: 'memo.pdf', fileSize: '156 KB', status: 'processing', threats: [], processingTime: 0, createdAt: new Date(Date.now() - 300000), output: {} } ]; jobs = sampleJobs; renderJobsTable(); } // Render jobs table function renderJobsTable() { const tableBody = document.getElementById('jobs-table'); if (jobs.length === 0) { document.getElementById('drop-zone').style.display = 'block'; return; } document.getElementById('drop-zone').style.display = 'none'; tableBody.innerHTML = jobs.map(job => { const timeAgo = getTimeAgo(job.createdAt); const statusClass = getStatusClass(job.status); const statusText = getStatusText(job.status); return `
${job.fileName}
${job.fileSize}
${job.status === 'processing' ? '
' : ''} ${statusText}
${job.threats.length > 0 ? job.threats.join(', ') : '-'}
${job.status === 'completed' ? Wash.formatTime(job.processingTime) : '-'} ${job.status === 'completed' ? `
` : '-'} `; }).join(''); // Re-initialize feather icons feather.replace(); } // Get status CSS class function getStatusClass(status) { switch (status) { case 'completed': return 'bg-green-100 text-green-800'; case 'processing': return 'bg-yellow-100 text-yellow-800'; case 'failed': return 'bg-red-100 text-red-800'; default: return 'bg-gray-100 text-gray-800'; } } // Get status text function getStatusText(status) { switch (status) { case 'completed': return 'Completed'; case 'processing': return 'Processing…'; case 'failed': return 'Failed'; default: return 'Queued'; } } // Get time ago string function getTimeAgo(date) { const now = new Date(); const diff = now - date; const minutes = Math.floor(diff / 60000); const hours = Math.floor(diff / 3600000); if (minutes < 60) { return `${minutes}m ago`; } else { return `${hours}h ago`; } } // Show job details panel function showJobDetails(jobId) { const job = jobs.find(j => j.id === jobId); if (!job) return; currentJob = job; const panel = document.getElementById('job-details-panel'); const content = document.getElementById('job-details-content'); content.innerHTML = `

${job.fileName}

Size: ${job.fileSize}
Status: ${getStatusText(job.status)}
Processed: ${getTimeAgo(job.createdAt)}
${job.status === 'completed' ? `
Time: ${Wash.formatTime(job.processingTime)}
` : ''}
${job.status === 'completed' ? `

Threats Found

${job.threats.map(threat => `
${threat}
`).join('')}

Before/After

Original: ${job.metadata.originalSize} bytes
Cleaned: ${job.metadata.cleanedSize} bytes
Before (with threats):
This document‎ contains‎ zero-width‎ characters‎ and embedded‎ JavaScript code.
After (clean):
This document contains zero-width characters and embedded JavaScript code.

Download Clean Output

` : `
${job.status === 'processing' ? `

Processing your document...

` : `

Job details will appear here when processing is complete.

`}
`}
`; // Show panel panel.classList.remove('translate-x-full'); feather.replace(); } // Close job details panel function closeJobDetails() { const panel = document.getElementById('job-details-panel'); panel.classList.add('translate-x-full'); currentJob = null; } // Download output function downloadOutput(jobId, format) { const job = jobs.find(j => j.id == jobId); if (!job || !job.output[format]) return; // Create blob and download const blob = new Blob([job.output[format]], { type: format === 'pdf' ? 'application/pdf' : 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${job.fileName.split('.')[0]}_clean.${format}`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); Wash.showNotification(`Downloaded clean ${format.toUpperCase()} file`, 'success'); } // Copy cURL command function copyCurlCommand(jobId) { const job = jobs.find(j => j.id == jobId); if (!job) return; const curlCommand = `curl -X POST https://api.wash.dev/v1/clean \\ -H "Authorization: Bearer YOUR_API_KEY" \\ -F "file=@${job.fileName}"`; navigator.clipboard.writeText(curlCommand).then(() => { Wash.showNotification('cURL command copied to clipboard', 'success'); }); } // Export jobs to CSV function exportJobsToCSV() { const headers = ['File Name', 'Status', 'Threats', 'Processing Time', 'Created At']; const csvContent = [ headers.join(','), ...jobs.map(job => [ `"${job.fileName}"`, `"${getStatusText(job.status)}"`, `"${job.threats.join('; ')}"`, job.processingTime ? `"${Wash.formatTime(job.processingTime)}"` : '""', `"${job.createdAt.toISOString()}"` ].join(',')) ].join('\n'); const blob = new Blob([csvContent], { type: 'text/csv' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `wash-jobs-${new Date().toISOString().split('T')[0]}.csv`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }