feat: show download/target paths for admin users

- Admin users see download path (SABnzbd storage / qBittorrent save_path)
- Admin users see target path (Sonarr series folder / Radarr movie folder)
- Paths displayed in monospace font at bottom of card details
- Non-admin users unaffected (paths not sent in API response)
This commit is contained in:
2026-05-15 21:07:19 +01:00
parent 8b526aa13b
commit b75cd18580
4 changed files with 99 additions and 8 deletions

View File

@@ -459,6 +459,24 @@ function createDownloadCard(download) {
details.appendChild(completed); details.appendChild(completed);
} }
if (isAdmin && (download.downloadPath || download.targetPath)) {
const pathsDiv = document.createElement('div');
pathsDiv.className = 'download-paths';
if (download.downloadPath) {
const dlPath = document.createElement('div');
dlPath.className = 'path-item';
dlPath.innerHTML = '<span class="path-label">Download:</span> <span class="path-value">' + escapeHtml(download.downloadPath) + '</span>';
pathsDiv.appendChild(dlPath);
}
if (download.targetPath) {
const tgtPath = document.createElement('div');
tgtPath.className = 'path-item';
tgtPath.innerHTML = '<span class="path-label">Target:</span> <span class="path-value">' + escapeHtml(download.targetPath) + '</span>';
pathsDiv.appendChild(tgtPath);
}
details.appendChild(pathsDiv);
}
infoDiv.appendChild(details); infoDiv.appendChild(details);
card.appendChild(infoDiv); card.appendChild(infoDiv);
@@ -484,6 +502,12 @@ function createDetailItem(label, value) {
return item; return item;
} }
function escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
function formatSize(size) { function formatSize(size) {
if (!size) return 'N/A'; if (!size) return 'N/A';
// If already a formatted string (e.g., "21.5 GB"), return as-is // If already a formatted string (e.g., "21.5 GB"), return as-is

View File

@@ -602,6 +602,36 @@ body {
accent-color: var(--accent); accent-color: var(--accent);
} }
/* ===== Download Paths (Admin) ===== */
.download-paths {
flex-basis: 100%;
display: flex;
flex-direction: column;
gap: 2px;
margin-top: 2px;
}
.path-item {
font-size: 0.7rem;
font-family: 'SF Mono', 'Fira Code', 'Fira Mono', Menlo, Consolas, monospace;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.path-label {
font-weight: 600;
color: var(--text-secondary);
font-size: 0.65rem;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.path-value {
color: var(--text-muted);
}
.download-user-badge { .download-user-badge {
padding: 2px 8px; padding: 2px 8px;
border-radius: 10px; border-radius: 10px;

View File

@@ -304,7 +304,7 @@ router.get('/user-downloads', async (req, res) => {
if (series) { if (series) {
const userTag = extractUserTag(series.tags, sonarrTagMap); const userTag = extractUserTag(series.tags, sonarrTagMap);
if (userTag && (showAll || userTag.toLowerCase() === username)) { if (userTag && (showAll || userTag.toLowerCase() === username)) {
userDownloads.push({ const dlObj = {
type: 'series', type: 'series',
title: nzbName, title: nzbName,
coverArt: getCoverArt(series), coverArt: getCoverArt(series),
@@ -318,7 +318,12 @@ router.get('/user-downloads', async (req, res) => {
seriesName: series.title, seriesName: series.title,
episodeInfo: sonarrMatch, episodeInfo: sonarrMatch,
userTag: userTag userTag: userTag
}); };
if (isAdmin) {
dlObj.downloadPath = slot.storage || null;
dlObj.targetPath = series.path || null;
}
userDownloads.push(dlObj);
} }
} }
} }
@@ -334,7 +339,7 @@ router.get('/user-downloads', async (req, res) => {
if (movie) { if (movie) {
const userTag = extractUserTag(movie.tags, radarrTagMap); const userTag = extractUserTag(movie.tags, radarrTagMap);
if (userTag && (showAll || userTag.toLowerCase() === username)) { if (userTag && (showAll || userTag.toLowerCase() === username)) {
userDownloads.push({ const dlObj = {
type: 'movie', type: 'movie',
title: nzbName, title: nzbName,
coverArt: getCoverArt(movie), coverArt: getCoverArt(movie),
@@ -348,7 +353,12 @@ router.get('/user-downloads', async (req, res) => {
movieName: movie.title, movieName: movie.title,
movieInfo: radarrMatch, movieInfo: radarrMatch,
userTag: userTag userTag: userTag
}); };
if (isAdmin) {
dlObj.downloadPath = slot.storage || null;
dlObj.targetPath = movie.path || null;
}
userDownloads.push(dlObj);
} }
} }
} }
@@ -381,7 +391,7 @@ router.get('/user-downloads', async (req, res) => {
if (series) { if (series) {
const userTag = extractUserTag(series.tags, sonarrTagMap); const userTag = extractUserTag(series.tags, sonarrTagMap);
if (userTag && (showAll || userTag.toLowerCase() === username)) { if (userTag && (showAll || userTag.toLowerCase() === username)) {
userDownloads.push({ const dlObj = {
type: 'series', type: 'series',
title: nzbName, title: nzbName,
coverArt: getCoverArt(series), coverArt: getCoverArt(series),
@@ -391,7 +401,12 @@ router.get('/user-downloads', async (req, res) => {
seriesName: series.title, seriesName: series.title,
episodeInfo: sonarrMatch, episodeInfo: sonarrMatch,
userTag: userTag userTag: userTag
}); };
if (isAdmin) {
dlObj.downloadPath = slot.storage || null;
dlObj.targetPath = series.path || null;
}
userDownloads.push(dlObj);
} }
} }
} }
@@ -407,7 +422,7 @@ router.get('/user-downloads', async (req, res) => {
if (movie) { if (movie) {
const userTag = extractUserTag(movie.tags, radarrTagMap); const userTag = extractUserTag(movie.tags, radarrTagMap);
if (userTag && (showAll || userTag.toLowerCase() === username)) { if (userTag && (showAll || userTag.toLowerCase() === username)) {
userDownloads.push({ const dlObj = {
type: 'movie', type: 'movie',
title: nzbName, title: nzbName,
coverArt: getCoverArt(movie), coverArt: getCoverArt(movie),
@@ -417,7 +432,12 @@ router.get('/user-downloads', async (req, res) => {
movieName: movie.title, movieName: movie.title,
movieInfo: radarrMatch, movieInfo: radarrMatch,
userTag: userTag userTag: userTag
}); };
if (isAdmin) {
dlObj.downloadPath = slot.storage || null;
dlObj.targetPath = movie.path || null;
}
userDownloads.push(dlObj);
} }
} }
} }
@@ -476,6 +496,10 @@ router.get('/user-downloads', async (req, res) => {
download.seriesName = series.title; download.seriesName = series.title;
download.episodeInfo = sonarrMatch; download.episodeInfo = sonarrMatch;
download.userTag = userTag; download.userTag = userTag;
if (isAdmin) {
download.downloadPath = download.savePath || null;
download.targetPath = series.path || null;
}
userDownloads.push(download); userDownloads.push(download);
continue; // Skip to next torrent continue; // Skip to next torrent
} }
@@ -500,6 +524,10 @@ router.get('/user-downloads', async (req, res) => {
download.movieName = movie.title; download.movieName = movie.title;
download.movieInfo = radarrMatch; download.movieInfo = radarrMatch;
download.userTag = userTag; download.userTag = userTag;
if (isAdmin) {
download.downloadPath = download.savePath || null;
download.targetPath = movie.path || null;
}
userDownloads.push(download); userDownloads.push(download);
continue; // Skip to next torrent continue; // Skip to next torrent
} }
@@ -524,6 +552,10 @@ router.get('/user-downloads', async (req, res) => {
download.seriesName = series.title; download.seriesName = series.title;
download.episodeInfo = sonarrHistoryMatch; download.episodeInfo = sonarrHistoryMatch;
download.userTag = userTag; download.userTag = userTag;
if (isAdmin) {
download.downloadPath = download.savePath || null;
download.targetPath = series.path || null;
}
userDownloads.push(download); userDownloads.push(download);
continue; continue;
} }
@@ -548,6 +580,10 @@ router.get('/user-downloads', async (req, res) => {
download.movieName = movie.title; download.movieName = movie.title;
download.movieInfo = radarrHistoryMatch; download.movieInfo = radarrHistoryMatch;
download.userTag = userTag; download.userTag = userTag;
if (isAdmin) {
download.downloadPath = download.savePath || null;
download.targetPath = movie.path || null;
}
userDownloads.push(download); userDownloads.push(download);
continue; continue;
} }

View File

@@ -198,6 +198,7 @@ function mapTorrentToDownload(torrent) {
hash: torrent.hash, hash: torrent.hash,
category: torrent.category, category: torrent.category,
tags: torrent.tags, tags: torrent.tags,
savePath: torrent.save_path || torrent.content_path || null,
qbittorrent: true qbittorrent: true
}; };
} }