udpate add bulk update of TV eps
Local Docker Build (Dev) / build-dev (push) Successful in 5s

This commit is contained in:
2025-11-03 15:52:42 -05:00
parent 1e292434c0
commit 3e3957234d
4 changed files with 229 additions and 6 deletions
+1 -1
View File
@@ -1 +1 @@
2.9.7-status-format-fix
2.9.8-bulk-date-update
+72
View File
@@ -1574,6 +1574,78 @@ def register_web_routes(app, dependencies):
async def api_bulk_update_source(media_type: str, old_source: str, new_source: str):
return {"error": "Bulk operations not available in web container. Use core container on port 8085."}
@app.post("/api/episodes/bulk-update-dates")
async def api_bulk_update_episode_dates(request: Request):
"""Bulk update episode dates"""
try:
data = await request.json()
episodes = data.get('episodes', [])
date_source = data.get('date_source', 'airdate')
custom_date = data.get('custom_date')
if not episodes:
return {"success": False, "message": "No episodes provided"}
db = dependencies["db"]
sonarr = dependencies.get("sonarr_client")
updated_count = 0
failed_count = 0
for ep in episodes:
imdb_id = ep.get('imdb_id')
season = ep.get('season')
episode = ep.get('episode')
if not all([imdb_id, season is not None, episode is not None]):
failed_count += 1
continue
try:
dateadded = None
source = "manual"
if date_source == 'airdate':
# Get airdate from Sonarr
if sonarr:
episode_data = sonarr.get_episode(imdb_id, season, episode)
if episode_data and episode_data.get('airDateUtc'):
dateadded = episode_data['airDateUtc']
source = "sonarr:airdate"
elif date_source == 'import':
# Get import date from Sonarr history
if sonarr:
import_date = sonarr.get_episode_import_date(imdb_id, season, episode)
if import_date:
dateadded = import_date
source = "sonarr:import"
elif date_source == 'custom':
# Use custom date
if custom_date:
dateadded = custom_date
source = "manual:custom"
if dateadded:
db.upsert_episode_dates(imdb_id, season, episode, dateadded, source)
updated_count += 1
else:
failed_count += 1
except Exception as e:
print(f"❌ Error updating episode {imdb_id} S{season:02d}E{episode:02d}: {e}")
failed_count += 1
return {
"success": True,
"updated": updated_count,
"failed": failed_count,
"message": f"Updated {updated_count} episode(s), {failed_count} failed"
}
except Exception as e:
print(f"❌ Bulk update error: {e}")
return {"success": False, "message": str(e)}
# Reports
@app.get("/api/reports/missing-dates")
async def api_missing_dates_report():
+1 -1
View File
@@ -711,6 +711,6 @@
<!-- Toast Notifications -->
<div class="toast-container" id="toast-container"></div>
<script src="/static/js/app.js?v=2.9.7-status-format-fix"></script>
<script src="/static/js/app.js?v=2.9.8-bulk-date-update"></script>
</body>
</html>
+151
View File
@@ -509,10 +509,44 @@ function showEpisodesModal(data) {
<button id="bulk-select-all" class="btn btn-sm btn-secondary" onclick="toggleSelectAll()">
<i class="fas fa-check-square"></i> Select All
</button>
<button id="bulk-update-dates" class="btn btn-sm btn-primary" onclick="showBulkUpdateModal()" style="margin-left: 10px;" disabled>
<i class="fas fa-calendar"></i> Update Dates (<span id="update-count">0</span>)
</button>
<button id="bulk-delete-selected" class="btn btn-sm btn-danger" onclick="bulkDeleteSelected()" style="margin-left: 10px;" disabled>
<i class="fas fa-trash"></i> Delete Selected (<span id="selected-count">0</span>)
</button>
</div>
<!-- Bulk Update Dates Modal -->
<div id="bulk-update-modal" class="modal" style="display: none;">
<div class="modal-content" style="max-width: 500px;">
<div class="modal-header">
<h3>Bulk Update Episode Dates</h3>
<button class="close-button" onclick="closeBulkUpdateModal()">&times;</button>
</div>
<div class="modal-body">
<p>Update <strong><span id="modal-selected-count">0</span></strong> selected episode(s) to:</p>
<div class="form-group">
<label>Date Source:</label>
<select id="bulk-date-source" class="form-control">
<option value="airdate">Air Date (from Sonarr)</option>
<option value="import">Import Date (from Sonarr history)</option>
<option value="custom">Custom Date</option>
</select>
</div>
<div id="custom-date-input" style="display: none; margin-top: 10px;">
<label>Custom Date:</label>
<input type="datetime-local" id="bulk-custom-date" class="form-control">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeBulkUpdateModal()">Cancel</button>
<button class="btn btn-primary" onclick="executeBulkUpdate()">
<i class="fas fa-save"></i> Update Dates
</button>
</div>
</div>
</div>
<div class="table-container">
<table class="data-table sortable-table">
<thead>
@@ -1670,16 +1704,26 @@ function updateBulkDeleteButton() {
const selectedCheckboxes = document.querySelectorAll('.episode-checkbox:checked');
const selectedCount = selectedCheckboxes.length;
const bulkDeleteButton = document.getElementById('bulk-delete-selected');
const bulkUpdateButton = document.getElementById('bulk-update-dates');
const selectedCountSpan = document.getElementById('selected-count');
const updateCountSpan = document.getElementById('update-count');
if (selectedCountSpan) {
selectedCountSpan.textContent = selectedCount;
}
if (updateCountSpan) {
updateCountSpan.textContent = selectedCount;
}
if (bulkDeleteButton) {
bulkDeleteButton.disabled = selectedCount === 0;
}
if (bulkUpdateButton) {
bulkUpdateButton.disabled = selectedCount === 0;
}
// Update select all checkbox state
const selectAllCheckbox = document.getElementById('select-all-checkbox');
const allCheckboxes = document.querySelectorAll('.episode-checkbox');
@@ -1754,6 +1798,113 @@ async function bulkDeleteSelected() {
}
}
// Bulk update dates functions
function showBulkUpdateModal() {
const selectedCheckboxes = document.querySelectorAll('.episode-checkbox:checked');
const selectedCount = selectedCheckboxes.length;
if (selectedCount === 0) {
showToast('❌ No episodes selected', 'error');
return;
}
const modal = document.getElementById('bulk-update-modal');
const modalSelectedCount = document.getElementById('modal-selected-count');
const dateSourceSelect = document.getElementById('bulk-date-source');
const customDateInput = document.getElementById('custom-date-input');
if (modalSelectedCount) {
modalSelectedCount.textContent = selectedCount;
}
// Show/hide custom date input based on selection
if (dateSourceSelect) {
dateSourceSelect.onchange = function() {
if (customDateInput) {
customDateInput.style.display = this.value === 'custom' ? 'block' : 'none';
}
};
}
if (modal) {
modal.style.display = 'flex';
}
}
function closeBulkUpdateModal() {
const modal = document.getElementById('bulk-update-modal');
if (modal) {
modal.style.display = 'none';
}
}
async function executeBulkUpdate() {
const selectedCheckboxes = document.querySelectorAll('.episode-checkbox:checked');
const selectedCount = selectedCheckboxes.length;
const dateSource = document.getElementById('bulk-date-source').value;
const customDate = document.getElementById('bulk-custom-date').value;
if (selectedCount === 0) {
showToast('❌ No episodes selected', 'error');
return;
}
if (dateSource === 'custom' && !customDate) {
showToast('❌ Please enter a custom date', 'error');
return;
}
// Close modal
closeBulkUpdateModal();
// Collect selected episodes
const episodes = [];
selectedCheckboxes.forEach(checkbox => {
const row = checkbox.closest('tr');
episodes.push({
imdb_id: row.getAttribute('data-imdb'),
season: parseInt(row.getAttribute('data-season')),
episode: parseInt(row.getAttribute('data-episode'))
});
});
// Show progress
showToast(`⏳ Updating ${selectedCount} episode(s)...`, 'info');
try {
const response = await apiCall('/api/episodes/bulk-update-dates', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
episodes: episodes,
date_source: dateSource,
custom_date: customDate || null
})
});
if (response.success) {
showToast(`✅ Successfully updated ${response.updated || selectedCount} episode(s)`, 'success');
// Reload the current series to show updated dates
const currentImdbId = currentTVSeries;
if (currentImdbId) {
loadEpisodes(currentImdbId);
}
// Clear selections
selectedCheckboxes.forEach(checkbox => checkbox.checked = false);
updateBulkDeleteButton();
} else {
showToast(`${response.message || 'Failed to update episodes'}`, 'error');
}
} catch (error) {
console.error('Bulk update error:', error);
showToast('❌ Error updating episodes', 'error');
}
}
// ==================== Database Population Functions ====================
let populatePollingInterval = null;