feat: show import-pending red lozenge when Sonarr/Radarr has issues
All checks were successful
Build and Push Docker Image / build (push) Successful in 23s

- Detect trackedDownloadState=importPending or status=warning/error
- Extract statusMessages and errorMessage from queue records
- Display red 'Import Pending' badge on download card header
- Hover reveals tooltip with the specific issue messages
- Visible to all users (not admin-only)
This commit is contained in:
2026-05-15 23:23:25 +01:00
parent efa66b9fd6
commit eda9770f49
3 changed files with 71 additions and 0 deletions

View File

@@ -384,6 +384,14 @@ function createDownloadCard(download) {
header.appendChild(type); header.appendChild(type);
header.appendChild(status); header.appendChild(status);
if (download.importIssues && download.importIssues.length > 0) {
const issueBadge = document.createElement('span');
issueBadge.className = 'import-issue-badge';
issueBadge.textContent = 'Import Pending';
issueBadge.setAttribute('data-tooltip', download.importIssues.join('\n'));
header.appendChild(issueBadge);
}
const title = document.createElement('h3'); const title = document.createElement('h3');
title.className = 'download-title'; title.className = 'download-title';

View File

@@ -688,6 +688,38 @@ body {
color: var(--text-muted); color: var(--text-muted);
} }
/* ===== Import Issue Badge ===== */
.import-issue-badge {
padding: 2px 8px;
border-radius: 10px;
font-size: 0.65rem;
font-weight: 600;
background: #ffebee;
color: #c62828;
cursor: help;
position: relative;
white-space: nowrap;
}
.import-issue-badge:hover::after {
content: attr(data-tooltip);
position: absolute;
top: calc(100% + 6px);
left: 0;
background: #424242;
color: #fff;
padding: 8px 12px;
border-radius: 6px;
font-size: 0.7rem;
font-weight: 400;
white-space: pre-line;
max-width: 320px;
z-index: 100;
box-shadow: 0 2px 8px rgba(0,0,0,0.25);
line-height: 1.4;
pointer-events: none;
}
.download-user-badge { .download-user-badge {
padding: 2px 8px; padding: 2px 8px;
border-radius: 10px; border-radius: 10px;

View File

@@ -59,6 +59,29 @@ function tagMatchesUser(tag, username) {
return false; return false;
} }
// Extract import issues from a Sonarr/Radarr queue record
function getImportIssues(queueRecord) {
if (!queueRecord) return null;
const state = queueRecord.trackedDownloadState;
const status = queueRecord.trackedDownloadStatus;
if (state !== 'importPending' && status !== 'warning' && status !== 'error') return null;
const messages = [];
if (queueRecord.statusMessages && queueRecord.statusMessages.length > 0) {
for (const sm of queueRecord.statusMessages) {
if (sm.messages && sm.messages.length > 0) {
messages.push(...sm.messages);
} else if (sm.title) {
messages.push(sm.title);
}
}
}
if (queueRecord.errorMessage) {
messages.push(queueRecord.errorMessage);
}
if (messages.length === 0) return null;
return messages;
}
// Helper to build Sonarr web UI link for a series // Helper to build Sonarr web UI link for a series
function getSonarrLink(series) { function getSonarrLink(series) {
if (!series || !series._instanceUrl || !series.titleSlug) return null; if (!series || !series._instanceUrl || !series.titleSlug) return null;
@@ -355,6 +378,8 @@ router.get('/user-downloads', async (req, res) => {
episodeInfo: sonarrMatch, episodeInfo: sonarrMatch,
userTag: userTag userTag: userTag
}; };
const issues = getImportIssues(sonarrMatch);
if (issues) dlObj.importIssues = issues;
if (isAdmin) { if (isAdmin) {
dlObj.downloadPath = slot.storage || null; dlObj.downloadPath = slot.storage || null;
dlObj.targetPath = series.path || null; dlObj.targetPath = series.path || null;
@@ -391,6 +416,8 @@ router.get('/user-downloads', async (req, res) => {
movieInfo: radarrMatch, movieInfo: radarrMatch,
userTag: userTag userTag: userTag
}; };
const issues = getImportIssues(radarrMatch);
if (issues) dlObj.importIssues = issues;
if (isAdmin) { if (isAdmin) {
dlObj.downloadPath = slot.storage || null; dlObj.downloadPath = slot.storage || null;
dlObj.targetPath = movie.path || null; dlObj.targetPath = movie.path || null;
@@ -536,6 +563,8 @@ 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;
const sonarrIssues = getImportIssues(sonarrMatch);
if (sonarrIssues) download.importIssues = sonarrIssues;
if (isAdmin) { if (isAdmin) {
download.downloadPath = download.savePath || null; download.downloadPath = download.savePath || null;
download.targetPath = series.path || null; download.targetPath = series.path || null;
@@ -565,6 +594,8 @@ 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;
const radarrIssues = getImportIssues(radarrMatch);
if (radarrIssues) download.importIssues = radarrIssues;
if (isAdmin) { if (isAdmin) {
download.downloadPath = download.savePath || null; download.downloadPath = download.savePath || null;
download.targetPath = movie.path || null; download.targetPath = movie.path || null;