Add Admin section to System: reclaim space from unwatched shows
All checks were successful
Deploy / deploy (push) Successful in 48s
All checks were successful
Deploy / deploy (push) Successful in 48s
Cross-references Sonarr's per-series disk usage against Tautulli's full play history to surface the 10 largest shows nobody has ever watched, excluding anything added in the last 6 months so new downloads aren't flagged before they've had a chance to be watched. Delete removes the series from Sonarr along with its episode files and folder, behind the site's usual two-tap arm/confirm rather than a native dialog. Also dedupes the never-wired /api/tautulli/never-watched endpoint onto the same helper, and clears the Sonarr/Radarr list caches on delete so a removed title doesn't linger in other cards until the TTL expires. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LPJxtE9tPURsaWDZ6rC4Dt
This commit is contained in:
parent
d38ab1b561
commit
cad4cd0a84
3 changed files with 209 additions and 55 deletions
|
|
@ -234,6 +234,75 @@
|
|||
armed = btn;
|
||||
};
|
||||
|
||||
/* ── Admin: delete unwatched (P105) ───────────────────
|
||||
Removing a series from Sonarr with its files is permanent,
|
||||
so it shares the same two-tap arming as everything else here
|
||||
rather than a native confirm(). Each row carries its own byte
|
||||
count, so the reclaimable total in the field meta can be
|
||||
recomputed client-side after a delete instead of going stale
|
||||
until the next page load. */
|
||||
function fmtBytes(b) {
|
||||
if (b >= 1073741824) return (b / 1073741824).toFixed(1) + ' GB';
|
||||
return (b / 1048576).toFixed(0) + ' MB';
|
||||
}
|
||||
|
||||
function settleAdmin() {
|
||||
var list = document.getElementById('admin-rows');
|
||||
var meta = document.getElementById('admin-meta');
|
||||
var rows = list ? list.querySelectorAll('.tt-row') : [];
|
||||
var total = 0;
|
||||
for (var i = 0; i < rows.length; i++) total += parseInt(rows[i].dataset.bytes, 10) || 0;
|
||||
if (meta) meta.textContent = rows.length + ' unwatched · ' + fmtBytes(total) + ' reclaimable';
|
||||
if (rows.length === 0 && list) {
|
||||
var empty = document.createElement('p');
|
||||
empty.className = 'tt-empty';
|
||||
empty.id = 'admin-empty';
|
||||
empty.textContent = 'Nothing left to reclaim.';
|
||||
list.parentNode.replaceChild(empty, list);
|
||||
}
|
||||
}
|
||||
|
||||
window.armDelete = function (btn) {
|
||||
if (btn.disabled) return;
|
||||
if (armed && armed !== btn) disarm();
|
||||
|
||||
if (armed === btn) {
|
||||
var row = btn.closest('.tt-row');
|
||||
btn.disabled = true;
|
||||
btn.classList.remove('armed');
|
||||
btn.textContent = 'DELETING';
|
||||
armed = null;
|
||||
|
||||
fetch(BASE + '/api/delete/show', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ sonarr_id: btn.dataset.id })
|
||||
})
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (d) {
|
||||
if (d && d.success) {
|
||||
if (row && row.parentNode) row.parentNode.removeChild(row);
|
||||
settleAdmin();
|
||||
} else {
|
||||
btn.disabled = false;
|
||||
btn.classList.add('armed');
|
||||
btn.textContent = 'FAILED';
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
btn.disabled = false;
|
||||
btn.classList.add('armed');
|
||||
btn.textContent = 'FAILED';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
btn.dataset.label = btn.textContent.trim();
|
||||
btn.classList.add('armed');
|
||||
btn.textContent = 'CONFIRM?';
|
||||
armed = btn;
|
||||
};
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
if (armed && !armed.contains(e.target)) disarm();
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue