Files

50 lines
3.2 KiB
JavaScript

/* ── Downloads page ─────────────────────────────────────────── */
function escH(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }
function fmtSpeed(b) { return b>1048576?(b/1048576).toFixed(1)+' MB/s':b>1024?(b/1024).toFixed(0)+' KB/s':b+' B/s'; }
function fmtEta(s) {
if (s < 0) return '';
if (s < 60) return s + 's';
if (s < 3600) return Math.floor(s/60) + 'm ' + (s%60) + 's';
return Math.floor(s/3600) + 'h ' + Math.floor((s%3600)/60) + 'm';
}
function renderDownloads() {
fetch(BASE + '/api/downloads', {cache: 'no-store'})
.then(function(r) { return r.json(); })
.then(function(dl) {
var el = document.getElementById('dl-table');
if (!dl.length) { el.innerHTML = '<p style="color:var(--dim)">No downloads yet.</p>'; return; }
var html = '<table><tr><th></th><th>Name</th><th>Status</th><th>Progress</th><th>Speed</th><th>ETA</th><th></th></tr>';
dl.slice().reverse().forEach(function(d) {
var pct = d.pct || 0, mb = (d.downloaded/1048576).toFixed(1);
var cls = d.status==='done' ? 'done' : d.status.startsWith('error') ? 'err' : d.status==='downloading' ? 'dl' : '';
var thumb = d.cover_url
? '<img src="' + escH(d.cover_url) + '" style="width:32px;height:48px;object-fit:cover;border-radius:3px" onerror="this.style.display=\'none\'">'
: '<span style="color:var(--dim);font-size:18px">&#9723;</span>';
html += '<tr><td>' + thumb + '</td><td>' + escH(d.name) + '</td><td class=' + cls + '>' + escH(d.status) + '</td><td>';
if (d.status === 'downloading')
html += '<div class=pbar><div class=pfill style="width:' + pct + '%"></div></div>&nbsp;' + pct + '% (' + mb + 'MB)';
else html += pct + '%';
html += '</td><td>' + (d.status==='downloading'&&d.speed_bps>0 ? fmtSpeed(d.speed_bps) : '—') + '</td>';
html += '<td>' + (d.status==='downloading'&&d.eta_s>=0 ? fmtEta(d.eta_s) : '—') + '</td><td>';
if (d.status==='downloading' || d.status==='queued')
html += '<button class="btn btn-s" onclick="cancelDl(\'' + d.id + '\')" style="padding:2px 8px">✕</button>';
html += '</td></tr>';
});
html += '</table>';
el.innerHTML = html;
}).catch(function() {});
}
function cancelDl(id) {
fetch(BASE + '/api/cancel', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({id:id})})
.then(function() { setTimeout(renderDownloads, 400); });
}
function clearDl() { fetch(BASE + '/api/clear', {method:'POST'}).then(function() { setTimeout(renderDownloads, 400); }); }
function clearCancelled() { fetch(BASE + '/api/clear-cancelled', {method:'POST'}).then(function() { setTimeout(renderDownloads, 400); }); }
function retryInterrupted() { fetch(BASE + '/api/retry-interrupted', {method:'POST'}).then(function() { setTimeout(renderDownloads, 400); }); }
function cleanPartials() { fetch(BASE + '/api/clean-partials', {method:'POST'}).then(function() { renderDownloads(); }); }
renderDownloads();
setInterval(renderDownloads, 3000);