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:
fallenbagel
2026-02-07 01:38:21 +05:00
committed by GitHub
parent a0a784b976
commit faa2c0a005
9 changed files with 109 additions and 7 deletions

View File

@@ -92,11 +92,13 @@ class ServarrBase<QueueItemAppendT> extends ExternalAPI {
apiKey,
cacheName,
apiName,
timeout = 5000,
}: {
url: string;
apiKey: string;
cacheName: AvailableCacheIds;
apiName: string;
timeout?: number;
}) {
super(
url,
@@ -105,6 +107,7 @@ class ServarrBase<QueueItemAppendT> extends ExternalAPI {
},
{
nodeCache: cacheManager.getCache(cacheName).data,
timeout,
}
);

View File

@@ -64,8 +64,16 @@ export interface RadarrMovie {
}
class RadarrAPI extends ServarrBase<{ movieId: number }> {
constructor({ url, apiKey }: { url: string; apiKey: string }) {
super({ url, apiKey, cacheName: 'radarr', apiName: 'Radarr' });
constructor({
url,
apiKey,
timeout,
}: {
url: string;
apiKey: string;
timeout?: number;
}) {
super({ url, apiKey, cacheName: 'radarr', apiName: 'Radarr', timeout });
}
public getMovies = async (): Promise<RadarrMovie[]> => {

View File

@@ -111,8 +111,16 @@ class SonarrAPI extends ServarrBase<{
episodeId: number;
episode: EpisodeResult;
}> {
constructor({ url, apiKey }: { url: string; apiKey: string }) {
super({ url, apiKey, apiName: 'Sonarr', cacheName: 'sonarr' });
constructor({
url,
apiKey,
timeout,
}: {
url: string;
apiKey: string;
timeout?: number;
}) {
super({ url, apiKey, apiName: 'Sonarr', cacheName: 'sonarr', timeout });
}
public async getSeries(): Promise<SonarrSeries[]> {

View File

@@ -7,6 +7,10 @@ export interface RequestResultsResponse extends PaginatedResponse {
profileName?: string;
canRemove?: boolean;
})[];
serviceErrors: {
radarr: { id: number; name: string }[];
sonarr: { id: number; name: string }[];
};
}
export type MediaRequestBody = {

View File

@@ -275,6 +275,24 @@ requestRoutes.get<Record<string, unknown>, RequestResultsResponse>(
page: Math.ceil(skip / pageSize) + 1,
},
results: mappedRequests,
serviceErrors: {
radarr: radarrServers
.filter((s) => !s.profiles)
.map((s) => ({
id: s.id,
name:
settings.radarr.find((r) => r.id === s.id)?.name ||
`Radarr ${s.id}`,
})),
sonarr: sonarrServers
.filter((s) => !s.profiles)
.map((s) => ({
id: s.id,
name:
settings.sonarr.find((r) => r.id === s.id)?.name ||
`Sonarr ${s.id}`,
})),
},
});
} catch (e) {
next({ status: 500, message: e.message });