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
+
+
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 += `- Total found: ${m.total || 0}
`;
+ html += `- Added: ${m.added || 0}
`;
+ html += `- Skipped: ${m.skipped || 0}
`;
+ html += `- Errors: ${m.errors || 0}
`;
+ html += `- Duration: ${(m.duration || 0).toFixed(2)}s
`;
+ html += '
';
+ html += '
';
+ }
+
+ // TV Episodes
+ if (result.tv) {
+ const tv = result.tv;
+ html += '';
+ html += '
TV Shows
';
+ html += '
';
+ html += `- Series: ${tv.total_series || 0}
`;
+ html += `- Episodes: ${tv.total_episodes || 0}
`;
+ html += `- Added: ${tv.added || 0}
`;
+ html += `- Skipped: ${tv.skipped || 0}
`;
+ html += `- Errors: ${tv.errors || 0}
`;
+ html += `- Duration: ${(tv.duration || 0).toFixed(2)}s
`;
+ 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';
+ }
+ }
+}