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)} - - - - - ) - )} - -
-
-
-

- {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)} + + + + + )); + })()} + + + +
+

+ {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 = () => {

-
- -
- {errors.dnsCacheForceMinTtl && - touched.dnsCacheForceMinTtl && - typeof errors.dnsCacheForceMinTtl === 'string' && ( -
- {errors.dnsCacheForceMinTtl} -
- )} +
+ {errors.dnsCacheForceMinTtl && + touched.dnsCacheForceMinTtl && + typeof errors.dnsCacheForceMinTtl === 'string' && ( +
+ {errors.dnsCacheForceMinTtl} +
+ )}
-
- -
- {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",