Manual Scan
+Run manual scans on your media library
+ +Custom Directory Scan
+Scan a specific directory or path
+ +Bulk Source Update
Change source for multiple items at once
diff --git a/nfoguard-web/static/js/app.js b/nfoguard-web/static/js/app.js index aa1b7e2..f37e9f5 100644 --- a/nfoguard-web/static/js/app.js +++ b/nfoguard-web/static/js/app.js @@ -76,6 +76,13 @@ function initializeEventListeners() { // Forms document.getElementById('edit-form').addEventListener('submit', handleEditSubmit); document.getElementById('bulk-update-form').addEventListener('submit', handleBulkUpdate); + + // Manual scan forms + document.getElementById('manual-scan-form').addEventListener('submit', handleManualScan); + document.getElementById('custom-scan-form').addEventListener('submit', handleCustomScan); + + // Custom directory input auto-formatting + document.getElementById('custom-directory').addEventListener('input', handleDirectoryFormatting); } // API calls @@ -1434,4 +1441,187 @@ async function logout() { console.error('Logout failed:', error); showToast('❌ Logout error', 'error'); } +} + +// =========================== +// Manual Scan Functions +// =========================== + +async function handleManualScan(event) { + event.preventDefault(); + + const scanType = document.getElementById('scan-type').value; + const scanMode = document.getElementById('scan-mode').value; + + if (!scanType || !scanMode) { + showToast('Please select both scan type and mode', 'warning'); + return; + } + + try { + showToast('🔍 Starting manual scan...', 'info'); + updateScanStatus('Starting manual scan...', true); + + const result = await apiCall('/manual/scan', { + method: 'POST', + body: JSON.stringify({ + scan_type: scanType, + scan_mode: scanMode + }) + }); + + showToast(`✅ Manual scan initiated: ${result.message}`, 'success'); + + // Start polling for scan status + startScanStatusPolling(); + + } catch (error) { + console.error('Manual scan failed:', error); + showToast(`❌ Manual scan failed: ${error.message}`, 'error'); + updateScanStatus('Scan failed', false); + } +} + +async function handleCustomScan(event) { + event.preventDefault(); + + const customDirectory = document.getElementById('custom-directory').value.trim(); + const customScanMode = document.getElementById('custom-scan-mode').value; + + if (!customDirectory) { + showToast('Please enter a directory path', 'warning'); + return; + } + + if (!customScanMode) { + showToast('Please select a scan mode', 'warning'); + return; + } + + try { + showToast('🔍 Starting custom directory scan...', 'info'); + updateScanStatus('Starting custom directory scan...', true); + + const result = await apiCall('/manual/scan', { + method: 'POST', + body: JSON.stringify({ + directory: customDirectory, + scan_mode: customScanMode + }) + }); + + showToast(`✅ Custom scan initiated: ${result.message}`, 'success'); + + // Start polling for scan status + startScanStatusPolling(); + + } catch (error) { + console.error('Custom scan failed:', error); + showToast(`❌ Custom scan failed: ${error.message}`, 'error'); + updateScanStatus('Scan failed', false); + } +} + +function handleDirectoryFormatting(event) { + const input = event.target; + let value = input.value; + + // Auto-format common directory patterns + if (value && !value.startsWith('/')) { + // Add leading slash for absolute paths + value = '/' + value; + input.value = value; + } + + // Remove multiple consecutive slashes + value = value.replace(/\/+/g, '/'); + + // Remove trailing slash unless it's just '/' + if (value.length > 1 && value.endsWith('/')) { + value = value.slice(0, -1); + } + + input.value = value; +} + +// =========================== +// Scan Status Functions +// =========================== + +let scanStatusInterval = null; + +function updateScanStatus(message, isActive = false) { + const statusBanner = document.getElementById('scan-status-banner'); + const statusText = document.getElementById('scan-status-text'); + + if (!statusBanner || !statusText) { + console.log('Scan status elements not found, skipping update'); + return; + } + + statusText.textContent = message; + + if (isActive) { + statusBanner.className = 'scan-status-banner active'; + statusBanner.style.display = 'block'; + } else { + statusBanner.className = 'scan-status-banner'; + // Don't hide completely, just mark as inactive + setTimeout(() => { + if (statusBanner.className === 'scan-status-banner') { + statusBanner.style.display = 'none'; + } + }, 3000); + } +} + +async function checkScanStatus() { + try { + const status = await apiCall('/api/scan/status'); + + if (status.scanning) { + updateScanStatus(status.message || 'Scan in progress...', true); + return true; // Continue polling + } else { + updateScanStatus(status.message || 'No scan in progress', false); + return false; // Stop polling + } + + } catch (error) { + console.error('Failed to check scan status:', error); + updateScanStatus('Unable to check scan status', false); + return false; // Stop polling on error + } +} + +function startScanStatusPolling() { + // Clear any existing interval + if (scanStatusInterval) { + clearInterval(scanStatusInterval); + } + + // Check status every 2 seconds + scanStatusInterval = setInterval(async () => { + const shouldContinue = await checkScanStatus(); + + if (!shouldContinue) { + clearInterval(scanStatusInterval); + scanStatusInterval = null; + + // Refresh dashboard data after scan completes + if (currentTab === 'dashboard') { + setTimeout(loadDashboard, 1000); + } + } + }, 2000); + + // Initial status check + checkScanStatus(); +} + +function stopScanStatusPolling() { + if (scanStatusInterval) { + clearInterval(scanStatusInterval); + scanStatusInterval = null; + } } \ No newline at end of file