From 91261f6a61416b5daca6630e70fb9c5dd6396f3d Mon Sep 17 00:00:00 2001
From: fallenbagel <98979876+fallenbagel@users.noreply.github.com>
Date: Fri, 13 Feb 2026 04:16:10 +0500
Subject: [PATCH] fix(settings): DNS cache UI consistency, validation, and
conditional rendering (#2382)
---
.../Settings/SettingsJobsCache/index.tsx | 211 +++++++++++-------
.../Settings/SettingsNetwork/index.tsx | 90 ++++----
src/i18n/locale/en.json | 2 +
3 files changed, 178 insertions(+), 125 deletions(-)
diff --git a/src/components/Settings/SettingsJobsCache/index.tsx b/src/components/Settings/SettingsJobsCache/index.tsx
index 7812d54e..5fa356e0 100644
--- a/src/components/Settings/SettingsJobsCache/index.tsx
+++ b/src/components/Settings/SettingsJobsCache/index.tsx
@@ -69,6 +69,7 @@ const messages: { [messageName: string]: MessageDescriptor } = defineMessages(
dnsCacheGlobalStats: 'Global DNS Cache Stats',
dnsCacheGlobalStatsDescription:
'These stats are aggregated across all DNS cache entries.',
+ dnsNoCacheEntries: 'No DNS lookups have been cached yet.',
size: 'Size',
hits: 'Hits',
misses: 'Misses',
@@ -611,91 +612,133 @@ const SettingsJobs = () => {
-
-
{intl.formatMessage(messages.dnsCache)}
-
- {intl.formatMessage(messages.dnsCacheDescription)}
-
-
-
-
-
-
- {intl.formatMessage(messages.dnscachename)}
-
- {intl.formatMessage(messages.dnscacheactiveaddress)}
-
- {intl.formatMessage(messages.dnscachehits)}
- {intl.formatMessage(messages.dnscachemisses)}
- {intl.formatMessage(messages.dnscacheage)}
-
-
-
-
- {Object.entries(cacheData?.dnsCache.entries || {}).map(
- ([hostname, data]) => (
-
- {hostname}
- {data.activeAddress}
- {intl.formatNumber(data.hits)}
- {intl.formatNumber(data.misses)}
- {formatAge(data.age)}
-
- flushDnsCache(hostname)}
- >
-
- {intl.formatMessage(messages.flushdnscache)}
-
-
-
- )
- )}
-
-
-
-
-
- {intl.formatMessage(messages.dnsCacheGlobalStats)}
-
-
- {intl.formatMessage(messages.dnsCacheGlobalStatsDescription)}
-
-
-
-
-
-
- {Object.entries(cacheData?.dnsCache.stats || {})
- .filter(([statName]) => statName !== 'maxSize')
- .map(([statName]) => (
-
- {messages[statName]
- ? intl.formatMessage(messages[statName])
- : statName}
+ {cacheData?.dnsCache != null && (
+ <>
+
+
{intl.formatMessage(messages.dnsCache)}
+
+ {intl.formatMessage(messages.dnsCacheDescription)}
+
+
+
+
+
+
+
+ {intl.formatMessage(messages.dnscachename)}
- ))}
-
-
-
-
- {Object.entries(cacheData?.dnsCache.stats || {})
- .filter(([statName]) => statName !== 'maxSize')
- .map(([statName, statValue]) => (
-
- {statName === 'hitRate'
- ? intl.formatNumber(statValue, {
- style: 'percent',
- maximumFractionDigits: 2,
- })
- : intl.formatNumber(statValue)}
-
- ))}
-
-
-
-
+
+ {intl.formatMessage(messages.dnscacheactiveaddress)}
+
+
+ {intl.formatMessage(messages.dnscachehits)}
+
+
+ {intl.formatMessage(messages.dnscachemisses)}
+
+
+ {intl.formatMessage(messages.dnscacheage)}
+
+
+
+
+
+ {(() => {
+ if (!cacheData) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ const entries = Object.entries(
+ cacheData.dnsCache?.entries ?? {}
+ );
+
+ if (entries.length === 0) {
+ return (
+
+
+ {intl.formatMessage(messages.dnsNoCacheEntries)}
+
+
+ );
+ }
+
+ return entries.map(([hostname, data]) => (
+
+ {hostname}
+ {data.activeAddress}
+ {intl.formatNumber(data.hits)}
+ {intl.formatNumber(data.misses)}
+ {formatAge(data.age)}
+
+ flushDnsCache(hostname)}
+ >
+
+
+ {intl.formatMessage(messages.flushdnscache)}
+
+
+
+
+ ));
+ })()}
+
+
+
+
+
+ {intl.formatMessage(messages.dnsCacheGlobalStats)}
+
+
+ {intl.formatMessage(messages.dnsCacheGlobalStatsDescription)}
+
+
+
+ {!cacheData ? (
+
+ ) : (
+
+
+
+ {Object.entries(cacheData.dnsCache?.stats ?? {})
+ .filter(([statName]) => statName !== 'maxSize')
+ .map(([statName]) => (
+
+ {messages[statName]
+ ? intl.formatMessage(messages[statName])
+ : statName}
+
+ ))}
+
+
+
+
+ {Object.entries(cacheData.dnsCache?.stats ?? {})
+ .filter(([statName]) => statName !== 'maxSize')
+ .map(([statName, statValue]) => (
+
+ {statName === 'hitRate'
+ ? intl.formatNumber(statValue, {
+ style: 'percent',
+ maximumFractionDigits: 2,
+ })
+ : intl.formatNumber(statValue)}
+
+ ))}
+
+
+
+ )}
+
+ >
+ )}
{intl.formatMessage(messages.imagecache)}
diff --git a/src/components/Settings/SettingsNetwork/index.tsx b/src/components/Settings/SettingsNetwork/index.tsx
index c8f07eee..21dc6a00 100644
--- a/src/components/Settings/SettingsNetwork/index.tsx
+++ b/src/components/Settings/SettingsNetwork/index.tsx
@@ -29,6 +29,8 @@ const messages = defineMessages('components.Settings.SettingsNetwork', {
trustProxyTip:
'Allow Seerr to correctly register client IP addresses behind a proxy',
proxyEnabled: 'HTTP(S) Proxy',
+ proxyEnabledTip:
+ 'Send ALL outgoing HTTP/HTTPS requests through a proxy server (host/port). Does NOT enable HTTPS, SSL, or certificate configuration.',
proxyHostname: 'Proxy Hostname',
proxyPort: 'Proxy Port',
proxySsl: 'Use SSL For Proxy',
@@ -78,13 +80,16 @@ const SettingsNetwork = () => {
then: Yup.number()
.typeError(intl.formatMessage(messages.validationDnsCacheMaxTtl))
.required(intl.formatMessage(messages.validationDnsCacheMaxTtl))
- .min(0),
+ .min(-1),
}),
proxyPort: Yup.number().when('proxyEnabled', {
is: (proxyEnabled: boolean) => proxyEnabled,
- then: Yup.number().required(
- intl.formatMessage(messages.validationProxyPort)
- ),
+ then: Yup.number()
+ .typeError(intl.formatMessage(messages.validationProxyPort))
+ .integer(intl.formatMessage(messages.validationProxyPort))
+ .min(1, intl.formatMessage(messages.validationProxyPort))
+ .max(65535, intl.formatMessage(messages.validationProxyPort))
+ .required(intl.formatMessage(messages.validationProxyPort)),
}),
});
@@ -288,50 +293,50 @@ const SettingsNetwork = () => {
{intl.formatMessage(messages.dnsCacheForceMinTtl)}
-
-
-
- {errors.dnsCacheForceMinTtl &&
- touched.dnsCacheForceMinTtl &&
- typeof errors.dnsCacheForceMinTtl === 'string' && (
-
- {errors.dnsCacheForceMinTtl}
-
- )}
+
+ {errors.dnsCacheForceMinTtl &&
+ touched.dnsCacheForceMinTtl &&
+ typeof errors.dnsCacheForceMinTtl === 'string' && (
+
+ {errors.dnsCacheForceMinTtl}
+
+ )}
{intl.formatMessage(messages.dnsCacheForceMaxTtl)}
-
-
-
- {errors.dnsCacheForceMaxTtl &&
- touched.dnsCacheForceMaxTtl &&
- typeof errors.dnsCacheForceMaxTtl === 'string' && (
-
- {errors.dnsCacheForceMaxTtl}
-
- )}
+
+ {errors.dnsCacheForceMaxTtl &&
+ touched.dnsCacheForceMaxTtl &&
+ typeof errors.dnsCacheForceMaxTtl === 'string' && (
+
+ {errors.dnsCacheForceMaxTtl}
+
+ )}
>
@@ -343,6 +348,9 @@ const SettingsNetwork = () => {
+
+ {intl.formatMessage(messages.proxyEnabledTip)}
+
{
{intl.formatMessage(messages.proxyPort)}
-
-
-
+
{errors.proxyPort &&
touched.proxyPort &&
typeof errors.proxyPort === 'string' && (
diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json
index fb53bfc2..d2b4453d 100644
--- a/src/i18n/locale/en.json
+++ b/src/i18n/locale/en.json
@@ -890,6 +890,7 @@
"components.Settings.SettingsJobsCache.dnsCacheDescription": "Seerr caches DNS lookups to optimize performance and avoid making unnecessary API calls.",
"components.Settings.SettingsJobsCache.dnsCacheGlobalStats": "Global DNS Cache Stats",
"components.Settings.SettingsJobsCache.dnsCacheGlobalStatsDescription": "These stats are aggregated across all DNS cache entries.",
+ "components.Settings.SettingsJobsCache.dnsNoCacheEntries": "No DNS lookups have been cached yet.",
"components.Settings.SettingsJobsCache.dnscacheactiveaddress": "Active Address",
"components.Settings.SettingsJobsCache.dnscacheage": "Age",
"components.Settings.SettingsJobsCache.dnscacheflushed": "{hostname} dns cache flushed.",
@@ -1015,6 +1016,7 @@
"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.proxyEnabledTip": "Send ALL outgoing HTTP/HTTPS requests through a proxy server (host/port). Does NOT enable HTTPS, SSL, or certificate configuration.",
"components.Settings.SettingsNetwork.proxyHostname": "Proxy Hostname",
"components.Settings.SettingsNetwork.proxyPassword": "Proxy Password",
"components.Settings.SettingsNetwork.proxyPort": "Proxy Port",