diff --git a/backend/package-lock.json b/backend/package-lock.json index da0dc57..888ef77 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster-backend", - "version": "0.12.0-13", + "version": "0.12.0-14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster-backend", - "version": "0.12.0-13", + "version": "0.12.0-14", "dependencies": { "archiver": "^7.0.1", "cors": "^2.8.5", diff --git a/backend/package.json b/backend/package.json index 67196d6..ff66423 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "ripster-backend", - "version": "0.12.0-13", + "version": "0.12.0-14", "private": true, "type": "commonjs", "scripts": { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index cff31e5..aa6d543 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster-frontend", - "version": "0.12.0-13", + "version": "0.12.0-14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster-frontend", - "version": "0.12.0-13", + "version": "0.12.0-14", "dependencies": { "primeicons": "^7.0.0", "primereact": "^10.9.2", diff --git a/frontend/package.json b/frontend/package.json index d7e487b..7bc80a5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "ripster-frontend", - "version": "0.12.0-13", + "version": "0.12.0-14", "private": true, "type": "module", "scripts": { diff --git a/frontend/src/components/PipelineStatusCard.jsx b/frontend/src/components/PipelineStatusCard.jsx index 58d273b..33a9be3 100644 --- a/frontend/src/components/PipelineStatusCard.jsx +++ b/frontend/src/components/PipelineStatusCard.jsx @@ -315,8 +315,7 @@ export default function PipelineStatusCard({ onRetry, onDeleteJob, isQueued = false, - busy, - liveJobLog = '' + busy }) { const state = pipeline?.state || 'IDLE'; const stateLabel = getStatusLabel(state); @@ -331,6 +330,7 @@ export default function PipelineStatusCard({ const reviewMode = String(mediaInfoReview?.mode || '').trim().toLowerCase(); const isPreRipReview = reviewMode === 'pre_rip' || Boolean(mediaInfoReview?.preRip); const jobMediaProfile = resolvePipelineMediaProfile(pipeline, mediaInfoReview); + const [liveJobLog, setLiveJobLog] = useState(''); const [selectedEncodeTitleId, setSelectedEncodeTitleId] = useState(null); const [selectedPlaylistId, setSelectedPlaylistId] = useState(null); const [selectedHandBrakeTitleIdState, setSelectedHandBrakeTitleIdState] = useState(null); @@ -592,6 +592,30 @@ export default function PipelineStatusCard({ pipeline?.context?.selectedPlaylist ]); + useEffect(() => { + if (!running || !retryJobId) { + setLiveJobLog(''); + return undefined; + } + let cancelled = false; + const refresh = async () => { + try { + const response = await api.getJob(retryJobId, { includeLiveLog: true, lite: true }); + if (!cancelled) { + setLiveJobLog(response?.job?.log || ''); + } + } catch (_err) { + // ignore transient polling errors + } + }; + void refresh(); + const interval = setInterval(refresh, 2500); + return () => { + cancelled = true; + clearInterval(interval); + }; + }, [running, retryJobId]); + const playlistDecisionRequiredBeforeStart = state === 'WAITING_FOR_USER_DECISION' && !handBrakeTitleDecisionRequired; const commandOutputPath = useMemo( () => buildOutputPathPreview(settingsMap, jobMediaProfile, selectedMetadata, retryJobId), diff --git a/frontend/src/pages/DashboardPage.jsx b/frontend/src/pages/DashboardPage.jsx index 6560b7e..9ae2925 100644 --- a/frontend/src/pages/DashboardPage.jsx +++ b/frontend/src/pages/DashboardPage.jsx @@ -933,7 +933,6 @@ export default function DashboardPage({ const [queueReorderBusy, setQueueReorderBusy] = useState(false); const [draggingQueueEntryId, setDraggingQueueEntryId] = useState(null); const [insertQueueDialog, setInsertQueueDialog] = useState({ visible: false, afterEntryId: null }); - const [liveJobLog, setLiveJobLog] = useState(''); const [runtimeActivities, setRuntimeActivities] = useState(() => normalizeRuntimeActivitiesPayload(null)); const [runtimeLoading, setRuntimeLoading] = useState(false); const [runtimeActionBusyKeys, setRuntimeActionBusyKeys] = useState(() => new Set()); @@ -960,15 +959,6 @@ export default function DashboardPage({ const state = String(pipeline?.state || 'IDLE').trim().toUpperCase(); const currentPipelineJobId = normalizeJobId(pipeline?.activeJobId || pipeline?.context?.jobId); const isProcessing = processingStates.includes(state); - // Always prefer the ENCODING job from dashboardJobs for the live log. - // When a second job enters ANALYZING, isProcessing becomes true (ANALYZING is a processingState) - // and currentPipelineJobId switches to job2 — but job1 is still ENCODING and its log must stay visible. - const encodingJobId = useMemo(() => { - const bg = dashboardJobs.find((job) => normalizeStatus(job?.status) === 'ENCODING'); - return bg ? normalizeJobId(bg.id) : null; - }, [dashboardJobs]); - const liveLogJobId = encodingJobId || currentPipelineJobId; - const liveLogActive = isProcessing || Boolean(encodingJobId); const monitoringState = useMemo( () => normalizeHardwareMonitoringPayload(hardwareMonitoring), [hardwareMonitoring] @@ -1314,31 +1304,6 @@ export default function DashboardPage({ return undefined; }, [audiobookUploadPhase]); - useEffect(() => { - if (!liveLogJobId || !liveLogActive) { - setLiveJobLog(''); - return undefined; - } - - let cancelled = false; - const refreshLiveLog = async () => { - try { - const response = await api.getJob(liveLogJobId, { includeLiveLog: true, lite: true }); - if (!cancelled) { - setLiveJobLog(response?.job?.log || ''); - } - } catch (_error) { - // ignore transient polling errors to avoid noisy toasts while background polling - } - }; - - void refreshLiveLog(); - const interval = setInterval(refreshLiveLog, 2500); - return () => { - cancelled = true; - clearInterval(interval); - }; - }, [liveLogJobId, liveLogActive]); const pipelineByJobId = useMemo(() => { const map = new Map(); @@ -2841,7 +2806,7 @@ export default function DashboardPage({ const statusBadgeValue = getStatusLabel(job?.status, { queued: isQueued }); const statusBadgeSeverity = getStatusSeverity(normalizedStatus, { queued: isQueued }); const isExpanded = normalizeJobId(expandedJobId) === jobId; - const isCurrentSession = liveLogJobId === jobId && (state !== 'IDLE' || liveLogActive); + const isCurrentSession = jobId === currentPipelineJobId && state !== 'IDLE'; const reviewConfirmed = Boolean(Number(job?.encode_review_confirmed || 0)); const pipelineForJob = pipelineByJobId.get(jobId) || pipeline; const jobTitle = job?.title || job?.detected_title || `Job #${jobId}`; @@ -2981,7 +2946,6 @@ export default function DashboardPage({ onRemoveFromQueue={handleRemoveQueuedJob} isQueued={isQueued} busy={busyJobIds.has(jobId)} - liveJobLog={isCurrentSession ? liveJobLog : ''} /> ) : null} diff --git a/package-lock.json b/package-lock.json index 1a4df55..c91eca7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster", - "version": "0.12.0-13", + "version": "0.12.0-14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster", - "version": "0.12.0-13", + "version": "0.12.0-14", "devDependencies": { "concurrently": "^9.1.2" } diff --git a/package.json b/package.json index 8169e10..7ed230e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ripster", "private": true, - "version": "0.12.0-13", + "version": "0.12.0-14", "scripts": { "dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"", "dev:backend": "npm run dev --prefix backend",