Improve request handling and qBittorrent categories
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { authFetch, clearToken, getApiBase, getToken } from '../lib/auth'
|
||||
import AdminShell from '../ui/AdminShell'
|
||||
@@ -107,7 +107,7 @@ export default function SettingsPage({ section }: SettingsPageProps) {
|
||||
const [maintenanceStatus, setMaintenanceStatus] = useState<string | null>(null)
|
||||
const [maintenanceBusy, setMaintenanceBusy] = useState(false)
|
||||
|
||||
const loadSettings = async () => {
|
||||
const loadSettings = useCallback(async () => {
|
||||
const baseUrl = getApiBase()
|
||||
const response = await authFetch(`${baseUrl}/admin/settings`)
|
||||
if (!response.ok) {
|
||||
@@ -139,9 +139,9 @@ export default function SettingsPage({ section }: SettingsPageProps) {
|
||||
}
|
||||
setFormValues(initialValues)
|
||||
setStatus(null)
|
||||
}
|
||||
}, [router])
|
||||
|
||||
const loadArtworkPrefetchStatus = async () => {
|
||||
const loadArtworkPrefetchStatus = useCallback(async () => {
|
||||
try {
|
||||
const baseUrl = getApiBase()
|
||||
const response = await authFetch(`${baseUrl}/admin/requests/artwork/status`)
|
||||
@@ -153,10 +153,9 @@ export default function SettingsPage({ section }: SettingsPageProps) {
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
const loadOptions = async (service: 'sonarr' | 'radarr') => {
|
||||
const loadOptions = useCallback(async (service: 'sonarr' | 'radarr') => {
|
||||
try {
|
||||
const baseUrl = getApiBase()
|
||||
const response = await authFetch(`${baseUrl}/admin/${service}/options`)
|
||||
@@ -185,7 +184,7 @@ export default function SettingsPage({ section }: SettingsPageProps) {
|
||||
setRadarrError('Could not load Radarr options.')
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
@@ -213,7 +212,7 @@ export default function SettingsPage({ section }: SettingsPageProps) {
|
||||
if (section === 'radarr') {
|
||||
void loadOptions('radarr')
|
||||
}
|
||||
}, [router, section])
|
||||
}, [loadArtworkPrefetchStatus, loadOptions, loadSettings, router, section])
|
||||
|
||||
const groupedSettings = useMemo(() => {
|
||||
const groups: Record<string, AdminSetting[]> = {}
|
||||
@@ -271,10 +270,12 @@ export default function SettingsPage({ section }: SettingsPageProps) {
|
||||
sonarr_api_key: 'API key for Sonarr.',
|
||||
sonarr_quality_profile_id: 'Quality profile used when adding TV shows.',
|
||||
sonarr_root_folder: 'Root folder where Sonarr stores TV shows.',
|
||||
sonarr_qbittorrent_category: 'qBittorrent category for manual Sonarr downloads.',
|
||||
radarr_base_url: 'Radarr server URL for movies.',
|
||||
radarr_api_key: 'API key for Radarr.',
|
||||
radarr_quality_profile_id: 'Quality profile used when adding movies.',
|
||||
radarr_root_folder: 'Root folder where Radarr stores movies.',
|
||||
radarr_qbittorrent_category: 'qBittorrent category for manual Radarr downloads.',
|
||||
prowlarr_base_url: 'Prowlarr server URL for indexer searches.',
|
||||
prowlarr_api_key: 'API key for Prowlarr.',
|
||||
qbittorrent_base_url: 'qBittorrent server URL for download status.',
|
||||
@@ -472,7 +473,7 @@ export default function SettingsPage({ section }: SettingsPageProps) {
|
||||
active = false
|
||||
clearInterval(timer)
|
||||
}
|
||||
}, [artworkPrefetch?.status])
|
||||
}, [artworkPrefetch])
|
||||
|
||||
useEffect(() => {
|
||||
if (!artworkPrefetch || artworkPrefetch.status === 'running') {
|
||||
@@ -482,7 +483,7 @@ export default function SettingsPage({ section }: SettingsPageProps) {
|
||||
setArtworkPrefetch(null)
|
||||
}, 5000)
|
||||
return () => clearTimeout(timer)
|
||||
}, [artworkPrefetch?.status])
|
||||
}, [artworkPrefetch])
|
||||
|
||||
useEffect(() => {
|
||||
if (!requestsSync || requestsSync.status !== 'running') {
|
||||
@@ -510,7 +511,7 @@ export default function SettingsPage({ section }: SettingsPageProps) {
|
||||
active = false
|
||||
clearInterval(timer)
|
||||
}
|
||||
}, [requestsSync?.status])
|
||||
}, [requestsSync])
|
||||
|
||||
useEffect(() => {
|
||||
if (!requestsSync || requestsSync.status === 'running') {
|
||||
@@ -520,9 +521,9 @@ export default function SettingsPage({ section }: SettingsPageProps) {
|
||||
setRequestsSync(null)
|
||||
}, 5000)
|
||||
return () => clearTimeout(timer)
|
||||
}, [requestsSync?.status])
|
||||
}, [requestsSync])
|
||||
|
||||
const loadLogs = async () => {
|
||||
const loadLogs = useCallback(async () => {
|
||||
setLogsStatus(null)
|
||||
try {
|
||||
const baseUrl = getApiBase()
|
||||
@@ -547,7 +548,7 @@ export default function SettingsPage({ section }: SettingsPageProps) {
|
||||
: 'Could not load logs.'
|
||||
setLogsStatus(message)
|
||||
}
|
||||
}
|
||||
}, [logsCount])
|
||||
|
||||
useEffect(() => {
|
||||
if (!showLogs) {
|
||||
@@ -558,7 +559,7 @@ export default function SettingsPage({ section }: SettingsPageProps) {
|
||||
void loadLogs()
|
||||
}, 5000)
|
||||
return () => clearInterval(timer)
|
||||
}, [logsCount, showLogs])
|
||||
}, [loadLogs, showLogs])
|
||||
|
||||
const loadCache = async () => {
|
||||
setCacheStatus(null)
|
||||
|
||||
@@ -132,7 +132,7 @@ export default function HowItWorksPage() {
|
||||
</section>
|
||||
|
||||
<section className="how-callout">
|
||||
<h2>Why Magent sometimes says "waiting"</h2>
|
||||
<h2>Why Magent sometimes says "waiting"</h2>
|
||||
<p>
|
||||
If the search helper cannot find a match yet, Magent will say there is nothing to grab.
|
||||
That does not mean it is broken. It usually means the release is not available yet.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import Image from 'next/image'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { authFetch, clearToken, getApiBase, getToken } from '../../lib/auth'
|
||||
@@ -33,6 +34,7 @@ type ReleaseOption = {
|
||||
seeders?: number
|
||||
leechers?: number
|
||||
protocol?: string
|
||||
publishDate?: string
|
||||
infoUrl?: string
|
||||
downloadUrl?: string
|
||||
}
|
||||
@@ -123,7 +125,7 @@ const friendlyState = (value: string) => {
|
||||
const map: Record<string, string> = {
|
||||
REQUESTED: 'Waiting for approval',
|
||||
APPROVED: 'Approved and queued',
|
||||
NEEDS_ADD: 'Needs adding to the library',
|
||||
NEEDS_ADD: 'Push to Sonarr/Radarr',
|
||||
ADDED_TO_ARR: 'Added to the library queue',
|
||||
SEARCHING: 'Searching for releases',
|
||||
GRABBED: 'Download queued',
|
||||
@@ -155,7 +157,7 @@ const friendlyTimelineStatus = (service: string, status: string) => {
|
||||
}
|
||||
if (service === 'Sonarr/Radarr') {
|
||||
const map: Record<string, string> = {
|
||||
missing: 'Not added yet',
|
||||
missing: 'Push to Sonarr/Radarr',
|
||||
added: 'Added to the library queue',
|
||||
searching: 'Searching for releases',
|
||||
available: 'Ready to watch',
|
||||
@@ -250,7 +252,7 @@ export default function RequestTimelinePage({ params }: { params: { id: string }
|
||||
}
|
||||
|
||||
load()
|
||||
}, [params.id])
|
||||
}, [params.id, router])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -274,9 +276,11 @@ export default function RequestTimelinePage({ params }: { params: { id: string }
|
||||
const downloadHop = snapshot.timeline.find((hop) => hop.service === 'qBittorrent')
|
||||
const downloadState = downloadHop?.details?.summary ?? downloadHop?.status ?? 'Unknown'
|
||||
const jellyfinAvailable = Boolean(snapshot.raw?.jellyfin?.available)
|
||||
const arrStageLabel =
|
||||
snapshot.state === 'NEEDS_ADD' ? 'Push to Sonarr/Radarr' : 'Library queue'
|
||||
const pipelineSteps = [
|
||||
{ key: 'Jellyseerr', label: 'Jellyseerr' },
|
||||
{ key: 'Sonarr/Radarr', label: 'Library queue' },
|
||||
{ key: 'Sonarr/Radarr', label: arrStageLabel },
|
||||
{ key: 'Prowlarr', label: 'Search' },
|
||||
{ key: 'qBittorrent', label: 'Download' },
|
||||
{ key: 'Jellyfin', label: 'Jellyfin' },
|
||||
@@ -308,11 +312,14 @@ export default function RequestTimelinePage({ params }: { params: { id: string }
|
||||
<div className="request-header">
|
||||
<div className="request-header-main">
|
||||
{resolvedPoster && (
|
||||
<img
|
||||
<Image
|
||||
className="request-poster"
|
||||
src={resolvedPoster}
|
||||
alt={`${snapshot.title} poster`}
|
||||
loading="lazy"
|
||||
width={90}
|
||||
height={135}
|
||||
sizes="90px"
|
||||
unoptimized
|
||||
/>
|
||||
)}
|
||||
<div>
|
||||
@@ -592,6 +599,12 @@ export default function RequestTimelinePage({ params }: { params: { id: string }
|
||||
indexerId: release.indexerId,
|
||||
indexerName: release.indexer,
|
||||
downloadUrl: release.downloadUrl,
|
||||
title: release.title,
|
||||
size: release.size,
|
||||
protocol: release.protocol,
|
||||
publishDate: release.publishDate,
|
||||
seeders: release.seeders,
|
||||
leechers: release.leechers,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user