fix(servarr): add timeout to Radarr/Sonarr API requests to prevent infinite loading (#2375)
* fix(servarr): add timeout to Radarr/Sonarr API requests to prevent infinite loading Adds a 5-second timeout to all Radarr/Sonarr API requests and displays a warning banner when services are unreachable. This prevents the Recent Requests section and request list pages from hanging indefinitely when a configured service has connection issues. fix #2374 * fix(requests): only show service error banner to users with advanced permissions
This commit is contained in:
@@ -1,14 +1,25 @@
|
||||
import { sliderTitles } from '@app/components/Discover/constants';
|
||||
import RequestCard from '@app/components/RequestCard';
|
||||
import Slider from '@app/components/Slider';
|
||||
import { ArrowRightCircleIcon } from '@heroicons/react/24/outline';
|
||||
import { Permission, useUser } from '@app/hooks/useUser';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import {
|
||||
ArrowRightCircleIcon,
|
||||
ExclamationTriangleIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import type { RequestResultsResponse } from '@server/interfaces/api/requestInterfaces';
|
||||
import Link from 'next/link';
|
||||
import { useIntl } from 'react-intl';
|
||||
import useSWR from 'swr';
|
||||
|
||||
const messages = defineMessages('components.Discover.RecentRequestsSlider', {
|
||||
unableToConnect:
|
||||
'Unable to connect to {services}. Some information may be unavailable.',
|
||||
});
|
||||
|
||||
const RecentRequestsSlider = () => {
|
||||
const intl = useIntl();
|
||||
const { hasPermission } = useUser();
|
||||
const { data: requests, error: requestError } =
|
||||
useSWR<RequestResultsResponse>(
|
||||
'/api/v1/request?filter=all&take=10&sort=modified&skip=0',
|
||||
@@ -21,6 +32,11 @@ const RecentRequestsSlider = () => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hasServiceErrors =
|
||||
requests?.serviceErrors &&
|
||||
(requests.serviceErrors.radarr.length > 0 ||
|
||||
requests.serviceErrors.sonarr.length > 0);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="slider-header">
|
||||
@@ -29,6 +45,23 @@ const RecentRequestsSlider = () => {
|
||||
<ArrowRightCircleIcon />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{hasServiceErrors &&
|
||||
(hasPermission(Permission.MANAGE_REQUESTS) ||
|
||||
hasPermission(Permission.REQUEST_ADVANCED)) && (
|
||||
<div className="service-error-banner">
|
||||
<ExclamationTriangleIcon className="h-5 w-5 flex-shrink-0" />
|
||||
<span>
|
||||
{intl.formatMessage(messages.unableToConnect, {
|
||||
services: [
|
||||
...requests.serviceErrors.radarr.map((s) => s.name),
|
||||
...requests.serviceErrors.sonarr.map((s) => s.name),
|
||||
].join(', '),
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Slider
|
||||
sliderKey="requests"
|
||||
isLoading={!requests}
|
||||
|
||||
@@ -5,9 +5,10 @@ import PageTitle from '@app/components/Common/PageTitle';
|
||||
import Tooltip from '@app/components/Common/Tooltip';
|
||||
import RequestItem from '@app/components/RequestList/RequestItem';
|
||||
import { useUpdateQueryParams } from '@app/hooks/useUpdateQueryParams';
|
||||
import { useUser } from '@app/hooks/useUser';
|
||||
import { Permission, useUser } from '@app/hooks/useUser';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { ExclamationTriangleIcon } from '@heroicons/react/24/outline';
|
||||
import {
|
||||
ArrowDownIcon,
|
||||
ArrowUpIcon,
|
||||
@@ -30,6 +31,8 @@ const messages = defineMessages('components.RequestList', {
|
||||
sortAdded: 'Most Recent',
|
||||
sortModified: 'Last Modified',
|
||||
sortDirection: 'Toggle Sort Direction',
|
||||
unableToConnect:
|
||||
'Unable to connect to {services}. Some information may be unavailable.',
|
||||
});
|
||||
|
||||
enum Filter {
|
||||
@@ -56,7 +59,7 @@ const RequestList = () => {
|
||||
const { user } = useUser({
|
||||
id: Number(router.query.userId),
|
||||
});
|
||||
const { user: currentUser } = useUser();
|
||||
const { user: currentUser, hasPermission } = useUser();
|
||||
const [currentFilter, setCurrentFilter] = useState<Filter>(Filter.PENDING);
|
||||
const [currentSort, setCurrentSort] = useState<Sort>('added');
|
||||
const [currentMediaType, setCurrentMediaType] = useState<string>('all');
|
||||
@@ -288,6 +291,25 @@ const RequestList = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{data.serviceErrors &&
|
||||
(data.serviceErrors.radarr.length > 0 ||
|
||||
data.serviceErrors.sonarr.length > 0) &&
|
||||
(hasPermission(Permission.MANAGE_REQUESTS) ||
|
||||
hasPermission(Permission.REQUEST_ADVANCED)) && (
|
||||
<div className="service-error-banner">
|
||||
<ExclamationTriangleIcon className="h-5 w-5 flex-shrink-0" />
|
||||
<span>
|
||||
{intl.formatMessage(messages.unableToConnect, {
|
||||
services: [
|
||||
...data.serviceErrors.radarr.map((s) => s.name),
|
||||
...data.serviceErrors.sonarr.map((s) => s.name),
|
||||
].join(', '),
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data.results.map((request) => {
|
||||
return (
|
||||
<div className="py-2" key={`request-list-${request.id}`}>
|
||||
|
||||
Reference in New Issue
Block a user