mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2026-01-01 12:18:35 -05:00
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:
@@ -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',
|
||||
|
||||
@@ -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">
|
||||
|
||||
461
src/components/Settings/SettingsNetwork/index.tsx
Normal file
461
src/components/Settings/SettingsNetwork/index.tsx
Normal 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;
|
||||
@@ -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",
|
||||
|
||||
16
src/pages/settings/network.tsx
Normal file
16
src/pages/settings/network.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user