feat(servarr-api): make Servarr API request timeout configurable (#2556)
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import ExternalAPI from '@server/api/externalapi';
|
import ExternalAPI from '@server/api/externalapi';
|
||||||
import type { AvailableCacheIds } from '@server/lib/cache';
|
import type { AvailableCacheIds } from '@server/lib/cache';
|
||||||
import cacheManager 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 {
|
export interface SystemStatus {
|
||||||
version: string;
|
version: string;
|
||||||
@@ -92,14 +92,14 @@ class ServarrBase<QueueItemAppendT> extends ExternalAPI {
|
|||||||
apiKey,
|
apiKey,
|
||||||
cacheName,
|
cacheName,
|
||||||
apiName,
|
apiName,
|
||||||
timeout = 10000,
|
|
||||||
}: {
|
}: {
|
||||||
url: string;
|
url: string;
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
cacheName: AvailableCacheIds;
|
cacheName: AvailableCacheIds;
|
||||||
apiName: string;
|
apiName: string;
|
||||||
timeout?: number;
|
|
||||||
}) {
|
}) {
|
||||||
|
const timeout = getSettings().network.apiRequestTimeout;
|
||||||
|
|
||||||
super(
|
super(
|
||||||
url,
|
url,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -64,16 +64,8 @@ export interface RadarrMovie {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class RadarrAPI extends ServarrBase<{ movieId: number }> {
|
class RadarrAPI extends ServarrBase<{ movieId: number }> {
|
||||||
constructor({
|
constructor({ url, apiKey }: { url: string; apiKey: string }) {
|
||||||
url,
|
super({ url, apiKey, cacheName: 'radarr', apiName: 'Radarr' });
|
||||||
apiKey,
|
|
||||||
timeout,
|
|
||||||
}: {
|
|
||||||
url: string;
|
|
||||||
apiKey: string;
|
|
||||||
timeout?: number;
|
|
||||||
}) {
|
|
||||||
super({ url, apiKey, cacheName: 'radarr', apiName: 'Radarr', timeout });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getMovies = async (): Promise<RadarrMovie[]> => {
|
public getMovies = async (): Promise<RadarrMovie[]> => {
|
||||||
|
|||||||
@@ -111,16 +111,8 @@ class SonarrAPI extends ServarrBase<{
|
|||||||
episodeId: number;
|
episodeId: number;
|
||||||
episode: EpisodeResult;
|
episode: EpisodeResult;
|
||||||
}> {
|
}> {
|
||||||
constructor({
|
constructor({ url, apiKey }: { url: string; apiKey: string }) {
|
||||||
url,
|
super({ url, apiKey, apiName: 'Sonarr', cacheName: 'sonarr' });
|
||||||
apiKey,
|
|
||||||
timeout,
|
|
||||||
}: {
|
|
||||||
url: string;
|
|
||||||
apiKey: string;
|
|
||||||
timeout?: number;
|
|
||||||
}) {
|
|
||||||
super({ url, apiKey, apiName: 'Sonarr', cacheName: 'sonarr', timeout });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getSeries(): Promise<SonarrSeries[]> {
|
public async getSeries(): Promise<SonarrSeries[]> {
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ export interface NetworkSettings {
|
|||||||
trustProxy: boolean;
|
trustProxy: boolean;
|
||||||
proxy: ProxySettings;
|
proxy: ProxySettings;
|
||||||
dnsCache: DnsCacheSettings;
|
dnsCache: DnsCacheSettings;
|
||||||
|
apiRequestTimeout: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PublicSettings {
|
interface PublicSettings {
|
||||||
@@ -593,6 +594,7 @@ class Settings {
|
|||||||
forceMinTtl: 0,
|
forceMinTtl: 0,
|
||||||
forceMaxTtl: -1,
|
forceMaxTtl: -1,
|
||||||
},
|
},
|
||||||
|
apiRequestTimeout: 10000,
|
||||||
},
|
},
|
||||||
migrations: [],
|
migrations: [],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ const messages = defineMessages('components.Settings.SettingsNetwork', {
|
|||||||
'Do NOT enable this if you are experiencing issues with DNS lookups',
|
'Do NOT enable this if you are experiencing issues with DNS lookups',
|
||||||
dnsCacheForceMinTtl: 'DNS Cache Minimum TTL',
|
dnsCacheForceMinTtl: 'DNS Cache Minimum TTL',
|
||||||
dnsCacheForceMaxTtl: 'DNS Cache Maximum 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 = () => {
|
const SettingsNetwork = () => {
|
||||||
@@ -91,6 +95,10 @@ const SettingsNetwork = () => {
|
|||||||
.max(65535, intl.formatMessage(messages.validationProxyPort))
|
.max(65535, intl.formatMessage(messages.validationProxyPort))
|
||||||
.required(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) {
|
if (!data && !error) {
|
||||||
@@ -130,6 +138,10 @@ const SettingsNetwork = () => {
|
|||||||
proxyPassword: data?.proxy?.password,
|
proxyPassword: data?.proxy?.password,
|
||||||
proxyBypassFilter: data?.proxy?.bypassFilter,
|
proxyBypassFilter: data?.proxy?.bypassFilter,
|
||||||
proxyBypassLocalAddresses: data?.proxy?.bypassLocalAddresses,
|
proxyBypassLocalAddresses: data?.proxy?.bypassLocalAddresses,
|
||||||
|
apiRequestTimeout:
|
||||||
|
data?.apiRequestTimeout !== undefined
|
||||||
|
? data.apiRequestTimeout / 1000
|
||||||
|
: 10,
|
||||||
}}
|
}}
|
||||||
enableReinitialize
|
enableReinitialize
|
||||||
validationSchema={NetworkSettingsSchema}
|
validationSchema={NetworkSettingsSchema}
|
||||||
@@ -154,6 +166,7 @@ const SettingsNetwork = () => {
|
|||||||
bypassFilter: values.proxyBypassFilter,
|
bypassFilter: values.proxyBypassFilter,
|
||||||
bypassLocalAddresses: values.proxyBypassLocalAddresses,
|
bypassLocalAddresses: values.proxyBypassLocalAddresses,
|
||||||
},
|
},
|
||||||
|
apiRequestTimeout: Number(values.apiRequestTimeout) * 1000,
|
||||||
});
|
});
|
||||||
mutate('/api/v1/settings/public');
|
mutate('/api/v1/settings/public');
|
||||||
mutate('/api/v1/status');
|
mutate('/api/v1/status');
|
||||||
@@ -341,6 +354,31 @@ const SettingsNetwork = () => {
|
|||||||
</div>
|
</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">
|
<div className="form-row">
|
||||||
<label htmlFor="proxyEnabled" className="checkbox-label">
|
<label htmlFor="proxyEnabled" className="checkbox-label">
|
||||||
<span className="mr-2">
|
<span className="mr-2">
|
||||||
|
|||||||
@@ -997,6 +997,8 @@
|
|||||||
"components.Settings.SettingsMain.validationUrlTrailingSlash": "URL must not end in a trailing slash",
|
"components.Settings.SettingsMain.validationUrlTrailingSlash": "URL must not end in a trailing slash",
|
||||||
"components.Settings.SettingsMain.youtubeUrl": "YouTube URL",
|
"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.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.csrfProtection": "Enable CSRF Protection",
|
||||||
"components.Settings.SettingsNetwork.csrfProtectionHoverTip": "Do NOT enable this setting unless you understand what you are doing!",
|
"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)",
|
"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.toastSettingsSuccess": "Settings saved successfully!",
|
||||||
"components.Settings.SettingsNetwork.trustProxy": "Enable Proxy Support",
|
"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.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.validationDnsCacheMaxTtl": "You must provide a valid maximum TTL",
|
||||||
"components.Settings.SettingsNetwork.validationDnsCacheMinTtl": "You must provide a valid minimum TTL",
|
"components.Settings.SettingsNetwork.validationDnsCacheMinTtl": "You must provide a valid minimum TTL",
|
||||||
"components.Settings.SettingsNetwork.validationProxyPort": "You must provide a valid port",
|
"components.Settings.SettingsNetwork.validationProxyPort": "You must provide a valid port",
|
||||||
|
|||||||
Reference in New Issue
Block a user