diff --git a/core/downloads/monitor.py b/core/downloads/monitor.py index 472e521c..60ab4585 100644 --- a/core/downloads/monitor.py +++ b/core/downloads/monitor.py @@ -250,6 +250,11 @@ def requeue_quarantined_task_for_retry(task_id, batch_id, trigger): task.pop('quarantine_entry_id', None) task['status'] = 'searching' task['status_change_time'] = time.time() + # Surface the retry progress to the UI ("attempt 2/5" next to the + # status while the task goes around again). Cleared implicitly on + # completion (UI only renders it for active/queued states). + task['retry_info'] = attempt_desc + task['retry_trigger'] = trigger logger.info( f"[Retry:{trigger}] Re-queuing task {task_id} for next-best candidate " diff --git a/core/downloads/status.py b/core/downloads/status.py index c1c0eefd..4acd8248 100644 --- a/core/downloads/status.py +++ b/core/downloads/status.py @@ -343,6 +343,9 @@ def build_batch_status_data(batch_id: str, batch: dict, live_transfers_lookup: d # 'verified' / 'unverified' / 'force_imported' — set by the # import pipeline once post-processing finishes. 'verification_status': task.get('verification_status'), + # "2/5" while the quarantine-retry engine walks candidates. + 'retry_info': task.get('retry_info'), + 'retry_trigger': task.get('retry_trigger'), } _ti = task.get('track_info') if isinstance(task.get('track_info'), dict) else {} task_filename = task.get('filename') or _ti.get('filename') @@ -742,6 +745,8 @@ def build_unified_downloads_response(limit: int, deps: StatusDeps) -> dict: 'progress': progress, 'error': task.get('error_message'), 'verification_status': task.get('verification_status'), + 'retry_info': task.get('retry_info'), + 'retry_trigger': task.get('retry_trigger'), 'batch_id': batch_id, 'batch_name': batch.get('playlist_name') or batch.get('album_name') or '', 'batch_source': batch.get('source_page') or batch.get('initiated_from') or '', diff --git a/webui/index.html b/webui/index.html index ce1e38e1..d46ee112 100644 --- a/webui/index.html +++ b/webui/index.html @@ -2390,6 +2390,7 @@ +
diff --git a/webui/static/downloads.js b/webui/static/downloads.js index dad32572..ae58ee4e 100644 --- a/webui/static/downloads.js +++ b/webui/static/downloads.js @@ -3665,8 +3665,16 @@ function processModalStatusUpdate(playlistId, data) { } else { switch (task.status) { case 'pending': statusText = '⏸️ Pending'; break; - case 'searching': statusText = '🔍 Searching...'; break; - case 'downloading': statusText = `⏬ Downloading... ${Math.round(task.progress || 0)}%`; break; + case 'searching': + statusText = '🔍 Searching...'; + // Quarantine-retry engine: show which attempt we're on + // ("retry 2/5") while it walks the next-best candidates. + if (task.retry_info) statusText += ` 🔁 retry ${task.retry_info}`; + break; + case 'downloading': + statusText = `⏬ Downloading... ${Math.round(task.progress || 0)}%`; + if (task.retry_info) statusText += ` 🔁 retry ${task.retry_info}`; + break; case 'post_processing': statusText = '⌛ Processing...'; break; case 'completed': { statusText = '✅ Completed'; diff --git a/webui/static/pages-extra.js b/webui/static/pages-extra.js index 83483aa6..5581afe5 100644 --- a/webui/static/pages-extra.js +++ b/webui/static/pages-extra.js @@ -2384,6 +2384,9 @@ function _adlRender() { if (_adlFilter === 'active') filtered = filtered.filter(d => activeStatuses.includes(d.status)); else if (_adlFilter === 'queued') filtered = filtered.filter(d => queuedStatuses.includes(d.status)); else if (_adlFilter === 'completed') filtered = filtered.filter(d => completedStatuses.includes(d.status)); + else if (_adlFilter === 'unverified') filtered = filtered.filter(d => + completedStatuses.includes(d.status) && + (d.verification_status === 'unverified' || d.verification_status === 'force_imported')); else if (_adlFilter === 'failed') filtered = filtered.filter(d => failedStatuses.includes(d.status)); const completedN = _adlData.filter(d => @@ -2509,7 +2512,7 @@ function _adlRender() {
- ${statusLabel}${_adlVerifBadge(dl)} + ${statusLabel}${_adlVerifBadge(dl)}${dl.retry_info && (statusClass === 'active' || statusClass === 'queued') ? ` 🔁 ${_adlEsc(String(dl.retry_info))}` : ''}
${cancelBtnHtml} `; diff --git a/webui/static/style.css b/webui/static/style.css index 1a46aada..7e74308a 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -67898,3 +67898,4 @@ body.em-scroll-lock { overflow: hidden; } .verif-badge.verif-ok { color: #2ecc71; background: rgba(46,204,113,0.12); } .verif-badge.verif-unverified { color: #f1c40f; background: rgba(241,196,15,0.14); } .verif-badge.verif-force { color: #e67e22; background: rgba(230,126,34,0.16); } +.adl-retry-info { margin-left: 6px; font-size: 11px; color: #e67e22; cursor: help; }