diff --git a/nfoguard-web/static/index.html b/nfoguard-web/static/index.html index d0bd51c..9e59a36 100644 --- a/nfoguard-web/static/index.html +++ b/nfoguard-web/static/index.html @@ -443,6 +443,39 @@ Refresh Stats + +
+

Populate Database

+

Bulk import data from Radarr/Sonarr into NFOGuard database

+
+
+ + +
+ +
+ +
diff --git a/nfoguard-web/static/js/app.js b/nfoguard-web/static/js/app.js index d6e8e5c..883d7ee 100644 --- a/nfoguard-web/static/js/app.js +++ b/nfoguard-web/static/js/app.js @@ -77,6 +77,7 @@ function initializeEventListeners() { document.getElementById('edit-form').addEventListener('submit', handleEditSubmit); document.getElementById('bulk-update-form').addEventListener('submit', handleBulkUpdate); document.getElementById('manual-scan-form').addEventListener('submit', handleManualScan); + document.getElementById('populate-form').addEventListener('submit', handlePopulateDatabase); } // API calls @@ -1728,11 +1729,11 @@ async function bulkDeleteSelected() { // Update UI updateEpisodeModalCounts(); updateBulkDeleteButton(); - + // Reset button bulkDeleteButton.innerHTML = originalText; bulkDeleteButton.disabled = true; - + // Show results if (successCount > 0 && failCount === 0) { showToast(`✅ Successfully deleted ${successCount} episode(s)`, 'success'); @@ -1742,3 +1743,212 @@ async function bulkDeleteSelected() { showToast(`❌ Failed to delete ${failCount} episode(s)`, 'error'); } } + +// ==================== Database Population Functions ==================== + +let populatePollingInterval = null; + +async function handlePopulateDatabase(event) { + event.preventDefault(); + + const mediaType = document.getElementById('populate-media-type').value; + const submitButton = event.target.querySelector('button[type="submit"]'); + const originalText = submitButton.innerHTML; + + // Confirm action + const confirmMsg = mediaType === 'both' + ? 'This will populate the database with ALL movies and TV episodes from Radarr/Sonarr. Continue?' + : mediaType === 'movies' + ? 'This will populate the database with ALL movies from Radarr. Continue?' + : 'This will populate the database with ALL TV episodes from Sonarr. Continue?'; + + if (!confirm(confirmMsg)) { + return; + } + + // Disable button and show loading + submitButton.disabled = true; + submitButton.innerHTML = ' Starting...'; + + try { + const response = await fetch('/admin/populate-database', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ media_type: mediaType }) + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + if (data.status === 'started') { + showPopulateStatus(); + startPopulatePolling(); + } else { + throw new Error('Failed to start population'); + } + + } catch (error) { + console.error('Error starting population:', error); + showToast('❌ Failed to start database population', 'error'); + submitButton.disabled = false; + submitButton.innerHTML = originalText; + } +} + +function showPopulateStatus() { + const statusDiv = document.getElementById('populate-status'); + const resultsDiv = document.getElementById('populate-results'); + + if (statusDiv) { + statusDiv.style.display = 'block'; + statusDiv.querySelector('.progress-fill').style.width = '0%'; + statusDiv.querySelector('.progress-text').textContent = 'Starting...'; + } + + if (resultsDiv) { + resultsDiv.style.display = 'none'; + } +} + +function hidePopulateStatus() { + const statusDiv = document.getElementById('populate-status'); + if (statusDiv) { + statusDiv.style.display = 'none'; + } +} + +function startPopulatePolling() { + // Clear any existing interval + if (populatePollingInterval) { + clearInterval(populatePollingInterval); + } + + // Poll every 2 seconds + populatePollingInterval = setInterval(async () => { + try { + const response = await fetch('/api/populate/status'); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const status = await response.json(); + updatePopulateProgress(status); + + // Stop polling if complete or error + if (status.status === 'completed' || status.status === 'error') { + stopPopulatePolling(); + + // Re-enable submit button + const submitButton = document.querySelector('#populate-form button[type="submit"]'); + if (submitButton) { + submitButton.disabled = false; + submitButton.innerHTML = ' Start Population'; + } + } + + } catch (error) { + console.error('Error polling populate status:', error); + stopPopulatePolling(); + + // Re-enable submit button + const submitButton = document.querySelector('#populate-form button[type="submit"]'); + if (submitButton) { + submitButton.disabled = false; + submitButton.innerHTML = ' Start Population'; + } + } + }, 2000); +} + +function stopPopulatePolling() { + if (populatePollingInterval) { + clearInterval(populatePollingInterval); + populatePollingInterval = null; + } +} + +function updatePopulateProgress(status) { + const progressFill = document.querySelector('#populate-status .progress-fill'); + const progressText = document.querySelector('#populate-status .progress-text'); + const resultsDiv = document.getElementById('populate-results'); + + if (status.status === 'in_progress') { + if (progressFill && progressText) { + // Show indeterminate progress + progressFill.style.width = '50%'; + progressText.textContent = 'Populating database...'; + } + } else if (status.status === 'completed') { + if (progressFill && progressText) { + progressFill.style.width = '100%'; + progressText.textContent = 'Complete!'; + } + + // Show results + if (resultsDiv && status.result) { + const result = status.result; + let html = '

Population Results

'; + + // Movies + if (result.movies) { + const m = result.movies; + html += '
'; + html += '
Movies
'; + html += ''; + html += '
'; + } + + // TV Episodes + if (result.tv) { + const tv = result.tv; + html += '
'; + html += '
TV Shows
'; + html += ''; + html += '
'; + } + + // Total duration + if (result.total_duration) { + html += `

Total Duration: ${result.total_duration.toFixed(2)}s

`; + } + + resultsDiv.innerHTML = html; + resultsDiv.style.display = 'block'; + } + + showToast('✅ Database population completed successfully', 'success'); + + } else if (status.status === 'error') { + if (progressFill && progressText) { + progressFill.style.width = '100%'; + progressFill.style.backgroundColor = '#dc3545'; + progressText.textContent = 'Error!'; + } + + const errorMsg = status.error || 'Unknown error occurred'; + showToast(`❌ Database population failed: ${errorMsg}`, 'error'); + + if (resultsDiv) { + resultsDiv.innerHTML = `

Error: ${errorMsg}

`; + resultsDiv.style.display = 'block'; + } + } +}