From 73937c0ccd667daed67e320a7fbc032b1207ef53 Mon Sep 17 00:00:00 2001 From: Ludovic Ortega Date: Tue, 28 Jul 2026 01:41:46 +0200 Subject: [PATCH] feat: allow to disable version check (#3137) Signed-off-by: Ludovic Ortega --- seerr-api.yml | 13 ++- server/interfaces/api/settingsInterfaces.ts | 5 +- server/lib/settings/index.ts | 4 + server/routes/index.ts | 60 ++++++------ src/components/Layout/VersionStatus/index.tsx | 43 +++++---- .../Settings/SettingsAbout/index.tsx | 95 ++++++++++++------- .../Settings/SettingsMain/index.tsx | 22 +++++ src/components/StatusChecker/index.tsx | 9 +- src/context/SettingsContext.tsx | 1 + src/i18n/locale/en.json | 3 + src/pages/_app.tsx | 1 + 11 files changed, 170 insertions(+), 86 deletions(-) diff --git a/seerr-api.yml b/seerr-api.yml index cd0012b67..4b33bf789 100644 --- a/seerr-api.yml +++ b/seerr-api.yml @@ -250,6 +250,9 @@ components: enableSpecialEpisodes: type: boolean example: false + versionCheck: + type: boolean + example: false NetworkSettings: type: object properties: @@ -2151,10 +2154,18 @@ paths: /status: get: summary: Get Seerr status - description: Returns the current Seerr status in a JSON object. + description: Returns the current Seerr status in a JSON object. updateAvailable and commitsBehind are omitted when checkUpdateAvailable is false. security: [] tags: - public + parameters: + - in: query + name: checkUpdateAvailable + description: If false, updateAvailable and commitsBehind will be omitted from the response. Defaults to the versionCheck setting. + required: false + example: false + schema: + type: boolean responses: '200': description: Returned status diff --git a/server/interfaces/api/settingsInterfaces.ts b/server/interfaces/api/settingsInterfaces.ts index 3dfa61920..f2793c9aa 100644 --- a/server/interfaces/api/settingsInterfaces.ts +++ b/server/interfaces/api/settingsInterfaces.ts @@ -48,6 +48,7 @@ export interface PublicSettingsResponse { emailEnabled: boolean; newPlexLogin: boolean; youtubeUrl: string; + versionCheck: boolean; plexClientIdentifier: string; } @@ -75,7 +76,7 @@ export interface CacheResponse { export interface StatusResponse { version: string; commitTag: string; - updateAvailable: boolean; - commitsBehind: number; + updateAvailable?: boolean; + commitsBehind?: number; restartRequired: boolean; } diff --git a/server/lib/settings/index.ts b/server/lib/settings/index.ts index 010c43d81..0a896524e 100644 --- a/server/lib/settings/index.ts +++ b/server/lib/settings/index.ts @@ -156,6 +156,7 @@ export interface MainSettings { enableSpecialEpisodes: boolean; locale: string; youtubeUrl: string; + versionCheck: boolean; } export interface ProxySettings { @@ -214,6 +215,7 @@ interface FullPublicSettings extends PublicSettings { userEmailRequired: boolean; newPlexLogin: boolean; youtubeUrl: string; + versionCheck: boolean; plexClientIdentifier: string; } @@ -429,6 +431,7 @@ class Settings { enableSpecialEpisodes: false, locale: 'en', youtubeUrl: '', + versionCheck: true, }, plex: { name: '', @@ -734,6 +737,7 @@ class Settings { this.data.notifications.agents.email.options.userEmailRequired, newPlexLogin: this.data.main.newPlexLogin, youtubeUrl: this.data.main.youtubeUrl, + versionCheck: this.data.main.versionCheck, plexClientIdentifier: this.data.clientId, }; } diff --git a/server/routes/index.ts b/server/routes/index.ts index f701acf96..270104ecf 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -48,40 +48,47 @@ const router = Router(); router.use(checkUser); router.get('/status', async (req, res) => { - const githubApi = new GithubAPI(); - + const settings = getSettings(); const currentVersion = getAppVersion(); const commitTag = getCommitTag(); + const checkUpdate = + req.query.checkUpdateAvailable !== undefined + ? req.query.checkUpdateAvailable + : settings.fullPublicSettings.versionCheck; let updateAvailable = false; let commitsBehind = 0; - if (currentVersion.startsWith('develop-') && commitTag !== 'local') { - const commits = await githubApi.getSeerrCommits(); + if (checkUpdate) { + const githubApi = new GithubAPI(); - if (commits.length) { - const filteredCommits = commits.filter( - (commit) => !commit.commit.message.includes('[skip ci]') - ); - if (filteredCommits[0].sha !== commitTag) { - updateAvailable = true; + if (currentVersion.startsWith('develop-') && commitTag !== 'local') { + const commits = await githubApi.getSeerrCommits(); + + if (commits.length) { + const filteredCommits = commits.filter( + (commit) => !commit.commit.message.includes('[skip ci]') + ); + if (filteredCommits[0].sha !== commitTag) { + updateAvailable = true; + } + + const commitIndex = filteredCommits.findIndex( + (commit) => commit.sha === commitTag + ); + + if (updateAvailable) { + commitsBehind = commitIndex; + } } + } else if (commitTag !== 'local') { + const releases = await githubApi.getSeerrReleases(); - const commitIndex = filteredCommits.findIndex( - (commit) => commit.sha === commitTag - ); + if (releases.length) { + const latestVersion = releases[0]; - if (updateAvailable) { - commitsBehind = commitIndex; - } - } - } else if (commitTag !== 'local') { - const releases = await githubApi.getSeerrReleases(); - - if (releases.length) { - const latestVersion = releases[0]; - - if (!latestVersion.name.includes(currentVersion)) { - updateAvailable = true; + if (!latestVersion.name.includes(currentVersion)) { + updateAvailable = true; + } } } } @@ -89,8 +96,7 @@ router.get('/status', async (req, res) => { return res.status(200).json({ version: getAppVersion(), commitTag: getCommitTag(), - updateAvailable, - commitsBehind, + ...(checkUpdate && { updateAvailable, commitsBehind }), restartRequired: restartFlag.isSet(), }); }); diff --git a/src/components/Layout/VersionStatus/index.tsx b/src/components/Layout/VersionStatus/index.tsx index e70d8391d..1b4e428f2 100644 --- a/src/components/Layout/VersionStatus/index.tsx +++ b/src/components/Layout/VersionStatus/index.tsx @@ -1,3 +1,4 @@ +import useSettings from '@app/hooks/useSettings'; import defineMessages from '@app/utils/defineMessages'; import { ArrowUpCircleIcon, @@ -23,10 +24,14 @@ interface VersionStatusProps { } const VersionStatus = ({ onClick }: VersionStatusProps) => { + const settings = useSettings(); const intl = useIntl(); - const { data } = useSWR('/api/v1/status', { - refreshInterval: 60 * 1000, - }); + const { data } = useSWR( + `/api/v1/status?checkUpdateAvailable=${settings.currentSettings.versionCheck}`, + { + refreshInterval: 60 * 1000, + } + ); if (!data) { return null; @@ -65,21 +70,23 @@ const VersionStatus = ({ onClick }: VersionStatusProps) => { )}
{versionStream} - - {data.commitTag === 'local' ? ( - '(⌐■_■)' - ) : data.commitsBehind > 0 ? ( - intl.formatMessage(messages.commitsbehind, { - commitsBehind: data.commitsBehind, - }) - ) : data.commitsBehind === -1 ? ( - intl.formatMessage(messages.outofdate) - ) : ( - - {data.version.replace('develop-', '')} - - )} - + {data.commitsBehind !== undefined && ( + + {data.commitTag === 'local' ? ( + '(⌐■_■)' + ) : data.commitsBehind > 0 ? ( + intl.formatMessage(messages.commitsbehind, { + commitsBehind: data.commitsBehind, + }) + ) : data.commitsBehind === -1 ? ( + intl.formatMessage(messages.outofdate) + ) : ( + + {data.version.replace('develop-', '')} + + )} + + )}
{data.updateAvailable && } diff --git a/src/components/Settings/SettingsAbout/index.tsx b/src/components/Settings/SettingsAbout/index.tsx index f5161fcad..dbfcfe184 100644 --- a/src/components/Settings/SettingsAbout/index.tsx +++ b/src/components/Settings/SettingsAbout/index.tsx @@ -4,6 +4,7 @@ import List from '@app/components/Common/List'; import LoadingSpinner from '@app/components/Common/LoadingSpinner'; import PageTitle from '@app/components/Common/PageTitle'; import Releases from '@app/components/Settings/SettingsAbout/Releases'; +import useSettings from '@app/hooks/useSettings'; import globalMessages from '@app/i18n/globalMessages'; import ErrorPage from '@app/pages/_error'; import defineMessages from '@app/utils/defineMessages'; @@ -29,17 +30,21 @@ const messages = defineMessages('components.Settings.SettingsAbout', { documentation: 'Documentation', outofdate: 'Out of Date', uptodate: 'Up to Date', + versionCheckDisabled: 'Version Check Disabled', runningDevelop: 'You are running the develop branch of Seerr, which is only recommended for those contributing to development or assisting with bleeding-edge testing.', }); const SettingsAbout = () => { + const settings = useSettings(); const intl = useIntl(); const { data, error } = useSWR( '/api/v1/settings/about' ); - const { data: status } = useSWR('/api/v1/status'); + const { data: status } = useSWR( + settings.currentSettings.versionCheck ? '/api/v1/status' : null + ); if (!data && !error) { return ; @@ -75,42 +80,62 @@ const SettingsAbout = () => { {data.version.replace('develop-', '')} - {status?.commitTag !== 'local' && - (status?.updateAvailable ? ( - - - {intl.formatMessage(messages.outofdate)} - - - ) : ( - - + {intl.formatMessage(messages.outofdate)} + + + ) : ( + - {intl.formatMessage(messages.uptodate)} - - - ))} + + {intl.formatMessage(messages.uptodate)} + + + ) + ) : null + ) : ( + + + {intl.formatMessage(messages.versionCheckDisabled)} + + + )} {intl.formatNumber(data.totalMediaItems)} diff --git a/src/components/Settings/SettingsMain/index.tsx b/src/components/Settings/SettingsMain/index.tsx index 00703ddd3..259a297a7 100644 --- a/src/components/Settings/SettingsMain/index.tsx +++ b/src/components/Settings/SettingsMain/index.tsx @@ -74,6 +74,8 @@ const messages = defineMessages('components.Settings.SettingsMain', { youtubeUrl: 'YouTube URL', youtubeUrlTip: 'Base URL for YouTube videos if a self-hosted YouTube instance is used.', + versionCheck: 'Version Check', + versionCheckTip: 'Automatically check for new versions on GitHub.', validationUrl: 'You must provide a valid URL', validationUrlTrailingSlash: 'URL must not end in a trailing slash', }); @@ -183,6 +185,7 @@ const SettingsMain = () => { enableSpecialEpisodes: data?.enableSpecialEpisodes, cacheImages: data?.cacheImages, youtubeUrl: data?.youtubeUrl, + versionCheck: data?.versionCheck, }} enableReinitialize validationSchema={MainSettingsSchema} @@ -205,6 +208,7 @@ const SettingsMain = () => { enableSpecialEpisodes: values.enableSpecialEpisodes, cacheImages: values.cacheImages, youtubeUrl: values.youtubeUrl, + versionCheck: values?.versionCheck, }); mutate('/api/v1/settings/public'); mutate('/api/v1/status'); @@ -607,6 +611,24 @@ const SettingsMain = () => { )} +
+ +
+ { + setFieldValue('versionCheck', !values.versionCheck); + }} + /> +
+
diff --git a/src/components/StatusChecker/index.tsx b/src/components/StatusChecker/index.tsx index 9ec17c987..5190b5603 100644 --- a/src/components/StatusChecker/index.tsx +++ b/src/components/StatusChecker/index.tsx @@ -23,9 +23,12 @@ const StatusChecker = () => { const intl = useIntl(); const settings = useSettings(); const { hasPermission } = useUser(); - const { data, error } = useSWR('/api/v1/status', { - refreshInterval: 60 * 1000, - }); + const { data, error } = useSWR( + '/api/v1/status?checkUpdateAvailable=false', + { + refreshInterval: 60 * 1000, + } + ); const [alertDismissed, setAlertDismissed] = useState(false); useEffect(() => { diff --git a/src/context/SettingsContext.tsx b/src/context/SettingsContext.tsx index f2ed11675..25fff9c2f 100644 --- a/src/context/SettingsContext.tsx +++ b/src/context/SettingsContext.tsx @@ -31,6 +31,7 @@ const defaultSettings = { emailEnabled: false, newPlexLogin: true, youtubeUrl: '', + versionCheck: true, plexClientIdentifier: '', }; diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 2a880430f..c456e21e5 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -920,6 +920,7 @@ "components.Settings.SettingsAbout.totalrequests": "Total Requests", "components.Settings.SettingsAbout.uptodate": "Up to Date", "components.Settings.SettingsAbout.version": "Version", + "components.Settings.SettingsAbout.versionCheckDisabled": "Version Check Disabled", "components.Settings.SettingsJobsCache.availability-sync": "Media Availability Sync", "components.Settings.SettingsJobsCache.cache": "Cache", "components.Settings.SettingsJobsCache.cacheDescription": "Seerr caches requests to external API endpoints to optimize performance and avoid making unnecessary API calls.", @@ -1045,6 +1046,8 @@ "components.Settings.SettingsMain.validationApplicationUrlTrailingSlash": "URL must not end in a trailing slash", "components.Settings.SettingsMain.validationUrl": "You must provide a valid URL", "components.Settings.SettingsMain.validationUrlTrailingSlash": "URL must not end in a trailing slash", + "components.Settings.SettingsMain.versionCheck": "Version Check", + "components.Settings.SettingsMain.versionCheckTip": "Automatically check for new versions on GitHub.", "components.Settings.SettingsMain.youtubeUrl": "YouTube URL", "components.Settings.SettingsMain.youtubeUrlTip": "Base URL for YouTube videos if a self-hosted YouTube instance is used.", "components.Settings.SettingsNetwork.apiRequestTimeout": "API Request Timeout", diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 4159fa526..381a0cf8f 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -261,6 +261,7 @@ CoreApp.getInitialProps = async (initialProps) => { emailEnabled: false, newPlexLogin: true, youtubeUrl: '', + versionCheck: true, plexClientIdentifier: '', };