updates to new phase
Local Docker Build (Dev) / build-dev (push) Successful in 5s

This commit is contained in:
2025-11-02 20:23:31 -05:00
parent 97a9f3431a
commit d414da5bf3
2 changed files with 245 additions and 2 deletions
+33
View File
@@ -443,6 +443,39 @@
<i class="fas fa-sync"></i> Refresh Stats <i class="fas fa-sync"></i> Refresh Stats
</button> </button>
</div> </div>
<div class="tool-card">
<h3><i class="fas fa-upload"></i> Populate Database</h3>
<p>Bulk import data from Radarr/Sonarr into NFOGuard database</p>
<form id="populate-form">
<div class="form-group">
<label>Media Type:</label>
<select id="populate-media-type" required>
<option value="both">Movies & TV Shows</option>
<option value="movies">Movies Only</option>
<option value="tv">TV Shows Only</option>
</select>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-play"></i> Start Population
</button>
</form>
<div id="populate-status" class="scan-status" style="display: none;">
<div class="scan-progress">
<div class="progress-bar">
<div class="progress-fill" id="populate-progress-bar"></div>
</div>
<div class="scan-info">
<span id="populate-current-operation">Running...</span>
<span id="populate-progress-text">In Progress</span>
</div>
</div>
<div id="populate-results" class="stats-display" style="margin-top: 10px;"></div>
<button class="btn btn-secondary btn-sm" onclick="stopPopulatePolling()">
<i class="fas fa-times"></i> Hide Status
</button>
</div>
</div>
</div> </div>
</div> </div>
</main> </main>
+212 -2
View File
@@ -77,6 +77,7 @@ function initializeEventListeners() {
document.getElementById('edit-form').addEventListener('submit', handleEditSubmit); document.getElementById('edit-form').addEventListener('submit', handleEditSubmit);
document.getElementById('bulk-update-form').addEventListener('submit', handleBulkUpdate); document.getElementById('bulk-update-form').addEventListener('submit', handleBulkUpdate);
document.getElementById('manual-scan-form').addEventListener('submit', handleManualScan); document.getElementById('manual-scan-form').addEventListener('submit', handleManualScan);
document.getElementById('populate-form').addEventListener('submit', handlePopulateDatabase);
} }
// API calls // API calls
@@ -1728,11 +1729,11 @@ async function bulkDeleteSelected() {
// Update UI // Update UI
updateEpisodeModalCounts(); updateEpisodeModalCounts();
updateBulkDeleteButton(); updateBulkDeleteButton();
// Reset button // Reset button
bulkDeleteButton.innerHTML = originalText; bulkDeleteButton.innerHTML = originalText;
bulkDeleteButton.disabled = true; bulkDeleteButton.disabled = true;
// Show results // Show results
if (successCount > 0 && failCount === 0) { if (successCount > 0 && failCount === 0) {
showToast(`✅ Successfully deleted ${successCount} episode(s)`, 'success'); showToast(`✅ Successfully deleted ${successCount} episode(s)`, 'success');
@@ -1742,3 +1743,212 @@ async function bulkDeleteSelected() {
showToast(`❌ Failed to delete ${failCount} episode(s)`, 'error'); 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 = '<i class="fas fa-spinner fa-spin"></i> 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 = '<i class="fas fa-play"></i> 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 = '<i class="fas fa-play"></i> 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 = '<h4>Population Results</h4>';
// Movies
if (result.movies) {
const m = result.movies;
html += '<div class="populate-section">';
html += '<h5><i class="fas fa-film"></i> Movies</h5>';
html += '<ul>';
html += `<li>Total found: ${m.total || 0}</li>`;
html += `<li>Added: ${m.added || 0}</li>`;
html += `<li>Skipped: ${m.skipped || 0}</li>`;
html += `<li>Errors: ${m.errors || 0}</li>`;
html += `<li>Duration: ${(m.duration || 0).toFixed(2)}s</li>`;
html += '</ul>';
html += '</div>';
}
// TV Episodes
if (result.tv) {
const tv = result.tv;
html += '<div class="populate-section">';
html += '<h5><i class="fas fa-tv"></i> TV Shows</h5>';
html += '<ul>';
html += `<li>Series: ${tv.total_series || 0}</li>`;
html += `<li>Episodes: ${tv.total_episodes || 0}</li>`;
html += `<li>Added: ${tv.added || 0}</li>`;
html += `<li>Skipped: ${tv.skipped || 0}</li>`;
html += `<li>Errors: ${tv.errors || 0}</li>`;
html += `<li>Duration: ${(tv.duration || 0).toFixed(2)}s</li>`;
html += '</ul>';
html += '</div>';
}
// Total duration
if (result.total_duration) {
html += `<p><strong>Total Duration:</strong> ${result.total_duration.toFixed(2)}s</p>`;
}
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 = `<p class="error">Error: ${errorMsg}</p>`;
resultsDiv.style.display = 'block';
}
}
}