Reconcile verified account IDs and make language repairs observable
Magent CI/CD / verify (push) Successful in 1m50s
Magent CI/CD / deploy-prod (push) Skipped
Magent CI/CD / deploy-beta (push) Skipped

This commit is contained in:
2026-09-11 19:56:32 +12:00
parent 38169b881e
commit de25255ea8
25 changed files with 593 additions and 160 deletions
+26 -12
View File
@@ -1,6 +1,8 @@
'use client'
import PageHeading from '../../ui/PageHeading'
import RequestLanguage from './RequestLanguage'
import { lockBodyScroll } from '../../lib/scrollLock'
import LatestActivity from './LatestActivity'
import Image from 'next/image'
@@ -339,14 +341,13 @@ export default function RequestTimelinePage() {
useEffect(() => {
if (!releasePickerOpen) return
const previousOverflow = document.body.style.overflow
const unlock = lockBodyScroll()
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') closeReleasePicker()
}
document.body.style.overflow = 'hidden'
window.addEventListener('keydown', handleKeyDown)
return () => {
document.body.style.overflow = previousOverflow
unlock()
window.removeEventListener('keydown', handleKeyDown)
}
}, [releasePickerOpen, busyAction])
@@ -588,14 +589,16 @@ export default function RequestTimelinePage() {
const timer = window.setInterval(() => void refreshProgress(), 650)
try {
const response = await request
const finalProgress = await refreshProgress()
if (!finalProgress || finalProgress.status === 'running') {
setOperationProgress((current) => current?.id === operationId ? {
...current, status: response.ok ? 'complete' : 'error',
events: [...current.events, { id: 'result', service: 'Magent', state: response.ok ? 'complete' : 'error',
message: response.ok ? 'This action has finished. Check the request status for what happens next.' : 'This action could not be completed. Check the message beside the request controls.' }],
} : current)
}
await refreshProgress()
let result: any = null
try { result = await response.clone().json() } catch { /* Non-JSON error is handled below. */ }
const needsAttention = !response.ok || result?.status === 'attention' || result?.outcome === 'attention'
const finalState = needsAttention ? 'error' : result?.status === 'searching' ? 'searching' : 'complete'
setOperationProgress((current) => current?.id === operationId ? {
...current, status: finalState,
events: [...current.events.map(event => event.state === 'active' ? { ...event, state: 'complete' as const } : event), { id: 'result', service: 'Magent', state: needsAttention ? 'error' : 'complete',
message: result?.message || (typeof result?.detail === 'string' ? result.detail : response.ok ? 'Action completed. The pipeline will update as the media services report progress.' : 'The action failed. Recheck the request before trying again.') }],
} : current)
return response
} catch (error) {
setOperationProgress((current) => current?.id === operationId ? {
@@ -660,7 +663,7 @@ export default function RequestTimelinePage() {
setReleaseOptions([])
setReleaseCollector(snapshot.request_type === 'tv' ? 'Sonarr' : 'Radarr')
setReleaseSearchMessage(null)
setReleasePickerOpen(true)
setReleasePickerOpen(false)
}
setBusyAction(action.id)
setActionError(null)
@@ -680,6 +683,7 @@ export default function RequestTimelinePage() {
if (action.id === 'search_releases') {
const releases = Array.isArray(data.releases) ? data.releases : []
setReleaseOptions(releases)
setReleasePickerOpen(true)
setReleaseCollector(data?.collector ?? (snapshot.request_type === 'tv' ? 'Sonarr' : 'Radarr'))
setReleaseSearchMessage(
data?.message ??
@@ -744,6 +748,16 @@ export default function RequestTimelinePage() {
leading={resolvedPoster && <Image className="request-poster" src={resolvedPoster} alt={`${snapshot.title} poster`} width={60} height={90} sizes="60px" unoptimized />}
/>
<RequestLanguage requestId={snapshot.request_id} disabled={Boolean(busyAction)} onApply={async (code) => {
setBusyAction('language')
try {
const response = await trackedPost('Use original audio and search', `${getApiBase()}/requests/${snapshot.request_id}/actions/language`, {
headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ acceptOriginalLanguage: true, languageCode: code }),
})
if (!response.ok) throw new Error(await readApiError(response, 'The audio choice could not be saved.'))
} finally { setBusyAction(null) }
}} />
<section className="request-overview" aria-labelledby="request-status-heading">
<div className="request-overview-block request-overview-status">
<span className="request-overview-label" id="request-status-heading">Status</span>