feat: allow to disable version check (#3137)

Signed-off-by: Ludovic Ortega <ludovic.ortega@adminafk.fr>
This commit is contained in:
Ludovic Ortega
2026-07-28 01:41:46 +02:00
committed by GitHub
parent f484791105
commit 73937c0ccd
11 changed files with 170 additions and 86 deletions

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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,
};
}

View File

@@ -48,40 +48,47 @@ const router = Router();
router.use(checkUser);
router.get<unknown, StatusResponse>('/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<unknown, StatusResponse>('/status', async (req, res) => {
return res.status(200).json({
version: getAppVersion(),
commitTag: getCommitTag(),
updateAvailable,
commitsBehind,
...(checkUpdate && { updateAvailable, commitsBehind }),
restartRequired: restartFlag.isSet(),
});
});

View File

@@ -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<StatusResponse>('/api/v1/status', {
refreshInterval: 60 * 1000,
});
const { data } = useSWR<StatusResponse>(
`/api/v1/status?checkUpdateAvailable=${settings.currentSettings.versionCheck}`,
{
refreshInterval: 60 * 1000,
}
);
if (!data) {
return null;
@@ -65,21 +70,23 @@ const VersionStatus = ({ onClick }: VersionStatusProps) => {
)}
<div className="flex min-w-0 flex-1 flex-col truncate px-2 last:pr-0">
<span className="font-bold">{versionStream}</span>
<span className="truncate">
{data.commitTag === 'local' ? (
'(⌐■_■)'
) : data.commitsBehind > 0 ? (
intl.formatMessage(messages.commitsbehind, {
commitsBehind: data.commitsBehind,
})
) : data.commitsBehind === -1 ? (
intl.formatMessage(messages.outofdate)
) : (
<code className="bg-transparent p-0">
{data.version.replace('develop-', '')}
</code>
)}
</span>
{data.commitsBehind !== undefined && (
<span className="truncate">
{data.commitTag === 'local' ? (
'(⌐■_■)'
) : data.commitsBehind > 0 ? (
intl.formatMessage(messages.commitsbehind, {
commitsBehind: data.commitsBehind,
})
) : data.commitsBehind === -1 ? (
intl.formatMessage(messages.outofdate)
) : (
<code className="bg-transparent p-0">
{data.version.replace('develop-', '')}
</code>
)}
</span>
)}
</div>
{data.updateAvailable && <ArrowUpCircleIcon className="h-6 w-6" />}
</Link>

View File

@@ -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 <code>develop</code> 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<SettingsAboutResponse>(
'/api/v1/settings/about'
);
const { data: status } = useSWR<StatusResponse>('/api/v1/status');
const { data: status } = useSWR<StatusResponse>(
settings.currentSettings.versionCheck ? '/api/v1/status' : null
);
if (!data && !error) {
return <LoadingSpinner />;
@@ -75,42 +80,62 @@ const SettingsAbout = () => {
<code className="truncate">
{data.version.replace('develop-', '')}
</code>
{status?.commitTag !== 'local' &&
(status?.updateAvailable ? (
<a
href={
data.version.startsWith('develop-')
? `https://github.com/seerr-team/seerr/compare/${status.commitTag}...develop`
: 'https://github.com/seerr-team/seerr/releases'
}
target="_blank"
rel="noopener noreferrer"
>
<Badge
badgeType="warning"
className="ml-2 !cursor-pointer transition hover:bg-yellow-400"
{settings.currentSettings.versionCheck ? (
status && status.commitTag !== 'local' ? (
status.updateAvailable ? (
<a
href={
data.version.startsWith('develop-')
? `https://github.com/seerr-team/seerr/compare/${status.commitTag}...develop`
: 'https://github.com/seerr-team/seerr/releases'
}
target="_blank"
rel="noopener noreferrer"
>
{intl.formatMessage(messages.outofdate)}
</Badge>
</a>
) : (
<a
href={
data.version.startsWith('develop-')
? 'https://github.com/seerr-team/seerr/commits/develop'
: 'https://github.com/seerr-team/seerr/releases'
}
target="_blank"
rel="noopener noreferrer"
>
<Badge
badgeType="success"
className="ml-2 !cursor-pointer transition hover:bg-green-400"
<Badge
badgeType="warning"
className="ml-2 !cursor-pointer transition hover:bg-yellow-400"
>
{intl.formatMessage(messages.outofdate)}
</Badge>
</a>
) : (
<a
href={
data.version.startsWith('develop-')
? 'https://github.com/seerr-team/seerr/commits/develop'
: 'https://github.com/seerr-team/seerr/releases'
}
target="_blank"
rel="noopener noreferrer"
>
{intl.formatMessage(messages.uptodate)}
</Badge>
</a>
))}
<Badge
badgeType="success"
className="ml-2 !cursor-pointer transition hover:bg-green-400"
>
{intl.formatMessage(messages.uptodate)}
</Badge>
</a>
)
) : null
) : (
<a
href={
data.version.startsWith('develop-')
? 'https://github.com/seerr-team/seerr/commits/develop'
: 'https://github.com/seerr-team/seerr/releases'
}
target="_blank"
rel="noopener noreferrer"
>
<Badge
badgeType="primary"
className="ml-2 !cursor-pointer transition hover:bg-yellow-400"
>
{intl.formatMessage(messages.versionCheckDisabled)}
</Badge>
</a>
)}
</List.Item>
<List.Item title={intl.formatMessage(messages.totalmedia)}>
{intl.formatNumber(data.totalMediaItems)}

View File

@@ -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 = () => {
)}
</div>
</div>
<div className="form-row">
<label htmlFor="versionCheck" className="text-label">
{intl.formatMessage(messages.versionCheck)}
<span className="label-tip">
{intl.formatMessage(messages.versionCheckTip)}
</span>
</label>
<div className="form-input-area">
<Field
type="checkbox"
id="versionCheck"
name="versionCheck"
onChange={() => {
setFieldValue('versionCheck', !values.versionCheck);
}}
/>
</div>
</div>
<div className="actions">
<div className="flex justify-end">
<span className="ml-3 inline-flex rounded-md shadow-sm">

View File

@@ -23,9 +23,12 @@ const StatusChecker = () => {
const intl = useIntl();
const settings = useSettings();
const { hasPermission } = useUser();
const { data, error } = useSWR<StatusResponse>('/api/v1/status', {
refreshInterval: 60 * 1000,
});
const { data, error } = useSWR<StatusResponse>(
'/api/v1/status?checkUpdateAvailable=false',
{
refreshInterval: 60 * 1000,
}
);
const [alertDismissed, setAlertDismissed] = useState(false);
useEffect(() => {

View File

@@ -31,6 +31,7 @@ const defaultSettings = {
emailEnabled: false,
newPlexLogin: true,
youtubeUrl: '',
versionCheck: true,
plexClientIdentifier: '',
};

View File

@@ -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",

View File

@@ -261,6 +261,7 @@ CoreApp.getInitialProps = async (initialProps) => {
emailEnabled: false,
newPlexLogin: true,
youtubeUrl: '',
versionCheck: true,
plexClientIdentifier: '',
};