diff --git a/public/app.js b/public/app.js index 88193cc..578bd0c 100644 --- a/public/app.js +++ b/public/app.js @@ -351,14 +351,22 @@ function createDownloadCard(download) { if (download.seriesName) { const series = document.createElement('p'); series.className = 'download-series'; - series.textContent = `Series: ${download.seriesName}`; + if (isAdmin && download.arrLink) { + series.innerHTML = 'Series: ' + escapeHtml(download.seriesName) + ''; + } else { + series.textContent = `Series: ${download.seriesName}`; + } infoDiv.appendChild(series); } if (download.movieName) { const movie = document.createElement('p'); movie.className = 'download-movie'; - movie.textContent = `Movie: ${download.movieName}`; + if (isAdmin && download.arrLink) { + movie.innerHTML = 'Movie: ' + escapeHtml(download.movieName) + ''; + } else { + movie.textContent = `Movie: ${download.movieName}`; + } infoDiv.appendChild(movie); } diff --git a/public/style.css b/public/style.css index be0284b..fc2c769 100644 --- a/public/style.css +++ b/public/style.css @@ -602,6 +602,18 @@ body { accent-color: var(--accent); } +/* ===== Arr Links (Admin) ===== */ +.arr-link { + color: var(--accent); + text-decoration: none; + border-bottom: 1px dotted var(--accent); +} + +.arr-link:hover { + opacity: 0.8; + border-bottom-style: solid; +} + /* ===== Download Paths (Admin) ===== */ .download-paths { flex-basis: 100%; diff --git a/server/routes/dashboard.js b/server/routes/dashboard.js index 3a37e74..d3dbe0c 100644 --- a/server/routes/dashboard.js +++ b/server/routes/dashboard.js @@ -42,6 +42,18 @@ function extractUserTag(tags, tagMap) { return userTag ? userTag.label : null; } +// Helper to build Sonarr web UI link for a series +function getSonarrLink(series) { + if (!series || !series._instanceUrl || !series.titleSlug) return null; + return `${series._instanceUrl}/series/${series.titleSlug}`; +} + +// Helper to build Radarr web UI link for a movie +function getRadarrLink(movie) { + if (!movie || !movie._instanceUrl || !movie.titleSlug) return null; + return `${movie._instanceUrl}/movie/${movie.titleSlug}`; +} + // Get user downloads for authenticated user router.get('/user-downloads', async (req, res) => { try { @@ -216,7 +228,10 @@ router.get('/user-downloads', async (req, res) => { } }; const sonarrSeries = { - data: sonarrSeriesResults.flatMap(s => s.data || []) + data: sonarrSeriesResults.flatMap(s => { + const inst = sonarrInstances.find(i => i.id === s.instance); + return (s.data || []).map(item => ({ ...item, _instanceUrl: inst ? inst.url : null })); + }) }; const radarrQueue = { data: { @@ -229,7 +244,10 @@ router.get('/user-downloads', async (req, res) => { } }; const radarrMovies = { - data: radarrMoviesResults.flatMap(m => m.data || []) + data: radarrMoviesResults.flatMap(m => { + const inst = radarrInstances.find(i => i.id === m.instance); + return (m.data || []).map(item => ({ ...item, _instanceUrl: inst ? inst.url : null })); + }) }; const radarrTags = { data: radarrTagsResults.flatMap(t => t.data || []) @@ -322,6 +340,7 @@ router.get('/user-downloads', async (req, res) => { if (isAdmin) { dlObj.downloadPath = slot.storage || null; dlObj.targetPath = series.path || null; + dlObj.arrLink = getSonarrLink(series); } userDownloads.push(dlObj); } @@ -357,6 +376,7 @@ router.get('/user-downloads', async (req, res) => { if (isAdmin) { dlObj.downloadPath = slot.storage || null; dlObj.targetPath = movie.path || null; + dlObj.arrLink = getRadarrLink(movie); } userDownloads.push(dlObj); } @@ -405,6 +425,7 @@ router.get('/user-downloads', async (req, res) => { if (isAdmin) { dlObj.downloadPath = slot.storage || null; dlObj.targetPath = series.path || null; + dlObj.arrLink = getSonarrLink(series); } userDownloads.push(dlObj); } @@ -436,6 +457,7 @@ router.get('/user-downloads', async (req, res) => { if (isAdmin) { dlObj.downloadPath = slot.storage || null; dlObj.targetPath = movie.path || null; + dlObj.arrLink = getRadarrLink(movie); } userDownloads.push(dlObj); } @@ -499,6 +521,7 @@ router.get('/user-downloads', async (req, res) => { if (isAdmin) { download.downloadPath = download.savePath || null; download.targetPath = series.path || null; + download.arrLink = getSonarrLink(series); } userDownloads.push(download); continue; // Skip to next torrent @@ -527,6 +550,7 @@ router.get('/user-downloads', async (req, res) => { if (isAdmin) { download.downloadPath = download.savePath || null; download.targetPath = movie.path || null; + download.arrLink = getRadarrLink(movie); } userDownloads.push(download); continue; // Skip to next torrent @@ -555,6 +579,7 @@ router.get('/user-downloads', async (req, res) => { if (isAdmin) { download.downloadPath = download.savePath || null; download.targetPath = series.path || null; + download.arrLink = getSonarrLink(series); } userDownloads.push(download); continue; @@ -583,6 +608,7 @@ router.get('/user-downloads', async (req, res) => { if (isAdmin) { download.downloadPath = download.savePath || null; download.targetPath = movie.path || null; + download.arrLink = getRadarrLink(movie); } userDownloads.push(download); continue;