diff --git a/jellyseerr-api.yml b/jellyseerr-api.yml index ccaa5dcc4..2e4012b87 100644 --- a/jellyseerr-api.yml +++ b/jellyseerr-api.yml @@ -3062,6 +3062,21 @@ paths: responses: '204': description: 'Flushed cache' + /settings/cache/dns/{dnsEntry}/flush: + post: + summary: Flush a specific DNS cache entry + description: Flushes a specific DNS cache entry + tags: + - settings + parameters: + - in: path + name: dnsEntry + required: true + schema: + type: string + responses: + '204': + description: 'Flushed dns cache' /settings/logs: get: summary: Returns logs diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index 1d6421b9f..436b85e10 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -786,6 +786,20 @@ settingsRoutes.post<{ cacheId: AvailableCacheIds }>( } ); +settingsRoutes.post<{ dnsEntry: string }>( + '/cache/dns/:dnsEntry/flush', + (req, res, next) => { + const dnsEntry = req.params.dnsEntry; + + if (dnsCache) { + dnsCache.clear(dnsEntry); + return res.status(204).send(); + } + + next({ status: 404, message: 'Cache not found.' }); + } +); + settingsRoutes.post( '/initialize', isAuthenticated(Permission.ADMIN), diff --git a/server/utils/dnsCacheManager.ts b/server/utils/dnsCacheManager.ts index bfcaece85..1835de7f7 100644 --- a/server/utils/dnsCacheManager.ts +++ b/server/utils/dnsCacheManager.ts @@ -895,7 +895,25 @@ class DnsCacheManager { }; } - clear() { + clearHostname(hostname: string): void { + if (!hostname || hostname.length === 0) { + return; + } + + if (this.cache.has(hostname)) { + this.cache.delete(hostname); + logger.debug(`Cleared DNS cache entry for ${hostname}`, { + label: 'DNSCache', + }); + } + } + + clear(hostname?: string): void { + if (hostname && hostname.length > 0) { + this.clearHostname(hostname); + return; + } + this.cache.clear(); this.stats.hits = 0; this.stats.misses = 0; diff --git a/src/components/Settings/SettingsJobsCache/index.tsx b/src/components/Settings/SettingsJobsCache/index.tsx index 612d01f30..63491bc8d 100644 --- a/src/components/Settings/SettingsJobsCache/index.tsx +++ b/src/components/Settings/SettingsJobsCache/index.tsx @@ -54,16 +54,18 @@ const messages: { [messageName: string]: MessageDescriptor } = defineMessages( cachekeys: 'Total Keys', cacheksize: 'Key Size', cachevsize: 'Value Size', + flushcache: 'Flush Cache', dnsCache: 'DNS Cache', dnsCacheDescription: 'Jellyseerr caches DNS lookups to optimize performance and avoid making unnecessary API calls.', + dnsCacheFlushed: '{hostname} dns cache flushed.', dnscachename: 'Hostname', dnscacheactiveaddress: 'Active Address', dnscachehits: 'Hits', dnscachemisses: 'Misses', dnscacheage: 'Age', dnscachenetworkerrors: 'Network Errors', - flushcache: 'Flush Cache', + flushdnscache: 'Flush DNS Cache', unknownJob: 'Unknown Job', 'plex-recently-added-scan': 'Plex Recently Added Scan', 'plex-full-scan': 'Plex Full Library Scan', @@ -252,6 +254,18 @@ const SettingsJobs = () => { cacheRevalidate(); }; + const flushDnsCache = async (hostname: string) => { + const res = await fetch(`/api/v1/settings/cache/dns/${hostname}/flush`, { + method: 'POST', + }); + if (!res.ok) throw new Error(); + addToast(intl.formatMessage(messages.dnscacheflushed, { hostname }), { + appearance: 'success', + autoDismiss: true, + }); + cacheRevalidate(); + }; + const scheduleJob = async () => { const jobScheduleCron = ['0', '0', '*', '*', '*', '*']; @@ -295,6 +309,13 @@ const SettingsJobs = () => { } }; + const formatAge = (milliseconds: number): string => { + const seconds = Math.floor(milliseconds / 1000); + const minutes = Math.floor(seconds / 60); + const remainingSeconds = seconds % 60; + return `${minutes}m ${remainingSeconds}s`; + }; + return ( <> { {intl.formatMessage(messages.dnscachenetworkerrors)} + @@ -611,19 +633,17 @@ const SettingsJobs = () => { {intl.formatNumber(cacheData?.dnsCache.stats.misses ?? 0)} - - {intl.formatNumber(Math.floor(data.age / 1000))}s - + {formatAge(data.age)} {intl.formatNumber(data.networkErrors)} - {/* + - */} + ) )} diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 31b4a73cf..5a4a04fe5 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -875,6 +875,7 @@ "components.Settings.SettingsJobsCache.command": "Command", "components.Settings.SettingsJobsCache.dnsCache": "DNS Cache", "components.Settings.SettingsJobsCache.dnsCacheDescription": "Jellyseerr caches DNS lookups to optimize performance and avoid making unnecessary API calls.", + "components.Settings.SettingsJobsCache.dnsCacheFlushed": "{hostname} dns cache flushed.", "components.Settings.SettingsJobsCache.dnscacheactiveaddress": "Active Address", "components.Settings.SettingsJobsCache.dnscacheage": "Age", "components.Settings.SettingsJobsCache.dnscachehits": "Hits", @@ -891,6 +892,7 @@ "components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "Every {jobScheduleMinutes, plural, one {minute} other {{jobScheduleMinutes} minutes}}", "components.Settings.SettingsJobsCache.editJobScheduleSelectorSeconds": "Every {jobScheduleSeconds, plural, one {second} other {{jobScheduleSeconds} seconds}}", "components.Settings.SettingsJobsCache.flushcache": "Flush Cache", + "components.Settings.SettingsJobsCache.flushdnscache": "Flush DNS Cache", "components.Settings.SettingsJobsCache.image-cache-cleanup": "Image Cache Cleanup", "components.Settings.SettingsJobsCache.imagecache": "Image Cache", "components.Settings.SettingsJobsCache.imagecacheDescription": "When enabled in settings, Jellyseerr will proxy and cache images from pre-configured external sources. Cached images are saved into your config folder. You can find the files in {appDataPath}/cache/images.",