feat(servarr-api): make Servarr API request timeout configurable (#2556)

This commit is contained in:
fallenbagel
2026-02-23 04:32:31 +05:00
committed by GitHub
parent 5013d1d54d
commit 3bcb4da1e5
6 changed files with 50 additions and 23 deletions

View File

@@ -1,7 +1,7 @@
import ExternalAPI from '@server/api/externalapi';
import type { AvailableCacheIds } from '@server/lib/cache';
import cacheManager from '@server/lib/cache';
import type { DVRSettings } from '@server/lib/settings';
import { getSettings, type DVRSettings } from '@server/lib/settings';
export interface SystemStatus {
version: string;
@@ -92,14 +92,14 @@ class ServarrBase<QueueItemAppendT> extends ExternalAPI {
apiKey,
cacheName,
apiName,
timeout = 10000,
}: {
url: string;
apiKey: string;
cacheName: AvailableCacheIds;
apiName: string;
timeout?: number;
}) {
const timeout = getSettings().network.apiRequestTimeout;
super(
url,
{

View File

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

View File

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

View File

@@ -171,6 +171,7 @@ export interface NetworkSettings {
trustProxy: boolean;
proxy: ProxySettings;
dnsCache: DnsCacheSettings;
apiRequestTimeout: number;
}
interface PublicSettings {
@@ -593,6 +594,7 @@ class Settings {
forceMinTtl: 0,
forceMaxTtl: -1,
},
apiRequestTimeout: 10000,
},
migrations: [],
};

View File

@@ -56,6 +56,10 @@ const messages = defineMessages('components.Settings.SettingsNetwork', {
'Do NOT enable this if you are experiencing issues with DNS lookups',
dnsCacheForceMinTtl: 'DNS Cache Minimum TTL',
dnsCacheForceMaxTtl: 'DNS Cache Maximum TTL',
apiRequestTimeout: 'API Request Timeout',
apiRequestTimeoutTip:
'Maximum time (in seconds) to wait for responses from external services like Radarr/Sonarr. Set to 0 for no timeout.',
validationApiRequestTimeout: 'You must provide a valid timeout value',
});
const SettingsNetwork = () => {
@@ -91,6 +95,10 @@ const SettingsNetwork = () => {
.max(65535, intl.formatMessage(messages.validationProxyPort))
.required(intl.formatMessage(messages.validationProxyPort)),
}),
apiRequestTimeout: Yup.number()
.typeError(intl.formatMessage(messages.validationApiRequestTimeout))
.required(intl.formatMessage(messages.validationApiRequestTimeout))
.min(0, intl.formatMessage(messages.validationApiRequestTimeout)),
});
if (!data && !error) {
@@ -130,6 +138,10 @@ const SettingsNetwork = () => {
proxyPassword: data?.proxy?.password,
proxyBypassFilter: data?.proxy?.bypassFilter,
proxyBypassLocalAddresses: data?.proxy?.bypassLocalAddresses,
apiRequestTimeout:
data?.apiRequestTimeout !== undefined
? data.apiRequestTimeout / 1000
: 10,
}}
enableReinitialize
validationSchema={NetworkSettingsSchema}
@@ -154,6 +166,7 @@ const SettingsNetwork = () => {
bypassFilter: values.proxyBypassFilter,
bypassLocalAddresses: values.proxyBypassLocalAddresses,
},
apiRequestTimeout: Number(values.apiRequestTimeout) * 1000,
});
mutate('/api/v1/settings/public');
mutate('/api/v1/status');
@@ -341,6 +354,31 @@ const SettingsNetwork = () => {
</div>
</>
)}
<div className="form-row">
<label htmlFor="apiRequestTimeout" className="text-label">
<span className="mr-2">
{intl.formatMessage(messages.apiRequestTimeout)}
</span>
<SettingsBadge badgeType="restartRequired" />
<span className="label-tip">
{intl.formatMessage(messages.apiRequestTimeoutTip)}
</span>
</label>
<div className="form-input-area">
<Field
id="apiRequestTimeout"
name="apiRequestTimeout"
type="text"
inputMode="numeric"
className="short"
/>
</div>
{errors.apiRequestTimeout &&
touched.apiRequestTimeout &&
typeof errors.apiRequestTimeout === 'string' && (
<div className="error">{errors.apiRequestTimeout}</div>
)}
</div>
<div className="form-row">
<label htmlFor="proxyEnabled" className="checkbox-label">
<span className="mr-2">

View File

@@ -997,6 +997,8 @@
"components.Settings.SettingsMain.validationUrlTrailingSlash": "URL must not end in a trailing slash",
"components.Settings.SettingsMain.youtubeUrl": "YouTube URL",
"components.Settings.SettingsMain.youtubeUrlTip": "Base URL for YouTube videos if a self-hosted YouTube instance is used.",
"components.Settings.SettingsNetwork.apiRequestTimeout": "API Request Timeout",
"components.Settings.SettingsNetwork.apiRequestTimeoutTip": "Maximum time (in seconds) to wait for responses from external services like Radarr/Sonarr. Set to 0 for no timeout.",
"components.Settings.SettingsNetwork.csrfProtection": "Enable CSRF Protection",
"components.Settings.SettingsNetwork.csrfProtectionHoverTip": "Do NOT enable this setting unless you understand what you are doing!",
"components.Settings.SettingsNetwork.csrfProtectionTip": "Set external API access to read-only (requires HTTPS)",
@@ -1026,6 +1028,7 @@
"components.Settings.SettingsNetwork.toastSettingsSuccess": "Settings saved successfully!",
"components.Settings.SettingsNetwork.trustProxy": "Enable Proxy Support",
"components.Settings.SettingsNetwork.trustProxyTip": "Allow Seerr to correctly register client IP addresses behind a proxy",
"components.Settings.SettingsNetwork.validationApiRequestTimeout": "You must provide a valid timeout value",
"components.Settings.SettingsNetwork.validationDnsCacheMaxTtl": "You must provide a valid maximum TTL",
"components.Settings.SettingsNetwork.validationDnsCacheMinTtl": "You must provide a valid minimum TTL",
"components.Settings.SettingsNetwork.validationProxyPort": "You must provide a valid port",