refactor(settings): move network settings to their own settings tab (#1287)

* refactor(settings): move network settings to their own settings tab

This PR moves the network settings out of the General Settings section to a new Netowrk Settings
tab.

* fix: add missing translations

* fix: fix cypress tests for network settings

* refactor: create a separate section for network settings
This commit is contained in:
Gauthier
2025-02-20 18:27:18 +01:00
committed by GitHub
parent 0d2273ff6e
commit 525a538f34
12 changed files with 655 additions and 398 deletions

View File

@@ -13,6 +13,7 @@ const messages = defineMessages('components.Settings', {
menuPlexSettings: 'Plex',
menuJellyfinSettings: '{mediaServerName}',
menuServices: 'Services',
menuNetwork: 'Network',
menuNotifications: 'Notifications',
menuLogs: 'Logs',
menuJobs: 'Jobs & Cache',
@@ -53,6 +54,11 @@ const SettingsLayout = ({ children }: SettingsLayoutProps) => {
route: '/settings/services',
regex: /^\/settings\/services/,
},
{
text: intl.formatMessage(messages.menuNetwork),
route: '/settings/network',
regex: /^\/settings\/network/,
},
{
text: intl.formatMessage(messages.menuNotifications),
route: '/settings/notifications/email',

View File

@@ -2,7 +2,6 @@ import Button from '@app/components/Common/Button';
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
import PageTitle from '@app/components/Common/PageTitle';
import SensitiveInput from '@app/components/Common/SensitiveInput';
import Tooltip from '@app/components/Common/Tooltip';
import LanguageSelector from '@app/components/LanguageSelector';
import RegionSelector from '@app/components/RegionSelector';
import CopyButton from '@app/components/Settings/CopyButton';
@@ -42,39 +41,15 @@ const messages = defineMessages('components.Settings.SettingsMain', {
toastSettingsSuccess: 'Settings saved successfully!',
toastSettingsFailure: 'Something went wrong while saving settings.',
hideAvailable: 'Hide Available Media',
csrfProtection: 'Enable CSRF Protection',
csrfProtectionTip: 'Set external API access to read-only (requires HTTPS)',
csrfProtectionHoverTip:
'Do NOT enable this setting unless you understand what you are doing!',
cacheImages: 'Enable Image Caching',
cacheImagesTip:
'Cache externally sourced images (requires a significant amount of disk space)',
trustProxy: 'Enable Proxy Support',
trustProxyTip:
'Allow Jellyseerr to correctly register client IP addresses behind a proxy',
validationApplicationTitle: 'You must provide an application title',
validationApplicationUrl: 'You must provide a valid URL',
validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash',
partialRequestsEnabled: 'Allow Partial Series Requests',
enableSpecialEpisodes: 'Allow Special Episodes Requests',
forceIpv4First: 'IPv4 Resolution First',
forceIpv4FirstTip:
'Force Jellyseerr to resolve IPv4 addresses first instead of IPv6',
dnsServers: 'Custom DNS Servers',
dnsServersTip:
'Comma-separated list of custom DNS servers, e.g. "1.1.1.1,[2606:4700:4700::1111]"',
locale: 'Display Language',
proxyEnabled: 'HTTP(S) Proxy',
proxyHostname: 'Proxy Hostname',
proxyPort: 'Proxy Port',
proxySsl: 'Use SSL For Proxy',
proxyUser: 'Proxy Username',
proxyPassword: 'Proxy Password',
proxyBypassFilter: 'Proxy Ignored Addresses',
proxyBypassFilterTip:
"Use ',' as a separator, and '*.' as a wildcard for subdomains",
proxyBypassLocalAddresses: 'Bypass Proxy for Local Addresses',
validationProxyPort: 'You must provide a valid port',
});
const SettingsMain = () => {
@@ -105,12 +80,6 @@ const SettingsMain = () => {
intl.formatMessage(messages.validationApplicationUrlTrailingSlash),
(value) => !value || !value.endsWith('/')
),
proxyPort: Yup.number().when('proxyEnabled', {
is: (proxyEnabled: boolean) => proxyEnabled,
then: Yup.number().required(
intl.formatMessage(messages.validationProxyPort)
),
}),
});
const regenerate = async () => {
@@ -158,7 +127,6 @@ const SettingsMain = () => {
initialValues={{
applicationTitle: data?.applicationTitle,
applicationUrl: data?.applicationUrl,
csrfProtection: data?.csrfProtection,
hideAvailable: data?.hideAvailable,
locale: data?.locale ?? 'en',
discoverRegion: data?.discoverRegion,
@@ -166,18 +134,7 @@ const SettingsMain = () => {
streamingRegion: data?.streamingRegion || 'US',
partialRequestsEnabled: data?.partialRequestsEnabled,
enableSpecialEpisodes: data?.enableSpecialEpisodes,
forceIpv4First: data?.forceIpv4First,
dnsServers: data?.dnsServers,
trustProxy: data?.trustProxy,
cacheImages: data?.cacheImages,
proxyEnabled: data?.proxy?.enabled,
proxyHostname: data?.proxy?.hostname,
proxyPort: data?.proxy?.port,
proxySsl: data?.proxy?.useSsl,
proxyUser: data?.proxy?.user,
proxyPassword: data?.proxy?.password,
proxyBypassFilter: data?.proxy?.bypassFilter,
proxyBypassLocalAddresses: data?.proxy?.bypassLocalAddresses,
}}
enableReinitialize
validationSchema={MainSettingsSchema}
@@ -191,7 +148,6 @@ const SettingsMain = () => {
body: JSON.stringify({
applicationTitle: values.applicationTitle,
applicationUrl: values.applicationUrl,
csrfProtection: values.csrfProtection,
hideAvailable: values.hideAvailable,
locale: values.locale,
discoverRegion: values.discoverRegion,
@@ -199,20 +155,7 @@ const SettingsMain = () => {
originalLanguage: values.originalLanguage,
partialRequestsEnabled: values.partialRequestsEnabled,
enableSpecialEpisodes: values.enableSpecialEpisodes,
forceIpv4First: values.forceIpv4First,
dnsServers: values.dnsServers,
trustProxy: values.trustProxy,
cacheImages: values.cacheImages,
proxy: {
enabled: values.proxyEnabled,
hostname: values.proxyHostname,
port: values.proxyPort,
useSsl: values.proxySsl,
user: values.proxyUser,
password: values.proxyPassword,
bypassFilter: values.proxyBypassFilter,
bypassLocalAddresses: values.proxyBypassLocalAddresses,
},
}),
});
if (!res.ok) throw new Error();
@@ -321,58 +264,6 @@ const SettingsMain = () => {
)}
</div>
</div>
<div className="form-row">
<label htmlFor="trustProxy" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.trustProxy)}
</span>
<SettingsBadge badgeType="restartRequired" />
<span className="label-tip">
{intl.formatMessage(messages.trustProxyTip)}
</span>
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="trustProxy"
name="trustProxy"
onChange={() => {
setFieldValue('trustProxy', !values.trustProxy);
}}
/>
</div>
</div>
<div className="form-row">
<label htmlFor="csrfProtection" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.csrfProtection)}
</span>
<SettingsBadge badgeType="advanced" className="mr-2" />
<SettingsBadge badgeType="restartRequired" />
<span className="label-tip">
{intl.formatMessage(messages.csrfProtectionTip)}
</span>
</label>
<div className="form-input-area">
<Tooltip
content={intl.formatMessage(
messages.csrfProtectionHoverTip
)}
>
<Field
type="checkbox"
id="csrfProtection"
name="csrfProtection"
onChange={() => {
setFieldValue(
'csrfProtection',
!values.csrfProtection
);
}}
/>
</Tooltip>
</div>
</div>
<div className="form-row">
<label htmlFor="cacheImages" className="checkbox-label">
<span className="mr-2">
@@ -534,231 +425,6 @@ const SettingsMain = () => {
/>
</div>
</div>
<div className="form-row">
<label htmlFor="forceIpv4First" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.forceIpv4First)}
</span>
<SettingsBadge badgeType="advanced" className="mr-2" />
<SettingsBadge badgeType="restartRequired" />
<span className="label-tip">
{intl.formatMessage(messages.forceIpv4FirstTip)}
</span>
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="forceIpv4First"
name="forceIpv4First"
onChange={() => {
setFieldValue('forceIpv4First', !values.forceIpv4First);
}}
/>
</div>
</div>
<div className="form-row">
<label htmlFor="dnsServers" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.dnsServers)}
</span>
<SettingsBadge badgeType="advanced" className="mr-2" />
<SettingsBadge badgeType="restartRequired" />
<span className="label-tip">
{intl.formatMessage(messages.dnsServersTip)}
</span>
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="dnsServers"
name="dnsServers"
type="text"
inputMode="url"
/>
</div>
{errors.dnsServers &&
touched.dnsServers &&
typeof errors.dnsServers === 'string' && (
<div className="error">{errors.dnsServers}</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="proxyEnabled" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.proxyEnabled)}
</span>
<SettingsBadge badgeType="advanced" className="mr-2" />
<SettingsBadge badgeType="restartRequired" />
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="proxyEnabled"
name="proxyEnabled"
onChange={() => {
setFieldValue('proxyEnabled', !values.proxyEnabled);
}}
/>
</div>
</div>
{values.proxyEnabled && (
<>
<div className="mr-2 ml-4">
<div className="form-row">
<label
htmlFor="proxyHostname"
className="checkbox-label"
>
{intl.formatMessage(messages.proxyHostname)}
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="proxyHostname"
name="proxyHostname"
type="text"
/>
</div>
{errors.proxyHostname &&
touched.proxyHostname &&
typeof errors.proxyHostname === 'string' && (
<div className="error">
{errors.proxyHostname}
</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="proxyPort" className="checkbox-label">
{intl.formatMessage(messages.proxyPort)}
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="proxyPort"
name="proxyPort"
type="text"
/>
</div>
{errors.proxyPort &&
touched.proxyPort &&
typeof errors.proxyPort === 'string' && (
<div className="error">{errors.proxyPort}</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="proxySsl" className="checkbox-label">
{intl.formatMessage(messages.proxySsl)}
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="proxySsl"
name="proxySsl"
onChange={() => {
setFieldValue('proxySsl', !values.proxySsl);
}}
/>
</div>
</div>
<div className="form-row">
<label htmlFor="proxyUser" className="checkbox-label">
{intl.formatMessage(messages.proxyUser)}
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="proxyUser"
name="proxyUser"
type="text"
/>
</div>
{errors.proxyUser &&
touched.proxyUser &&
typeof errors.proxyUser === 'string' && (
<div className="error">{errors.proxyUser}</div>
)}
</div>
</div>
<div className="form-row">
<label
htmlFor="proxyPassword"
className="checkbox-label"
>
{intl.formatMessage(messages.proxyPassword)}
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="proxyPassword"
name="proxyPassword"
type="password"
/>
</div>
{errors.proxyPassword &&
touched.proxyPassword &&
typeof errors.proxyPassword === 'string' && (
<div className="error">
{errors.proxyPassword}
</div>
)}
</div>
</div>
<div className="form-row">
<label
htmlFor="proxyBypassFilter"
className="checkbox-label"
>
{intl.formatMessage(messages.proxyBypassFilter)}
<span className="label-tip">
{intl.formatMessage(messages.proxyBypassFilterTip)}
</span>
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="proxyBypassFilter"
name="proxyBypassFilter"
type="text"
/>
</div>
{errors.proxyBypassFilter &&
touched.proxyBypassFilter &&
typeof errors.proxyBypassFilter === 'string' && (
<div className="error">
{errors.proxyBypassFilter}
</div>
)}
</div>
</div>
<div className="form-row">
<label
htmlFor="proxyBypassLocalAddresses"
className="checkbox-label"
>
{intl.formatMessage(
messages.proxyBypassLocalAddresses
)}
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="proxyBypassLocalAddresses"
name="proxyBypassLocalAddresses"
onChange={() => {
setFieldValue(
'proxyBypassLocalAddresses',
!values.proxyBypassLocalAddresses
);
}}
/>
</div>
</div>
</div>
</>
)}
<div className="actions">
<div className="flex justify-end">
<span className="ml-3 inline-flex rounded-md shadow-sm">

View File

@@ -0,0 +1,461 @@
import Button from '@app/components/Common/Button';
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
import PageTitle from '@app/components/Common/PageTitle';
import Tooltip from '@app/components/Common/Tooltip';
import SettingsBadge from '@app/components/Settings/SettingsBadge';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import type { NetworkSettings } from '@server/lib/settings';
import { Field, Form, Formik } from 'formik';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
import useSWR, { mutate } from 'swr';
import * as Yup from 'yup';
const messages = defineMessages('components.Settings.SettingsNetwork', {
toastSettingsSuccess: 'Settings saved successfully!',
toastSettingsFailure: 'Something went wrong while saving settings.',
network: 'Network',
networksettings: 'Network Settings',
networksettingsDescription:
'Configure network settings for your Jellyseerr instance.',
csrfProtection: 'Enable CSRF Protection',
csrfProtectionTip: 'Set external API access to read-only (requires HTTPS)',
csrfProtectionHoverTip:
'Do NOT enable this setting unless you understand what you are doing!',
trustProxy: 'Enable Proxy Support',
trustProxyTip:
'Allow Jellyseerr to correctly register client IP addresses behind a proxy',
forceIpv4First: 'IPv4 Resolution First',
forceIpv4FirstTip:
'Force Jellyseerr to resolve IPv4 addresses first instead of IPv6',
dnsServers: 'Custom DNS Servers',
dnsServersTip:
'Comma-separated list of custom DNS servers, e.g. "1.1.1.1,[2606:4700:4700::1111]"',
proxyEnabled: 'HTTP(S) Proxy',
proxyHostname: 'Proxy Hostname',
proxyPort: 'Proxy Port',
proxySsl: 'Use SSL For Proxy',
proxyUser: 'Proxy Username',
proxyPassword: 'Proxy Password',
proxyBypassFilter: 'Proxy Ignored Addresses',
proxyBypassFilterTip:
"Use ',' as a separator, and '*.' as a wildcard for subdomains",
proxyBypassLocalAddresses: 'Bypass Proxy for Local Addresses',
validationProxyPort: 'You must provide a valid port',
});
const SettingsMain = () => {
const { addToast } = useToasts();
const intl = useIntl();
const {
data,
error,
mutate: revalidate,
} = useSWR<NetworkSettings>('/api/v1/settings/network');
const NetworkSettingsSchema = Yup.object().shape({
proxyPort: Yup.number().when('proxyEnabled', {
is: (proxyEnabled: boolean) => proxyEnabled,
then: Yup.number().required(
intl.formatMessage(messages.validationProxyPort)
),
}),
});
if (!data && !error) {
return <LoadingSpinner />;
}
return (
<>
<PageTitle
title={[
intl.formatMessage(messages.network),
intl.formatMessage(globalMessages.settings),
]}
/>
<div className="mb-6">
<h3 className="heading">
{intl.formatMessage(messages.networksettings)}
</h3>
<p className="description">
{intl.formatMessage(messages.networksettingsDescription)}
</p>
</div>
<div className="section">
<Formik
initialValues={{
csrfProtection: data?.csrfProtection,
forceIpv4First: data?.forceIpv4First,
dnsServers: data?.dnsServers,
trustProxy: data?.trustProxy,
proxyEnabled: data?.proxy?.enabled,
proxyHostname: data?.proxy?.hostname,
proxyPort: data?.proxy?.port,
proxySsl: data?.proxy?.useSsl,
proxyUser: data?.proxy?.user,
proxyPassword: data?.proxy?.password,
proxyBypassFilter: data?.proxy?.bypassFilter,
proxyBypassLocalAddresses: data?.proxy?.bypassLocalAddresses,
}}
enableReinitialize
validationSchema={NetworkSettingsSchema}
onSubmit={async (values) => {
try {
const res = await fetch('/api/v1/settings/network', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
csrfProtection: values.csrfProtection,
forceIpv4First: values.forceIpv4First,
dnsServers: values.dnsServers,
trustProxy: values.trustProxy,
proxy: {
enabled: values.proxyEnabled,
hostname: values.proxyHostname,
port: values.proxyPort,
useSsl: values.proxySsl,
user: values.proxyUser,
password: values.proxyPassword,
bypassFilter: values.proxyBypassFilter,
bypassLocalAddresses: values.proxyBypassLocalAddresses,
},
}),
});
if (!res.ok) throw new Error();
mutate('/api/v1/settings/public');
mutate('/api/v1/status');
addToast(intl.formatMessage(messages.toastSettingsSuccess), {
autoDismiss: true,
appearance: 'success',
});
} catch (e) {
addToast(intl.formatMessage(messages.toastSettingsFailure), {
autoDismiss: true,
appearance: 'error',
});
} finally {
revalidate();
}
}}
>
{({
errors,
touched,
isSubmitting,
isValid,
values,
setFieldValue,
}) => {
return (
<Form className="section" data-testid="settings-network-form">
<div className="form-row">
<label htmlFor="trustProxy" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.trustProxy)}
</span>
<SettingsBadge badgeType="restartRequired" />
<span className="label-tip">
{intl.formatMessage(messages.trustProxyTip)}
</span>
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="trustProxy"
name="trustProxy"
onChange={() => {
setFieldValue('trustProxy', !values.trustProxy);
}}
/>
</div>
</div>
<div className="form-row">
<label htmlFor="csrfProtection" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.csrfProtection)}
</span>
<SettingsBadge badgeType="advanced" className="mr-2" />
<SettingsBadge badgeType="restartRequired" />
<span className="label-tip">
{intl.formatMessage(messages.csrfProtectionTip)}
</span>
</label>
<div className="form-input-area">
<Tooltip
content={intl.formatMessage(
messages.csrfProtectionHoverTip
)}
>
<Field
type="checkbox"
id="csrfProtection"
name="csrfProtection"
onChange={() => {
setFieldValue(
'csrfProtection',
!values.csrfProtection
);
}}
/>
</Tooltip>
</div>
</div>
<div className="form-row">
<label htmlFor="forceIpv4First" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.forceIpv4First)}
</span>
<SettingsBadge badgeType="advanced" className="mr-2" />
<SettingsBadge badgeType="restartRequired" />
<span className="label-tip">
{intl.formatMessage(messages.forceIpv4FirstTip)}
</span>
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="forceIpv4First"
name="forceIpv4First"
onChange={() => {
setFieldValue('forceIpv4First', !values.forceIpv4First);
}}
/>
</div>
</div>
<div className="form-row">
<label htmlFor="dnsServers" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.dnsServers)}
</span>
<SettingsBadge badgeType="advanced" className="mr-2" />
<SettingsBadge badgeType="restartRequired" />
<span className="label-tip">
{intl.formatMessage(messages.dnsServersTip)}
</span>
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="dnsServers"
name="dnsServers"
type="text"
inputMode="url"
/>
</div>
{errors.dnsServers &&
touched.dnsServers &&
typeof errors.dnsServers === 'string' && (
<div className="error">{errors.dnsServers}</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="proxyEnabled" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.proxyEnabled)}
</span>
<SettingsBadge badgeType="advanced" className="mr-2" />
<SettingsBadge badgeType="restartRequired" />
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="proxyEnabled"
name="proxyEnabled"
onChange={() => {
setFieldValue('proxyEnabled', !values.proxyEnabled);
}}
/>
</div>
</div>
{values.proxyEnabled && (
<>
<div className="mr-2 ml-4">
<div className="form-row">
<label
htmlFor="proxyHostname"
className="checkbox-label"
>
{intl.formatMessage(messages.proxyHostname)}
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="proxyHostname"
name="proxyHostname"
type="text"
/>
</div>
{errors.proxyHostname &&
touched.proxyHostname &&
typeof errors.proxyHostname === 'string' && (
<div className="error">
{errors.proxyHostname}
</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="proxyPort" className="checkbox-label">
{intl.formatMessage(messages.proxyPort)}
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="proxyPort"
name="proxyPort"
type="text"
/>
</div>
{errors.proxyPort &&
touched.proxyPort &&
typeof errors.proxyPort === 'string' && (
<div className="error">{errors.proxyPort}</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="proxySsl" className="checkbox-label">
{intl.formatMessage(messages.proxySsl)}
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="proxySsl"
name="proxySsl"
onChange={() => {
setFieldValue('proxySsl', !values.proxySsl);
}}
/>
</div>
</div>
<div className="form-row">
<label htmlFor="proxyUser" className="checkbox-label">
{intl.formatMessage(messages.proxyUser)}
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="proxyUser"
name="proxyUser"
type="text"
/>
</div>
{errors.proxyUser &&
touched.proxyUser &&
typeof errors.proxyUser === 'string' && (
<div className="error">{errors.proxyUser}</div>
)}
</div>
</div>
<div className="form-row">
<label
htmlFor="proxyPassword"
className="checkbox-label"
>
{intl.formatMessage(messages.proxyPassword)}
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="proxyPassword"
name="proxyPassword"
type="password"
/>
</div>
{errors.proxyPassword &&
touched.proxyPassword &&
typeof errors.proxyPassword === 'string' && (
<div className="error">
{errors.proxyPassword}
</div>
)}
</div>
</div>
<div className="form-row">
<label
htmlFor="proxyBypassFilter"
className="checkbox-label"
>
{intl.formatMessage(messages.proxyBypassFilter)}
<span className="label-tip">
{intl.formatMessage(messages.proxyBypassFilterTip)}
</span>
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
id="proxyBypassFilter"
name="proxyBypassFilter"
type="text"
/>
</div>
{errors.proxyBypassFilter &&
touched.proxyBypassFilter &&
typeof errors.proxyBypassFilter === 'string' && (
<div className="error">
{errors.proxyBypassFilter}
</div>
)}
</div>
</div>
<div className="form-row">
<label
htmlFor="proxyBypassLocalAddresses"
className="checkbox-label"
>
{intl.formatMessage(
messages.proxyBypassLocalAddresses
)}
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="proxyBypassLocalAddresses"
name="proxyBypassLocalAddresses"
onChange={() => {
setFieldValue(
'proxyBypassLocalAddresses',
!values.proxyBypassLocalAddresses
);
}}
/>
</div>
</div>
</div>
</>
)}
<div className="actions">
<div className="flex justify-end">
<span className="ml-3 inline-flex rounded-md shadow-sm">
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting || !isValid}
>
<ArrowDownOnSquareIcon />
<span>
{isSubmitting
? intl.formatMessage(globalMessages.saving)
: intl.formatMessage(globalMessages.save)}
</span>
</Button>
</span>
</div>
</div>
</Form>
);
}}
</Formik>
</div>
</>
);
};
export default SettingsMain;

View File

@@ -915,16 +915,9 @@
"components.Settings.SettingsMain.applicationurl": "Application URL",
"components.Settings.SettingsMain.cacheImages": "Enable Image Caching",
"components.Settings.SettingsMain.cacheImagesTip": "Cache externally sourced images (requires a significant amount of disk space)",
"components.Settings.SettingsMain.csrfProtection": "Enable CSRF Protection",
"components.Settings.SettingsMain.csrfProtectionHoverTip": "Do NOT enable this setting unless you understand what you are doing!",
"components.Settings.SettingsMain.csrfProtectionTip": "Set external API access to read-only (requires HTTPS)",
"components.Settings.SettingsMain.discoverRegion": "Discover Region",
"components.Settings.SettingsMain.discoverRegionTip": "Filter content by regional availability",
"components.Settings.SettingsMain.dnsServers": "Custom DNS Servers",
"components.Settings.SettingsMain.dnsServersTip": "Comma-separated list of custom DNS servers, e.g. \"1.1.1.1,[2606:4700:4700::1111]\"",
"components.Settings.SettingsMain.enableSpecialEpisodes": "Allow Special Episodes Requests",
"components.Settings.SettingsMain.forceIpv4First": "IPv4 Resolution First",
"components.Settings.SettingsMain.forceIpv4FirstTip": "Force Jellyseerr to resolve IPv4 addresses first instead of IPv6",
"components.Settings.SettingsMain.general": "General",
"components.Settings.SettingsMain.generalsettings": "General Settings",
"components.Settings.SettingsMain.generalsettingsDescription": "Configure global and default settings for Jellyseerr.",
@@ -933,27 +926,39 @@
"components.Settings.SettingsMain.originallanguage": "Discover Language",
"components.Settings.SettingsMain.originallanguageTip": "Filter content by original language",
"components.Settings.SettingsMain.partialRequestsEnabled": "Allow Partial Series Requests",
"components.Settings.SettingsMain.proxyBypassFilter": "Proxy Ignored Addresses",
"components.Settings.SettingsMain.proxyBypassFilterTip": "Use ',' as a separator, and '*.' as a wildcard for subdomains",
"components.Settings.SettingsMain.proxyBypassLocalAddresses": "Bypass Proxy for Local Addresses",
"components.Settings.SettingsMain.proxyEnabled": "HTTP(S) Proxy",
"components.Settings.SettingsMain.proxyHostname": "Proxy Hostname",
"components.Settings.SettingsMain.proxyPassword": "Proxy Password",
"components.Settings.SettingsMain.proxyPort": "Proxy Port",
"components.Settings.SettingsMain.proxySsl": "Use SSL For Proxy",
"components.Settings.SettingsMain.proxyUser": "Proxy Username",
"components.Settings.SettingsMain.streamingRegion": "Streaming Region",
"components.Settings.SettingsMain.streamingRegionTip": "Show streaming sites by regional availability",
"components.Settings.SettingsMain.toastApiKeyFailure": "Something went wrong while generating a new API key.",
"components.Settings.SettingsMain.toastApiKeySuccess": "New API key generated successfully!",
"components.Settings.SettingsMain.toastSettingsFailure": "Something went wrong while saving settings.",
"components.Settings.SettingsMain.toastSettingsSuccess": "Settings saved successfully!",
"components.Settings.SettingsMain.trustProxy": "Enable Proxy Support",
"components.Settings.SettingsMain.trustProxyTip": "Allow Jellyseerr to correctly register client IP addresses behind a proxy",
"components.Settings.SettingsMain.validationApplicationTitle": "You must provide an application title",
"components.Settings.SettingsMain.validationApplicationUrl": "You must provide a valid URL",
"components.Settings.SettingsMain.validationApplicationUrlTrailingSlash": "URL must not end in a trailing slash",
"components.Settings.SettingsMain.validationProxyPort": "You must provide a valid port",
"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)",
"components.Settings.SettingsNetwork.dnsServers": "Custom DNS Servers",
"components.Settings.SettingsNetwork.dnsServersTip": "Comma-separated list of custom DNS servers, e.g. \"1.1.1.1,[2606:4700:4700::1111]\"",
"components.Settings.SettingsNetwork.forceIpv4First": "IPv4 Resolution First",
"components.Settings.SettingsNetwork.forceIpv4FirstTip": "Force Jellyseerr to resolve IPv4 addresses first instead of IPv6",
"components.Settings.SettingsNetwork.network": "Network",
"components.Settings.SettingsNetwork.networksettings": "Network Settings",
"components.Settings.SettingsNetwork.networksettingsDescription": "Configure network settings for your Jellyseerr instance.",
"components.Settings.SettingsNetwork.proxyBypassFilter": "Proxy Ignored Addresses",
"components.Settings.SettingsNetwork.proxyBypassFilterTip": "Use ',' as a separator, and '*.' as a wildcard for subdomains",
"components.Settings.SettingsNetwork.proxyBypassLocalAddresses": "Bypass Proxy for Local Addresses",
"components.Settings.SettingsNetwork.proxyEnabled": "HTTP(S) Proxy",
"components.Settings.SettingsNetwork.proxyHostname": "Proxy Hostname",
"components.Settings.SettingsNetwork.proxyPassword": "Proxy Password",
"components.Settings.SettingsNetwork.proxyPort": "Proxy Port",
"components.Settings.SettingsNetwork.proxySsl": "Use SSL For Proxy",
"components.Settings.SettingsNetwork.proxyUser": "Proxy Username",
"components.Settings.SettingsNetwork.toastSettingsFailure": "Something went wrong while saving settings.",
"components.Settings.SettingsNetwork.toastSettingsSuccess": "Settings saved successfully!",
"components.Settings.SettingsNetwork.trustProxy": "Enable Proxy Support",
"components.Settings.SettingsNetwork.trustProxyTip": "Allow Jellyseerr to correctly register client IP addresses behind a proxy",
"components.Settings.SettingsNetwork.validationProxyPort": "You must provide a valid port",
"components.Settings.SettingsUsers.defaultPermissions": "Default Permissions",
"components.Settings.SettingsUsers.defaultPermissionsTip": "Initial permissions assigned to new users",
"components.Settings.SettingsUsers.localLogin": "Enable Local Sign-In",
@@ -1069,6 +1074,7 @@
"components.Settings.menuJellyfinSettings": "{mediaServerName}",
"components.Settings.menuJobs": "Jobs & Cache",
"components.Settings.menuLogs": "Logs",
"components.Settings.menuNetwork": "Network",
"components.Settings.menuNotifications": "Notifications",
"components.Settings.menuPlexSettings": "Plex",
"components.Settings.menuServices": "Services",

View File

@@ -0,0 +1,16 @@
import SettingsLayout from '@app/components/Settings/SettingsLayout';
import SettingsNetwork from '@app/components/Settings/SettingsNetwork';
import useRouteGuard from '@app/hooks/useRouteGuard';
import { Permission } from '@app/hooks/useUser';
import type { NextPage } from 'next';
const SettingsNetworkPage: NextPage = () => {
useRouteGuard(Permission.ADMIN);
return (
<SettingsLayout>
<SettingsNetwork />
</SettingsLayout>
);
};
export default SettingsNetworkPage;