feat: link series/movie titles to Sonarr/Radarr for admin users
All checks were successful
Build and Push Docker Image / build (push) Successful in 25s
All checks were successful
Build and Push Docker Image / build (push) Successful in 25s
- Series title links to Sonarr series page (/series/{titleSlug})
- Movie title links to Radarr movie page (/movie/{titleSlug})
- Links open in new tab, only shown for admin users
- Instance URL preserved through data aggregation for multi-instance support
This commit is contained in:
@@ -351,14 +351,22 @@ function createDownloadCard(download) {
|
|||||||
if (download.seriesName) {
|
if (download.seriesName) {
|
||||||
const series = document.createElement('p');
|
const series = document.createElement('p');
|
||||||
series.className = 'download-series';
|
series.className = 'download-series';
|
||||||
series.textContent = `Series: ${download.seriesName}`;
|
if (isAdmin && download.arrLink) {
|
||||||
|
series.innerHTML = 'Series: <a href="' + escapeHtml(download.arrLink) + '" target="_blank" class="arr-link">' + escapeHtml(download.seriesName) + '</a>';
|
||||||
|
} else {
|
||||||
|
series.textContent = `Series: ${download.seriesName}`;
|
||||||
|
}
|
||||||
infoDiv.appendChild(series);
|
infoDiv.appendChild(series);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (download.movieName) {
|
if (download.movieName) {
|
||||||
const movie = document.createElement('p');
|
const movie = document.createElement('p');
|
||||||
movie.className = 'download-movie';
|
movie.className = 'download-movie';
|
||||||
movie.textContent = `Movie: ${download.movieName}`;
|
if (isAdmin && download.arrLink) {
|
||||||
|
movie.innerHTML = 'Movie: <a href="' + escapeHtml(download.arrLink) + '" target="_blank" class="arr-link">' + escapeHtml(download.movieName) + '</a>';
|
||||||
|
} else {
|
||||||
|
movie.textContent = `Movie: ${download.movieName}`;
|
||||||
|
}
|
||||||
infoDiv.appendChild(movie);
|
infoDiv.appendChild(movie);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -602,6 +602,18 @@ body {
|
|||||||
accent-color: var(--accent);
|
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 (Admin) ===== */
|
||||||
.download-paths {
|
.download-paths {
|
||||||
flex-basis: 100%;
|
flex-basis: 100%;
|
||||||
|
|||||||
@@ -42,6 +42,18 @@ function extractUserTag(tags, tagMap) {
|
|||||||
return userTag ? userTag.label : null;
|
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
|
// Get user downloads for authenticated user
|
||||||
router.get('/user-downloads', async (req, res) => {
|
router.get('/user-downloads', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
@@ -216,7 +228,10 @@ router.get('/user-downloads', async (req, res) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const sonarrSeries = {
|
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 = {
|
const radarrQueue = {
|
||||||
data: {
|
data: {
|
||||||
@@ -229,7 +244,10 @@ router.get('/user-downloads', async (req, res) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const radarrMovies = {
|
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 = {
|
const radarrTags = {
|
||||||
data: radarrTagsResults.flatMap(t => t.data || [])
|
data: radarrTagsResults.flatMap(t => t.data || [])
|
||||||
@@ -322,6 +340,7 @@ router.get('/user-downloads', async (req, res) => {
|
|||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
dlObj.downloadPath = slot.storage || null;
|
dlObj.downloadPath = slot.storage || null;
|
||||||
dlObj.targetPath = series.path || null;
|
dlObj.targetPath = series.path || null;
|
||||||
|
dlObj.arrLink = getSonarrLink(series);
|
||||||
}
|
}
|
||||||
userDownloads.push(dlObj);
|
userDownloads.push(dlObj);
|
||||||
}
|
}
|
||||||
@@ -357,6 +376,7 @@ router.get('/user-downloads', async (req, res) => {
|
|||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
dlObj.downloadPath = slot.storage || null;
|
dlObj.downloadPath = slot.storage || null;
|
||||||
dlObj.targetPath = movie.path || null;
|
dlObj.targetPath = movie.path || null;
|
||||||
|
dlObj.arrLink = getRadarrLink(movie);
|
||||||
}
|
}
|
||||||
userDownloads.push(dlObj);
|
userDownloads.push(dlObj);
|
||||||
}
|
}
|
||||||
@@ -405,6 +425,7 @@ router.get('/user-downloads', async (req, res) => {
|
|||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
dlObj.downloadPath = slot.storage || null;
|
dlObj.downloadPath = slot.storage || null;
|
||||||
dlObj.targetPath = series.path || null;
|
dlObj.targetPath = series.path || null;
|
||||||
|
dlObj.arrLink = getSonarrLink(series);
|
||||||
}
|
}
|
||||||
userDownloads.push(dlObj);
|
userDownloads.push(dlObj);
|
||||||
}
|
}
|
||||||
@@ -436,6 +457,7 @@ router.get('/user-downloads', async (req, res) => {
|
|||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
dlObj.downloadPath = slot.storage || null;
|
dlObj.downloadPath = slot.storage || null;
|
||||||
dlObj.targetPath = movie.path || null;
|
dlObj.targetPath = movie.path || null;
|
||||||
|
dlObj.arrLink = getRadarrLink(movie);
|
||||||
}
|
}
|
||||||
userDownloads.push(dlObj);
|
userDownloads.push(dlObj);
|
||||||
}
|
}
|
||||||
@@ -499,6 +521,7 @@ router.get('/user-downloads', async (req, res) => {
|
|||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
download.downloadPath = download.savePath || null;
|
download.downloadPath = download.savePath || null;
|
||||||
download.targetPath = series.path || null;
|
download.targetPath = series.path || null;
|
||||||
|
download.arrLink = getSonarrLink(series);
|
||||||
}
|
}
|
||||||
userDownloads.push(download);
|
userDownloads.push(download);
|
||||||
continue; // Skip to next torrent
|
continue; // Skip to next torrent
|
||||||
@@ -527,6 +550,7 @@ router.get('/user-downloads', async (req, res) => {
|
|||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
download.downloadPath = download.savePath || null;
|
download.downloadPath = download.savePath || null;
|
||||||
download.targetPath = movie.path || null;
|
download.targetPath = movie.path || null;
|
||||||
|
download.arrLink = getRadarrLink(movie);
|
||||||
}
|
}
|
||||||
userDownloads.push(download);
|
userDownloads.push(download);
|
||||||
continue; // Skip to next torrent
|
continue; // Skip to next torrent
|
||||||
@@ -555,6 +579,7 @@ router.get('/user-downloads', async (req, res) => {
|
|||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
download.downloadPath = download.savePath || null;
|
download.downloadPath = download.savePath || null;
|
||||||
download.targetPath = series.path || null;
|
download.targetPath = series.path || null;
|
||||||
|
download.arrLink = getSonarrLink(series);
|
||||||
}
|
}
|
||||||
userDownloads.push(download);
|
userDownloads.push(download);
|
||||||
continue;
|
continue;
|
||||||
@@ -583,6 +608,7 @@ router.get('/user-downloads', async (req, res) => {
|
|||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
download.downloadPath = download.savePath || null;
|
download.downloadPath = download.savePath || null;
|
||||||
download.targetPath = movie.path || null;
|
download.targetPath = movie.path || null;
|
||||||
|
download.arrLink = getRadarrLink(movie);
|
||||||
}
|
}
|
||||||
userDownloads.push(download);
|
userDownloads.push(download);
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
Reference in New Issue
Block a user