From f900d623dc46b18a4d33c98fecf24a980ac45b39 Mon Sep 17 00:00:00 2001 From: Mark McDowall <mark@mcdowall.ca> Date: Sun, 14 Jul 2024 16:42:35 -0700 Subject: [PATCH] New: Allow major version updates to be installed (cherry picked from commit 0e95ba2021b23cc65bce0a0620dd48e355250dab) --- frontend/src/App/AppRoutes.js | 4 +- frontend/src/App/State/SystemAppState.ts | 3 + frontend/src/System/Updates/UpdateChanges.js | 52 --- frontend/src/System/Updates/UpdateChanges.tsx | 43 +++ frontend/src/System/Updates/Updates.js | 249 -------------- frontend/src/System/Updates/Updates.tsx | 303 ++++++++++++++++++ .../src/System/Updates/UpdatesConnector.js | 98 ------ frontend/src/typings/Update.ts | 20 ++ src/NzbDrone.Core/Localization/Core/ar.json | 2 +- src/NzbDrone.Core/Localization/Core/bg.json | 2 +- src/NzbDrone.Core/Localization/Core/ca.json | 2 +- src/NzbDrone.Core/Localization/Core/cs.json | 2 +- src/NzbDrone.Core/Localization/Core/da.json | 2 +- src/NzbDrone.Core/Localization/Core/de.json | 2 +- src/NzbDrone.Core/Localization/Core/el.json | 2 +- src/NzbDrone.Core/Localization/Core/en.json | 6 +- src/NzbDrone.Core/Localization/Core/es.json | 2 +- src/NzbDrone.Core/Localization/Core/fi.json | 2 +- src/NzbDrone.Core/Localization/Core/fr.json | 2 +- src/NzbDrone.Core/Localization/Core/he.json | 2 +- src/NzbDrone.Core/Localization/Core/hi.json | 2 +- src/NzbDrone.Core/Localization/Core/hu.json | 2 +- src/NzbDrone.Core/Localization/Core/is.json | 2 +- src/NzbDrone.Core/Localization/Core/it.json | 2 +- src/NzbDrone.Core/Localization/Core/ja.json | 2 +- src/NzbDrone.Core/Localization/Core/ko.json | 2 +- src/NzbDrone.Core/Localization/Core/nl.json | 2 +- src/NzbDrone.Core/Localization/Core/pl.json | 2 +- src/NzbDrone.Core/Localization/Core/pt.json | 2 +- .../Localization/Core/pt_BR.json | 2 +- src/NzbDrone.Core/Localization/Core/ro.json | 2 +- src/NzbDrone.Core/Localization/Core/ru.json | 2 +- src/NzbDrone.Core/Localization/Core/sv.json | 2 +- src/NzbDrone.Core/Localization/Core/th.json | 2 +- src/NzbDrone.Core/Localization/Core/tr.json | 2 +- src/NzbDrone.Core/Localization/Core/uk.json | 2 +- src/NzbDrone.Core/Localization/Core/vi.json | 2 +- .../Localization/Core/zh_CN.json | 2 +- .../Commands/ApplicationCheckUpdateCommand.cs | 2 + .../Commands/ApplicationUpdateCommand.cs | 1 + .../Update/InstallUpdateService.cs | 14 +- .../Update/UpdatePackageProvider.cs | 1 + 42 files changed, 419 insertions(+), 435 deletions(-) delete mode 100644 frontend/src/System/Updates/UpdateChanges.js create mode 100644 frontend/src/System/Updates/UpdateChanges.tsx delete mode 100644 frontend/src/System/Updates/Updates.js create mode 100644 frontend/src/System/Updates/Updates.tsx delete mode 100644 frontend/src/System/Updates/UpdatesConnector.js create mode 100644 frontend/src/typings/Update.ts diff --git a/frontend/src/App/AppRoutes.js b/frontend/src/App/AppRoutes.js index c5207e454..fa293dc22 100644 --- a/frontend/src/App/AppRoutes.js +++ b/frontend/src/App/AppRoutes.js @@ -31,7 +31,7 @@ import LogsTableConnector from 'System/Events/LogsTableConnector'; import Logs from 'System/Logs/Logs'; import Status from 'System/Status/Status'; import Tasks from 'System/Tasks/Tasks'; -import UpdatesConnector from 'System/Updates/UpdatesConnector'; +import Updates from 'System/Updates/Updates'; import getPathWithUrlBase from 'Utilities/getPathWithUrlBase'; import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector'; import MissingConnector from 'Wanted/Missing/MissingConnector'; @@ -228,7 +228,7 @@ function AppRoutes(props) { <Route path="/system/updates" - component={UpdatesConnector} + component={Updates} /> <Route diff --git a/frontend/src/App/State/SystemAppState.ts b/frontend/src/App/State/SystemAppState.ts index 7bfc6d5fb..1161f0e1e 100644 --- a/frontend/src/App/State/SystemAppState.ts +++ b/frontend/src/App/State/SystemAppState.ts @@ -2,18 +2,21 @@ import DiskSpace from 'typings/DiskSpace'; import Health from 'typings/Health'; import SystemStatus from 'typings/SystemStatus'; import Task from 'typings/Task'; +import Update from 'typings/Update'; import AppSectionState, { AppSectionItemState } from './AppSectionState'; export type DiskSpaceAppState = AppSectionState<DiskSpace>; export type HealthAppState = AppSectionState<Health>; export type SystemStatusAppState = AppSectionItemState<SystemStatus>; export type TaskAppState = AppSectionState<Task>; +export type UpdateAppState = AppSectionState<Update>; interface SystemAppState { diskSpace: DiskSpaceAppState; health: HealthAppState; status: SystemStatusAppState; tasks: TaskAppState; + updates: UpdateAppState; } export default SystemAppState; diff --git a/frontend/src/System/Updates/UpdateChanges.js b/frontend/src/System/Updates/UpdateChanges.js deleted file mode 100644 index a42e420fa..000000000 --- a/frontend/src/System/Updates/UpdateChanges.js +++ /dev/null @@ -1,52 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import InlineMarkdown from 'Components/Markdown/InlineMarkdown'; -import styles from './UpdateChanges.css'; - -class UpdateChanges extends Component { - - // - // Render - - render() { - const { - title, - changes - } = this.props; - - if (changes.length === 0) { - return null; - } - - const uniqueChanges = [...new Set(changes)]; - - return ( - <div> - <div className={styles.title}>{title}</div> - <ul> - { - uniqueChanges.map((change, index) => { - const checkChange = change.replace(/#\d{4,5}\b/g, (match, contents) => { - return `[${match}](https://github.com/Radarr/Radarr/issues/${match.substring(1)})`; - }); - - return ( - <li key={index}> - <InlineMarkdown data={checkChange} /> - </li> - ); - }) - } - </ul> - </div> - ); - } - -} - -UpdateChanges.propTypes = { - title: PropTypes.string.isRequired, - changes: PropTypes.arrayOf(PropTypes.string) -}; - -export default UpdateChanges; diff --git a/frontend/src/System/Updates/UpdateChanges.tsx b/frontend/src/System/Updates/UpdateChanges.tsx new file mode 100644 index 000000000..20338d011 --- /dev/null +++ b/frontend/src/System/Updates/UpdateChanges.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import InlineMarkdown from 'Components/Markdown/InlineMarkdown'; +import styles from './UpdateChanges.css'; + +interface UpdateChangesProps { + title: string; + changes: string[]; +} + +function UpdateChanges(props: UpdateChangesProps) { + const { title, changes } = props; + + if (changes.length === 0) { + return null; + } + + const uniqueChanges = [...new Set(changes)]; + + return ( + <div> + <div className={styles.title}>{title}</div> + <ul> + {uniqueChanges.map((change, index) => { + const checkChange = change.replace( + /#\d{4,5}\b/g, + (match) => + `[${match}](https://github.com/Radarr/Radarr/issues/${match.substring( + 1 + )})` + ); + + return ( + <li key={index}> + <InlineMarkdown data={checkChange} /> + </li> + ); + })} + </ul> + </div> + ); +} + +export default UpdateChanges; diff --git a/frontend/src/System/Updates/Updates.js b/frontend/src/System/Updates/Updates.js deleted file mode 100644 index aae373765..000000000 --- a/frontend/src/System/Updates/Updates.js +++ /dev/null @@ -1,249 +0,0 @@ -import _ from 'lodash'; -import PropTypes from 'prop-types'; -import React, { Component, Fragment } from 'react'; -import Alert from 'Components/Alert'; -import Icon from 'Components/Icon'; -import Label from 'Components/Label'; -import SpinnerButton from 'Components/Link/SpinnerButton'; -import LoadingIndicator from 'Components/Loading/LoadingIndicator'; -import InlineMarkdown from 'Components/Markdown/InlineMarkdown'; -import PageContent from 'Components/Page/PageContent'; -import PageContentBody from 'Components/Page/PageContentBody'; -import { icons, kinds } from 'Helpers/Props'; -import formatDate from 'Utilities/Date/formatDate'; -import formatDateTime from 'Utilities/Date/formatDateTime'; -import translate from 'Utilities/String/translate'; -import UpdateChanges from './UpdateChanges'; -import styles from './Updates.css'; - -class Updates extends Component { - - // - // Render - - render() { - const { - currentVersion, - isFetching, - isPopulated, - updatesError, - generalSettingsError, - items, - isInstallingUpdate, - updateMechanism, - updateMechanismMessage, - shortDateFormat, - longDateFormat, - timeFormat, - onInstallLatestPress - } = this.props; - - const hasError = !!(updatesError || generalSettingsError); - const hasUpdates = isPopulated && !hasError && items.length > 0; - const noUpdates = isPopulated && !hasError && !items.length; - const hasUpdateToInstall = hasUpdates && _.some(items, { installable: true, latest: true }); - const noUpdateToInstall = hasUpdates && !hasUpdateToInstall; - - const externalUpdaterPrefix = translate('UpdateRadarrDirectlyLoadError'); - const externalUpdaterMessages = { - external: translate('ExternalUpdater'), - apt: translate('AptUpdater'), - docker: translate('DockerUpdater') - }; - - return ( - <PageContent title={translate('Updates')}> - <PageContentBody> - { - !isPopulated && !hasError && - <LoadingIndicator /> - } - - { - noUpdates && - <Alert kind={kinds.INFO}> - {translate('NoUpdatesAreAvailable')} - </Alert> - } - - { - hasUpdateToInstall && - <div className={styles.messageContainer}> - { - updateMechanism === 'builtIn' || updateMechanism === 'script' ? - <SpinnerButton - className={styles.updateAvailable} - kind={kinds.PRIMARY} - isSpinning={isInstallingUpdate} - onPress={onInstallLatestPress} - > - {translate('InstallLatest')} - </SpinnerButton> : - - <Fragment> - <Icon - name={icons.WARNING} - kind={kinds.WARNING} - size={30} - /> - - <div className={styles.message}> - {externalUpdaterPrefix} <InlineMarkdown data={updateMechanismMessage || externalUpdaterMessages[updateMechanism] || externalUpdaterMessages.external} /> - </div> - </Fragment> - } - - { - isFetching && - <LoadingIndicator - className={styles.loading} - size={20} - /> - } - </div> - } - - { - noUpdateToInstall && - <div className={styles.messageContainer}> - <Icon - className={styles.upToDateIcon} - name={icons.CHECK_CIRCLE} - size={30} - /> - <div className={styles.message}> - {translate('OnLatestVersion')} - </div> - - { - isFetching && - <LoadingIndicator - className={styles.loading} - size={20} - /> - } - </div> - } - - { - hasUpdates && - <div> - { - items.map((update) => { - const hasChanges = !!update.changes; - - return ( - <div - key={update.version} - className={styles.update} - > - <div className={styles.info}> - <div className={styles.version}>{update.version}</div> - <div className={styles.space}>—</div> - <div - className={styles.date} - title={formatDateTime(update.releaseDate, longDateFormat, timeFormat)} - > - {formatDate(update.releaseDate, shortDateFormat)} - </div> - - { - update.branch === 'master' ? - null : - <Label - className={styles.label} - > - {update.branch} - </Label> - } - - { - update.version === currentVersion ? - <Label - className={styles.label} - kind={kinds.SUCCESS} - title={formatDateTime(update.installedOn, longDateFormat, timeFormat)} - > - {translate('CurrentlyInstalled')} - </Label> : - null - } - - { - update.version !== currentVersion && update.installedOn ? - <Label - className={styles.label} - kind={kinds.INVERSE} - title={formatDateTime(update.installedOn, longDateFormat, timeFormat)} - > - {translate('PreviouslyInstalled')} - </Label> : - null - } - </div> - - { - !hasChanges && - <div> - {translate('MaintenanceRelease')} - </div> - } - - { - hasChanges && - <div className={styles.changes}> - <UpdateChanges - title={translate('New')} - changes={update.changes.new} - /> - - <UpdateChanges - title={translate('Fixed')} - changes={update.changes.fixed} - /> - </div> - } - </div> - ); - }) - } - </div> - } - - { - !!updatesError && - <div> - {translate('FailedToFetchUpdates')} - </div> - } - - { - !!generalSettingsError && - <div> - {translate('FailedToUpdateSettings')} - </div> - } - </PageContentBody> - </PageContent> - ); - } - -} - -Updates.propTypes = { - currentVersion: PropTypes.string.isRequired, - isFetching: PropTypes.bool.isRequired, - isPopulated: PropTypes.bool.isRequired, - updatesError: PropTypes.object, - generalSettingsError: PropTypes.object, - items: PropTypes.array.isRequired, - isInstallingUpdate: PropTypes.bool.isRequired, - updateMechanism: PropTypes.string, - updateMechanismMessage: PropTypes.string, - shortDateFormat: PropTypes.string.isRequired, - longDateFormat: PropTypes.string.isRequired, - timeFormat: PropTypes.string.isRequired, - onInstallLatestPress: PropTypes.func.isRequired -}; - -export default Updates; diff --git a/frontend/src/System/Updates/Updates.tsx b/frontend/src/System/Updates/Updates.tsx new file mode 100644 index 000000000..df635e5d7 --- /dev/null +++ b/frontend/src/System/Updates/Updates.tsx @@ -0,0 +1,303 @@ +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { createSelector } from 'reselect'; +import AppState from 'App/State/AppState'; +import * as commandNames from 'Commands/commandNames'; +import Alert from 'Components/Alert'; +import Icon from 'Components/Icon'; +import Label from 'Components/Label'; +import SpinnerButton from 'Components/Link/SpinnerButton'; +import LoadingIndicator from 'Components/Loading/LoadingIndicator'; +import InlineMarkdown from 'Components/Markdown/InlineMarkdown'; +import ConfirmModal from 'Components/Modal/ConfirmModal'; +import PageContent from 'Components/Page/PageContent'; +import PageContentBody from 'Components/Page/PageContentBody'; +import { icons, kinds } from 'Helpers/Props'; +import { executeCommand } from 'Store/Actions/commandActions'; +import { fetchGeneralSettings } from 'Store/Actions/settingsActions'; +import { fetchUpdates } from 'Store/Actions/systemActions'; +import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; +import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector'; +import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector'; +import { UpdateMechanism } from 'typings/Settings/General'; +import formatDate from 'Utilities/Date/formatDate'; +import formatDateTime from 'Utilities/Date/formatDateTime'; +import translate from 'Utilities/String/translate'; +import UpdateChanges from './UpdateChanges'; +import styles from './Updates.css'; + +const VERSION_REGEX = /\d+\.\d+\.\d+\.\d+/i; + +function createUpdatesSelector() { + return createSelector( + (state: AppState) => state.system.updates, + (state: AppState) => state.settings.general, + (updates, generalSettings) => { + const { error: updatesError, items } = updates; + + const isFetching = updates.isFetching || generalSettings.isFetching; + const isPopulated = updates.isPopulated && generalSettings.isPopulated; + + return { + isFetching, + isPopulated, + updatesError, + generalSettingsError: generalSettings.error, + items, + updateMechanism: generalSettings.item.updateMechanism, + }; + } + ); +} + +function Updates() { + const currentVersion = useSelector((state: AppState) => state.app.version); + const { packageUpdateMechanismMessage } = useSelector( + createSystemStatusSelector() + ); + const { shortDateFormat, longDateFormat, timeFormat } = useSelector( + createUISettingsSelector() + ); + const isInstallingUpdate = useSelector( + createCommandExecutingSelector(commandNames.APPLICATION_UPDATE) + ); + + const { + isFetching, + isPopulated, + updatesError, + generalSettingsError, + items, + updateMechanism, + } = useSelector(createUpdatesSelector()); + + const dispatch = useDispatch(); + const [isMajorUpdateModalOpen, setIsMajorUpdateModalOpen] = useState(false); + const hasError = !!(updatesError || generalSettingsError); + const hasUpdates = isPopulated && !hasError && items.length > 0; + const noUpdates = isPopulated && !hasError && !items.length; + + const externalUpdaterPrefix = translate('UpdateAppDirectlyLoadError'); + const externalUpdaterMessages: Partial<Record<UpdateMechanism, string>> = { + external: translate('ExternalUpdater'), + apt: translate('AptUpdater'), + docker: translate('DockerUpdater'), + }; + + const { isMajorUpdate, hasUpdateToInstall } = useMemo(() => { + const majorVersion = parseInt( + currentVersion.match(VERSION_REGEX)?.[0] ?? '0' + ); + + const latestVersion = items[0]?.version; + const latestMajorVersion = parseInt( + latestVersion?.match(VERSION_REGEX)?.[0] ?? '0' + ); + + return { + isMajorUpdate: latestMajorVersion > majorVersion, + hasUpdateToInstall: items.some( + (update) => update.installable && update.latest + ), + }; + }, [currentVersion, items]); + + const noUpdateToInstall = hasUpdates && !hasUpdateToInstall; + + const handleInstallLatestPress = useCallback(() => { + if (isMajorUpdate) { + setIsMajorUpdateModalOpen(true); + } else { + dispatch(executeCommand({ name: commandNames.APPLICATION_UPDATE })); + } + }, [isMajorUpdate, setIsMajorUpdateModalOpen, dispatch]); + + const handleInstallLatestMajorVersionPress = useCallback(() => { + setIsMajorUpdateModalOpen(false); + + dispatch( + executeCommand({ + name: commandNames.APPLICATION_UPDATE, + installMajorUpdate: true, + }) + ); + }, [setIsMajorUpdateModalOpen, dispatch]); + + const handleCancelMajorVersionPress = useCallback(() => { + setIsMajorUpdateModalOpen(false); + }, [setIsMajorUpdateModalOpen]); + + useEffect(() => { + dispatch(fetchUpdates()); + dispatch(fetchGeneralSettings()); + }, [dispatch]); + + return ( + <PageContent title={translate('Updates')}> + <PageContentBody> + {isPopulated || hasError ? null : <LoadingIndicator />} + + {noUpdates ? ( + <Alert kind={kinds.INFO}>{translate('NoUpdatesAreAvailable')}</Alert> + ) : null} + + {hasUpdateToInstall ? ( + <div className={styles.messageContainer}> + {updateMechanism === 'builtIn' || updateMechanism === 'script' ? ( + <SpinnerButton + kind={kinds.PRIMARY} + isSpinning={isInstallingUpdate} + onPress={handleInstallLatestPress} + > + {translate('InstallLatest')} + </SpinnerButton> + ) : ( + <> + <Icon name={icons.WARNING} kind={kinds.WARNING} size={30} /> + + <div className={styles.message}> + {externalUpdaterPrefix}{' '} + <InlineMarkdown + data={ + packageUpdateMechanismMessage || + externalUpdaterMessages[updateMechanism] || + externalUpdaterMessages.external + } + /> + </div> + </> + )} + + {isFetching ? ( + <LoadingIndicator className={styles.loading} size={20} /> + ) : null} + </div> + ) : null} + + {noUpdateToInstall && ( + <div className={styles.messageContainer}> + <Icon + className={styles.upToDateIcon} + name={icons.CHECK_CIRCLE} + size={30} + /> + <div className={styles.message}>{translate('OnLatestVersion')}</div> + + {isFetching && ( + <LoadingIndicator className={styles.loading} size={20} /> + )} + </div> + )} + + {hasUpdates && ( + <div> + {items.map((update) => { + return ( + <div key={update.version} className={styles.update}> + <div className={styles.info}> + <div className={styles.version}>{update.version}</div> + <div className={styles.space}>—</div> + <div + className={styles.date} + title={formatDateTime( + update.releaseDate, + longDateFormat, + timeFormat + )} + > + {formatDate(update.releaseDate, shortDateFormat)} + </div> + + {update.branch === 'main' ? null : ( + <Label className={styles.label}>{update.branch}</Label> + )} + + {update.version === currentVersion ? ( + <Label + className={styles.label} + kind={kinds.SUCCESS} + title={formatDateTime( + update.installedOn, + longDateFormat, + timeFormat + )} + > + {translate('CurrentlyInstalled')} + </Label> + ) : null} + + {update.version !== currentVersion && update.installedOn ? ( + <Label + className={styles.label} + kind={kinds.INVERSE} + title={formatDateTime( + update.installedOn, + longDateFormat, + timeFormat + )} + > + {translate('PreviouslyInstalled')} + </Label> + ) : null} + </div> + + {update.changes ? ( + <div> + <UpdateChanges + title={translate('New')} + changes={update.changes.new} + /> + + <UpdateChanges + title={translate('Fixed')} + changes={update.changes.fixed} + /> + </div> + ) : ( + <div>{translate('MaintenanceRelease')}</div> + )} + </div> + ); + })} + </div> + )} + + {updatesError ? ( + <Alert kind={kinds.WARNING}> + {translate('FailedToFetchUpdates')} + </Alert> + ) : null} + + {generalSettingsError ? ( + <Alert kind={kinds.DANGER}> + {translate('FailedToUpdateSettings')} + </Alert> + ) : null} + + <ConfirmModal + isOpen={isMajorUpdateModalOpen} + kind={kinds.WARNING} + title={translate('InstallMajorVersionUpdate')} + message={ + <div> + <div>{translate('InstallMajorVersionUpdateMessage')}</div> + <div> + <InlineMarkdown + data={translate('InstallMajorVersionUpdateMessageLink', { + domain: 'radarr.video', + url: 'https://radarr.video/#downloads', + })} + /> + </div> + </div> + } + confirmLabel={translate('Install')} + onConfirm={handleInstallLatestMajorVersionPress} + onCancel={handleCancelMajorVersionPress} + /> + </PageContentBody> + </PageContent> + ); +} + +export default Updates; diff --git a/frontend/src/System/Updates/UpdatesConnector.js b/frontend/src/System/Updates/UpdatesConnector.js deleted file mode 100644 index 77d75dbda..000000000 --- a/frontend/src/System/Updates/UpdatesConnector.js +++ /dev/null @@ -1,98 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import * as commandNames from 'Commands/commandNames'; -import { executeCommand } from 'Store/Actions/commandActions'; -import { fetchGeneralSettings } from 'Store/Actions/settingsActions'; -import { fetchUpdates } from 'Store/Actions/systemActions'; -import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; -import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector'; -import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector'; -import Updates from './Updates'; - -function createMapStateToProps() { - return createSelector( - (state) => state.app.version, - createSystemStatusSelector(), - (state) => state.system.updates, - (state) => state.settings.general, - createUISettingsSelector(), - createCommandExecutingSelector(commandNames.APPLICATION_UPDATE), - ( - currentVersion, - status, - updates, - generalSettings, - uiSettings, - isInstallingUpdate - ) => { - const { - error: updatesError, - items - } = updates; - - const isFetching = updates.isFetching || generalSettings.isFetching; - const isPopulated = updates.isPopulated && generalSettings.isPopulated; - - return { - currentVersion, - isFetching, - isPopulated, - updatesError, - generalSettingsError: generalSettings.error, - items, - isInstallingUpdate, - updateMechanism: generalSettings.item.updateMechanism, - updateMechanismMessage: status.packageUpdateMechanismMessage, - shortDateFormat: uiSettings.shortDateFormat, - longDateFormat: uiSettings.longDateFormat, - timeFormat: uiSettings.timeFormat - }; - } - ); -} - -const mapDispatchToProps = { - dispatchFetchUpdates: fetchUpdates, - dispatchFetchGeneralSettings: fetchGeneralSettings, - dispatchExecuteCommand: executeCommand -}; - -class UpdatesConnector extends Component { - - // - // Lifecycle - - componentDidMount() { - this.props.dispatchFetchUpdates(); - this.props.dispatchFetchGeneralSettings(); - } - - // - // Listeners - - onInstallLatestPress = () => { - this.props.dispatchExecuteCommand({ name: commandNames.APPLICATION_UPDATE }); - }; - - // - // Render - - render() { - return ( - <Updates - onInstallLatestPress={this.onInstallLatestPress} - {...this.props} - /> - ); - } -} - -UpdatesConnector.propTypes = { - dispatchFetchUpdates: PropTypes.func.isRequired, - dispatchFetchGeneralSettings: PropTypes.func.isRequired, - dispatchExecuteCommand: PropTypes.func.isRequired -}; - -export default connect(createMapStateToProps, mapDispatchToProps)(UpdatesConnector); diff --git a/frontend/src/typings/Update.ts b/frontend/src/typings/Update.ts new file mode 100644 index 000000000..448b1728d --- /dev/null +++ b/frontend/src/typings/Update.ts @@ -0,0 +1,20 @@ +export interface Changes { + new: string[]; + fixed: string[]; +} + +interface Update { + version: string; + branch: string; + releaseDate: string; + fileName: string; + url: string; + installed: boolean; + installedOn: string; + installable: boolean; + latest: boolean; + changes: Changes | null; + hash: string; +} + +export default Update; diff --git a/src/NzbDrone.Core/Localization/Core/ar.json b/src/NzbDrone.Core/Localization/Core/ar.json index b437f7316..018c87cf7 100644 --- a/src/NzbDrone.Core/Localization/Core/ar.json +++ b/src/NzbDrone.Core/Localization/Core/ar.json @@ -56,7 +56,7 @@ "Unlimited": "غير محدود", "Ungroup": "فك التجميع", "Unavailable": "غير متوفره", - "UpdateRadarrDirectlyLoadError": "تعذر تحديث {appName} مباشرة ،", + "UpdateAppDirectlyLoadError": "تعذر تحديث {appName} مباشرة ،", "UiSettingsLoadError": "تعذر تحميل إعدادات واجهة المستخدم", "CalendarLoadError": "تعذر تحميل التقويم", "TagsLoadError": "تعذر تحميل العلامات", diff --git a/src/NzbDrone.Core/Localization/Core/bg.json b/src/NzbDrone.Core/Localization/Core/bg.json index b80b1e401..a8529a072 100644 --- a/src/NzbDrone.Core/Localization/Core/bg.json +++ b/src/NzbDrone.Core/Localization/Core/bg.json @@ -621,7 +621,7 @@ "UnableToLoadRootFolders": "Не може да се заредят коренови папки", "TagsLoadError": "Не може да се заредят маркери", "CalendarLoadError": "Календарът не може да се зареди", - "UpdateRadarrDirectlyLoadError": "Не може да се актуализира {appName} директно,", + "UpdateAppDirectlyLoadError": "Не може да се актуализира {appName} директно,", "Ungroup": "Разгрупиране", "Unlimited": "Неограничен", "UnmappedFilesOnly": "Само немапирани файлове", diff --git a/src/NzbDrone.Core/Localization/Core/ca.json b/src/NzbDrone.Core/Localization/Core/ca.json index d84131e64..700a44456 100644 --- a/src/NzbDrone.Core/Localization/Core/ca.json +++ b/src/NzbDrone.Core/Localization/Core/ca.json @@ -909,7 +909,7 @@ "TagsLoadError": "No es poden carregar les etiquetes", "CalendarLoadError": "No es pot carregar el calendari", "UiSettingsLoadError": "No es pot carregar la configuració de la IU", - "UpdateRadarrDirectlyLoadError": "No es pot actualitzar {appName} directament,", + "UpdateAppDirectlyLoadError": "No es pot actualitzar {appName} directament,", "Unreleased": "No disponible", "UnselectAll": "Desseleccioneu-ho tot", "UpdateCheckStartupNotWritableMessage": "L'actualització no es pot instal·lar perquè la carpeta d'inici '{startupFolder}' no té permisos d'escriptura per a l'usuari '{userName}'.", diff --git a/src/NzbDrone.Core/Localization/Core/cs.json b/src/NzbDrone.Core/Localization/Core/cs.json index 7382eefa6..d6edfbe11 100644 --- a/src/NzbDrone.Core/Localization/Core/cs.json +++ b/src/NzbDrone.Core/Localization/Core/cs.json @@ -901,7 +901,7 @@ "QualityProfilesLoadError": "Nelze načíst profily kvality", "UnableToLoadRootFolders": "Nelze načíst kořenové složky", "CalendarLoadError": "Kalendář nelze načíst", - "UpdateRadarrDirectlyLoadError": "{appName} nelze aktualizovat přímo,", + "UpdateAppDirectlyLoadError": "{appName} nelze aktualizovat přímo,", "UnmappedFilesOnly": "Pouze nezmapované soubory", "UnmappedFolders": "Nezmapované složky", "Unmonitored": "Nemonitorováno", diff --git a/src/NzbDrone.Core/Localization/Core/da.json b/src/NzbDrone.Core/Localization/Core/da.json index e96e44af2..e3deb2e06 100644 --- a/src/NzbDrone.Core/Localization/Core/da.json +++ b/src/NzbDrone.Core/Localization/Core/da.json @@ -159,7 +159,7 @@ "TagsSettingsSummary": "Se alle tags og hvordan de bruges. Ubrugte tags kan fjernes", "Time": "Tid", "MediaManagementSettingsLoadError": "Kan ikke indlæse indstillinger for mediestyring", - "UpdateRadarrDirectlyLoadError": "Kan ikke opdatere {appName} direkte,", + "UpdateAppDirectlyLoadError": "Kan ikke opdatere {appName} direkte,", "BindAddressHelpText": "Gyldig IP4-adresse, 'localhost' eller '*' for alle grænseflader", "CreateEmptyMovieFoldersHelpText": "Opret manglende filmmapper under diskscanning", "CouldNotConnectSignalR": "Kunne ikke oprette forbindelse til SignalR, UI opdateres ikke", diff --git a/src/NzbDrone.Core/Localization/Core/de.json b/src/NzbDrone.Core/Localization/Core/de.json index bd21479ca..7e4998d94 100644 --- a/src/NzbDrone.Core/Localization/Core/de.json +++ b/src/NzbDrone.Core/Localization/Core/de.json @@ -774,7 +774,7 @@ "UpgradesAllowed": "Upgrades erlaubt", "UnmappedFilesOnly": "Nur nicht zugeordnete Dateien", "Unlimited": "Unlimitiert", - "UpdateRadarrDirectlyLoadError": "{appName} konnte nicht direkt aktualisiert werden,", + "UpdateAppDirectlyLoadError": "{appName} konnte nicht direkt aktualisiert werden,", "UnableToLoadManualImportItems": "Einträge für manuelles importieren konnten nicht geladen werden", "AlternativeTitlesLoadError": "Alternative Titel konnten nicht geladen werden.", "Trigger": "Auslöser", diff --git a/src/NzbDrone.Core/Localization/Core/el.json b/src/NzbDrone.Core/Localization/Core/el.json index 90886d17d..a0b2e1309 100644 --- a/src/NzbDrone.Core/Localization/Core/el.json +++ b/src/NzbDrone.Core/Localization/Core/el.json @@ -893,7 +893,7 @@ "UnableToLoadRootFolders": "Δεν είναι δυνατή η φόρτωση ριζικών φακέλων", "TagsLoadError": "Δεν είναι δυνατή η φόρτωση ετικετών", "CalendarLoadError": "Δεν είναι δυνατή η φόρτωση του ημερολογίου", - "UpdateRadarrDirectlyLoadError": "Δεν είναι δυνατή η απευθείας ενημέρωση του {appName},", + "UpdateAppDirectlyLoadError": "Δεν είναι δυνατή η απευθείας ενημέρωση του {appName},", "Ungroup": "Κατάργηση ομάδας", "Unlimited": "Απεριόριστος", "UnmappedFilesOnly": "Μόνο μη αντιστοιχισμένα αρχεία", diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index ae7a54cc5..a5dc41ce0 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -795,7 +795,11 @@ "IndexersSettingsSummary": "Indexers and release restrictions", "Info": "Info", "InfoUrl": "Info URL", + "Install": "Install", "InstallLatest": "Install Latest", + "InstallMajorVersionUpdate": "Install Update", + "InstallMajorVersionUpdateMessage": "This update will install a new major version and may not be compatible with your system. Are you sure you want to install this update?", + "InstallMajorVersionUpdateMessageLink": "Please check [{domain}]({url}) for more information.", "InstanceName": "Instance Name", "InstanceNameHelpText": "Instance name in tab and for Syslog app name", "InteractiveImport": "Interactive Import", @@ -1774,6 +1778,7 @@ "UnsavedChanges": "Unsaved Changes", "UnselectAll": "Unselect All", "UpdateAll": "Update All", + "UpdateAppDirectlyLoadError": "Unable to update {appName} directly,", "UpdateAutomaticallyHelpText": "Automatically download and install updates. You will still be able to install from System: Updates", "UpdateAvailableHealthCheckMessage": "New update is available: {version}", "UpdateCheckStartupNotWritableMessage": "Cannot install update because startup folder '{startupFolder}' is not writable by the user '{userName}'.", @@ -1781,7 +1786,6 @@ "UpdateCheckUINotWritableMessage": "Cannot install update because UI folder '{uiFolder}' is not writable by the user '{userName}'.", "UpdateFiltered": "Update Filtered", "UpdateMechanismHelpText": "Use {appName}'s built-in updater or a script", - "UpdateRadarrDirectlyLoadError": "Unable to update {appName} directly,", "UpdateScriptPathHelpText": "Path to a custom script that takes an extracted update package and handle the remainder of the update process", "UpdateSelected": "Update Selected", "UpdaterLogFiles": "Updater Log Files", diff --git a/src/NzbDrone.Core/Localization/Core/es.json b/src/NzbDrone.Core/Localization/Core/es.json index 28a509be2..810583bc7 100644 --- a/src/NzbDrone.Core/Localization/Core/es.json +++ b/src/NzbDrone.Core/Localization/Core/es.json @@ -926,7 +926,7 @@ "Trigger": "Desencadenar", "Unlimited": "Ilimitado", "UnableToLoadManualImportItems": "No se pueden cargar elementos de importación manual", - "UpdateRadarrDirectlyLoadError": "No se puede actualizar {appName} directamente,", + "UpdateAppDirectlyLoadError": "No se puede actualizar {appName} directamente,", "UnmappedFilesOnly": "Solo archivos sin mapear", "UpgradeUntilCustomFormatScore": "Actualizar hasta la puntuación de formato personalizado", "UpgradeUntil": "Actualizar hasta", diff --git a/src/NzbDrone.Core/Localization/Core/fi.json b/src/NzbDrone.Core/Localization/Core/fi.json index 41d5ac72f..273289ba9 100644 --- a/src/NzbDrone.Core/Localization/Core/fi.json +++ b/src/NzbDrone.Core/Localization/Core/fi.json @@ -896,7 +896,7 @@ "UnableToLoadRootFolders": "Juurikansioiden lataus epäonnistui.", "TagsLoadError": "Tunnisteiden lataus ei onnistu", "CalendarLoadError": "Kalenterin lataus epäonnistui.", - "UpdateRadarrDirectlyLoadError": "{appName}ia ei voida päivittää suoraan,", + "UpdateAppDirectlyLoadError": "{appName}ia ei voida päivittää suoraan,", "Ungroup": "Pura ryhmä", "UnmappedFilesOnly": "Vain kohdistamattomat tiedostot", "UnmappedFolders": "Kohdistamattomat kansiot", diff --git a/src/NzbDrone.Core/Localization/Core/fr.json b/src/NzbDrone.Core/Localization/Core/fr.json index 9da523bd0..00be66ddb 100644 --- a/src/NzbDrone.Core/Localization/Core/fr.json +++ b/src/NzbDrone.Core/Localization/Core/fr.json @@ -934,7 +934,7 @@ "Trakt": "Trakt", "Trigger": "Déclencheur", "UnableToLoadManualImportItems": "Impossible de charger les éléments d'importation manuelle", - "UpdateRadarrDirectlyLoadError": "Impossible de mettre à jour {appName} directement,", + "UpdateAppDirectlyLoadError": "Impossible de mettre à jour {appName} directement,", "Unlimited": "Illimité", "UnmappedFilesOnly": "Fichiers non mappés uniquement", "UpgradeUntilCustomFormatScore": "Mise à niveau jusqu'au score de format personnalisé", diff --git a/src/NzbDrone.Core/Localization/Core/he.json b/src/NzbDrone.Core/Localization/Core/he.json index 774e1d9e1..7f48a470f 100644 --- a/src/NzbDrone.Core/Localization/Core/he.json +++ b/src/NzbDrone.Core/Localization/Core/he.json @@ -898,7 +898,7 @@ "UnableToLoadRootFolders": "לא ניתן לטעון תיקיות שורש", "CalendarLoadError": "לא ניתן לטעון את היומן", "Unlimited": "ללא הגבלה", - "UpdateRadarrDirectlyLoadError": "לא ניתן לעדכן את {appName} ישירות,", + "UpdateAppDirectlyLoadError": "לא ניתן לעדכן את {appName} ישירות,", "Ungroup": "בטל קבוצה", "UnmappedFilesOnly": "קבצים שלא ממופים בלבד", "UnmappedFolders": "תיקיות לא ממופות", diff --git a/src/NzbDrone.Core/Localization/Core/hi.json b/src/NzbDrone.Core/Localization/Core/hi.json index 82e035898..28b3170d4 100644 --- a/src/NzbDrone.Core/Localization/Core/hi.json +++ b/src/NzbDrone.Core/Localization/Core/hi.json @@ -241,7 +241,7 @@ "QualityProfilesLoadError": "गुणवत्ता प्रोफ़ाइल लोड करने में असमर्थ", "RemotePathMappingsLoadError": "दूरस्थ पथ मैपिंग लोड करने में असमर्थ", "CalendarLoadError": "कैलेंडर लोड करने में असमर्थ", - "UpdateRadarrDirectlyLoadError": "सीधे {appName} अद्यतन करने में असमर्थ,", + "UpdateAppDirectlyLoadError": "सीधे {appName} अद्यतन करने में असमर्थ,", "UnmappedFolders": "बिना मोड़े हुए फोल्डर", "ICalIncludeUnmonitoredMoviesHelpText": "ICal फीड में अनऑमिटर की गई फिल्में शामिल करें", "UnselectAll": "सभी का चयन रद्द", diff --git a/src/NzbDrone.Core/Localization/Core/hu.json b/src/NzbDrone.Core/Localization/Core/hu.json index 360416e1c..d8b4da173 100644 --- a/src/NzbDrone.Core/Localization/Core/hu.json +++ b/src/NzbDrone.Core/Localization/Core/hu.json @@ -846,7 +846,7 @@ "UpgradesAllowed": "Frissítések Engedélyezve", "UnmappedFilesOnly": "Kizárólag fel nem térképezett fájlokat", "Unlimited": "korlátlan", - "UpdateRadarrDirectlyLoadError": "Nem lehetséges közvetlenül frissíteni a {appName}-t", + "UpdateAppDirectlyLoadError": "Nem lehetséges közvetlenül frissíteni a {appName}-t", "UnableToLoadManualImportItems": "Nem lehetséges betölteni a manuálisan importált elemeket", "AlternativeTitlesLoadError": "Nem lehetséges betölteni az alternatív címeket.", "Trigger": "Trigger", diff --git a/src/NzbDrone.Core/Localization/Core/is.json b/src/NzbDrone.Core/Localization/Core/is.json index dac804092..a5dafe8bc 100644 --- a/src/NzbDrone.Core/Localization/Core/is.json +++ b/src/NzbDrone.Core/Localization/Core/is.json @@ -899,7 +899,7 @@ "UnableToLoadRootFolders": "Ekki er hægt að hlaða rótarmöppum", "TagsLoadError": "Ekki er hægt að hlaða merkin", "CalendarLoadError": "Ekki er hægt að hlaða dagatalið", - "UpdateRadarrDirectlyLoadError": "Ekki er hægt að uppfæra {appName} beint,", + "UpdateAppDirectlyLoadError": "Ekki er hægt að uppfæra {appName} beint,", "Ungroup": "Aftengja hópinn", "Unlimited": "Ótakmarkað", "UnmappedFilesOnly": "Aðeins ókortlagðar skrár", diff --git a/src/NzbDrone.Core/Localization/Core/it.json b/src/NzbDrone.Core/Localization/Core/it.json index c766a1979..77c4e8210 100644 --- a/src/NzbDrone.Core/Localization/Core/it.json +++ b/src/NzbDrone.Core/Localization/Core/it.json @@ -932,7 +932,7 @@ "Trigger": "Trigger", "UnableToLoadManualImportItems": "Impossibile caricare gli elementi di importazione manuale", "Unlimited": "Illimitato", - "UpdateRadarrDirectlyLoadError": "Impossibile aggiornare {appName} direttamente,", + "UpdateAppDirectlyLoadError": "Impossibile aggiornare {appName} direttamente,", "UnmappedFilesOnly": "Solo file non mappati", "UpgradeUntilCustomFormatScore": "Aggiorna fino al punteggio formato personalizzato", "UpgradeUntil": "Upgrade fino alla qualità", diff --git a/src/NzbDrone.Core/Localization/Core/ja.json b/src/NzbDrone.Core/Localization/Core/ja.json index c02ec68e4..157de467b 100644 --- a/src/NzbDrone.Core/Localization/Core/ja.json +++ b/src/NzbDrone.Core/Localization/Core/ja.json @@ -896,7 +896,7 @@ "UnableToLoadRootFolders": "ルートフォルダを読み込めません", "TagsLoadError": "タグを読み込めません", "CalendarLoadError": "カレンダーを読み込めません", - "UpdateRadarrDirectlyLoadError": "{appName}を直接更新できません。", + "UpdateAppDirectlyLoadError": "{appName}を直接更新できません。", "Ungroup": "グループ化を解除", "UnmappedFilesOnly": "マップされていないファイルのみ", "UnmappedFolders": "マップされていないフォルダ", diff --git a/src/NzbDrone.Core/Localization/Core/ko.json b/src/NzbDrone.Core/Localization/Core/ko.json index 2d3f8e185..23456bb35 100644 --- a/src/NzbDrone.Core/Localization/Core/ko.json +++ b/src/NzbDrone.Core/Localization/Core/ko.json @@ -899,7 +899,7 @@ "UnableToLoadRestrictions": "제한을 불러올 수 없습니다.", "UnableToLoadRootFolders": "루트 폴더를 불러올 수 없습니다.", "CalendarLoadError": "달력을 불러올 수 없습니다.", - "UpdateRadarrDirectlyLoadError": "{appName}를 직접 업데이트 할 수 없습니다.", + "UpdateAppDirectlyLoadError": "{appName}를 직접 업데이트 할 수 없습니다.", "Ungroup": "그룹 해제", "UnmappedFilesOnly": "매핑되지 않은 파일 만", "UnmappedFolders": "매핑되지 않은 폴더", diff --git a/src/NzbDrone.Core/Localization/Core/nl.json b/src/NzbDrone.Core/Localization/Core/nl.json index 2f9909b22..8764a475b 100644 --- a/src/NzbDrone.Core/Localization/Core/nl.json +++ b/src/NzbDrone.Core/Localization/Core/nl.json @@ -934,7 +934,7 @@ "Trakt": "Trakt", "Trigger": "In gang zetten", "UnableToLoadManualImportItems": "Kan items voor handmatig importeren niet laden", - "UpdateRadarrDirectlyLoadError": "Kan {appName} niet rechtstreeks updaten,", + "UpdateAppDirectlyLoadError": "Kan {appName} niet rechtstreeks updaten,", "Unlimited": "Onbeperkt", "UnmappedFilesOnly": "Alleen niet-toegewezen bestanden", "UpgradeUntilCustomFormatScore": "Upgraden tot Score aangepast formaat", diff --git a/src/NzbDrone.Core/Localization/Core/pl.json b/src/NzbDrone.Core/Localization/Core/pl.json index df6f9aa2c..5cc125898 100644 --- a/src/NzbDrone.Core/Localization/Core/pl.json +++ b/src/NzbDrone.Core/Localization/Core/pl.json @@ -901,7 +901,7 @@ "UnableToLoadRootFolders": "Nie można załadować folderów głównych", "TagsLoadError": "Nie można załadować tagów", "CalendarLoadError": "Nie można załadować kalendarza", - "UpdateRadarrDirectlyLoadError": "Nie można bezpośrednio zaktualizować {appName},", + "UpdateAppDirectlyLoadError": "Nie można bezpośrednio zaktualizować {appName},", "Ungroup": "Rozgrupuj", "Unlimited": "Nieograniczony", "Unmonitored": "Niemonitorowane", diff --git a/src/NzbDrone.Core/Localization/Core/pt.json b/src/NzbDrone.Core/Localization/Core/pt.json index 0d0a9f258..7fb42ff7d 100644 --- a/src/NzbDrone.Core/Localization/Core/pt.json +++ b/src/NzbDrone.Core/Localization/Core/pt.json @@ -934,7 +934,7 @@ "Trakt": "Trakt", "Trigger": "Acionador", "UnableToLoadManualImportItems": "Não foi possível carregar os itens de importação manual", - "UpdateRadarrDirectlyLoadError": "Não foi possível atualizar o {appName} diretamente,", + "UpdateAppDirectlyLoadError": "Não foi possível atualizar o {appName} diretamente,", "Unlimited": "Ilimitado", "UnmappedFilesOnly": "Somente ficheiros não mapeados", "UpgradeUntilCustomFormatScore": "Atualizar até a pontuação do formato personalizado", diff --git a/src/NzbDrone.Core/Localization/Core/pt_BR.json b/src/NzbDrone.Core/Localization/Core/pt_BR.json index 22ebe4532..e18661033 100644 --- a/src/NzbDrone.Core/Localization/Core/pt_BR.json +++ b/src/NzbDrone.Core/Localization/Core/pt_BR.json @@ -906,7 +906,7 @@ "Unlimited": "Ilimitado", "Ungroup": "Desagrupar", "Unavailable": "Indisponível", - "UpdateRadarrDirectlyLoadError": "Não foi possível carregar o {appName} diretamente,", + "UpdateAppDirectlyLoadError": "Não foi possível carregar o {appName} diretamente,", "UiSettingsLoadError": "Não foi possível carregar as configurações da interface", "CalendarLoadError": "Não foi possível carregar o calendário", "TagsLoadError": "Não foi possível carregar as tags", diff --git a/src/NzbDrone.Core/Localization/Core/ro.json b/src/NzbDrone.Core/Localization/Core/ro.json index 2e6f09358..36ea94c6b 100644 --- a/src/NzbDrone.Core/Localization/Core/ro.json +++ b/src/NzbDrone.Core/Localization/Core/ro.json @@ -912,7 +912,7 @@ "UnableToLoadRootFolders": "Imposibil de încărcat folderele rădăcină", "TagsLoadError": "Nu se pot încărca etichete", "CalendarLoadError": "Calendarul nu poate fi încărcat", - "UpdateRadarrDirectlyLoadError": "Imposibil de actualizat direct {appName},", + "UpdateAppDirectlyLoadError": "Imposibil de actualizat direct {appName},", "Ungroup": "Dezgrupează", "Unlimited": "Nelimitat", "UnmappedFilesOnly": "Numai fișiere nemapate", diff --git a/src/NzbDrone.Core/Localization/Core/ru.json b/src/NzbDrone.Core/Localization/Core/ru.json index 4b5627d5f..8d134e2ba 100644 --- a/src/NzbDrone.Core/Localization/Core/ru.json +++ b/src/NzbDrone.Core/Localization/Core/ru.json @@ -720,7 +720,7 @@ "Unlimited": "Неограниченно", "Ungroup": "Разгруппировать", "Unavailable": "Недоступно", - "UpdateRadarrDirectlyLoadError": "Невозможно обновить {appName} напрямую,", + "UpdateAppDirectlyLoadError": "Невозможно обновить {appName} напрямую,", "UiSettingsLoadError": "Не удалось загрузить настройки пользовательского интерфейса", "CalendarLoadError": "Не удалось загрузить календарь", "TagsLoadError": "Невозможно загрузить теги", diff --git a/src/NzbDrone.Core/Localization/Core/sv.json b/src/NzbDrone.Core/Localization/Core/sv.json index ca7f7d820..79c6e926b 100644 --- a/src/NzbDrone.Core/Localization/Core/sv.json +++ b/src/NzbDrone.Core/Localization/Core/sv.json @@ -935,7 +935,7 @@ "UnableToLoadRestrictions": "Det gick inte att ladda begränsningar", "UnableToLoadRootFolders": "Det gick inte att ladda rotmappar", "CalendarLoadError": "Det gick inte att ladda kalendern", - "UpdateRadarrDirectlyLoadError": "Det går inte att uppdatera {appName} direkt,", + "UpdateAppDirectlyLoadError": "Det går inte att uppdatera {appName} direkt,", "UpgradeUntilCustomFormatScore": "Uppgradera tills anpassat formatpoäng", "UpgradeUntil": "Uppgradera tills kvalitet", "UpgradeUntilThisQualityIsMetOrExceeded": "Uppgradera tills den här kvaliteten uppfylls eller överskrids", diff --git a/src/NzbDrone.Core/Localization/Core/th.json b/src/NzbDrone.Core/Localization/Core/th.json index 5f350362a..d70183180 100644 --- a/src/NzbDrone.Core/Localization/Core/th.json +++ b/src/NzbDrone.Core/Localization/Core/th.json @@ -907,7 +907,7 @@ "UnableToLoadRestrictions": "ไม่สามารถโหลดข้อ จำกัด", "UnableToLoadRootFolders": "ไม่สามารถโหลดโฟลเดอร์รูท", "CalendarLoadError": "ไม่สามารถโหลดปฏิทิน", - "UpdateRadarrDirectlyLoadError": "ไม่สามารถอัปเดต {appName} ได้โดยตรง", + "UpdateAppDirectlyLoadError": "ไม่สามารถอัปเดต {appName} ได้โดยตรง", "Ungroup": "ยกเลิกการจัดกลุ่ม", "Unlimited": "ไม่ จำกัด", "UnmappedFilesOnly": "ไฟล์ที่ไม่ได้แมปเท่านั้น", diff --git a/src/NzbDrone.Core/Localization/Core/tr.json b/src/NzbDrone.Core/Localization/Core/tr.json index a3b4c5de6..97ee048e4 100644 --- a/src/NzbDrone.Core/Localization/Core/tr.json +++ b/src/NzbDrone.Core/Localization/Core/tr.json @@ -338,7 +338,7 @@ "UnableToLoadRestrictions": "Kısıtlamalar yüklenemiyor", "UnableToLoadRootFolders": "Kök klasörler yüklenemiyor", "TagsLoadError": "Etiketler yüklenemiyor", - "UpdateRadarrDirectlyLoadError": "{appName} doğrudan güncellenemiyor,", + "UpdateAppDirectlyLoadError": "{appName} doğrudan güncellenemiyor,", "Ungroup": "Grubu çöz", "Unlimited": "Sınırsız", "UnmappedFilesOnly": "Yalnızca Eşlenmemiş Dosyalar", diff --git a/src/NzbDrone.Core/Localization/Core/uk.json b/src/NzbDrone.Core/Localization/Core/uk.json index 90b7947b0..fc596afd7 100644 --- a/src/NzbDrone.Core/Localization/Core/uk.json +++ b/src/NzbDrone.Core/Localization/Core/uk.json @@ -892,7 +892,7 @@ "QualityDefinitionsLoadError": "Не вдалося завантажити визначення якості", "RemotePathMappingsLoadError": "Неможливо завантажити віддалені відображення шляхів", "UnableToLoadRootFolders": "Не вдалося завантажити кореневі папки", - "UpdateRadarrDirectlyLoadError": "Неможливо оновити {appName} безпосередньо,", + "UpdateAppDirectlyLoadError": "Неможливо оновити {appName} безпосередньо,", "Unavailable": "Недоступний", "Unlimited": "Необмежений", "UnmappedFilesOnly": "Лише незіставлені файли", diff --git a/src/NzbDrone.Core/Localization/Core/vi.json b/src/NzbDrone.Core/Localization/Core/vi.json index 74becaf4e..6a58934f0 100644 --- a/src/NzbDrone.Core/Localization/Core/vi.json +++ b/src/NzbDrone.Core/Localization/Core/vi.json @@ -912,7 +912,7 @@ "QualityProfilesLoadError": "Không thể tải Hồ sơ chất lượng", "RemotePathMappingsLoadError": "Không thể tải Ánh xạ đường dẫn từ xa", "UnableToLoadRootFolders": "Không thể tải các thư mục gốc", - "UpdateRadarrDirectlyLoadError": "Không thể cập nhật {appName} trực tiếp,", + "UpdateAppDirectlyLoadError": "Không thể cập nhật {appName} trực tiếp,", "Ungroup": "Bỏ nhóm", "Unlimited": "Vô hạn", "UnmappedFilesOnly": "Chỉ các tệp chưa được ánh xạ", diff --git a/src/NzbDrone.Core/Localization/Core/zh_CN.json b/src/NzbDrone.Core/Localization/Core/zh_CN.json index 4a0e1bfc0..059441bb8 100644 --- a/src/NzbDrone.Core/Localization/Core/zh_CN.json +++ b/src/NzbDrone.Core/Localization/Core/zh_CN.json @@ -127,7 +127,7 @@ "Unmonitored": "未追踪项", "Unlimited": "无限制", "Unavailable": "不可用", - "UpdateRadarrDirectlyLoadError": "无法直接更新{appName}", + "UpdateAppDirectlyLoadError": "无法直接更新{appName}", "UiSettingsLoadError": "无法加载UI设置", "CalendarLoadError": "无法加载日历", "TagsLoadError": "无法加载标签", diff --git a/src/NzbDrone.Core/Update/Commands/ApplicationCheckUpdateCommand.cs b/src/NzbDrone.Core/Update/Commands/ApplicationCheckUpdateCommand.cs index 6987af3fa..86461405f 100644 --- a/src/NzbDrone.Core/Update/Commands/ApplicationCheckUpdateCommand.cs +++ b/src/NzbDrone.Core/Update/Commands/ApplicationCheckUpdateCommand.cs @@ -7,5 +7,7 @@ namespace NzbDrone.Core.Update.Commands public override bool SendUpdatesToClient => true; public override string CompletionMessage => null; + + public bool InstallMajorUpdate { get; set; } } } diff --git a/src/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs b/src/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs index 59a827a0b..6980af708 100644 --- a/src/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs +++ b/src/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs @@ -4,6 +4,7 @@ namespace NzbDrone.Core.Update.Commands { public class ApplicationUpdateCommand : Command { + public bool InstallMajorUpdate { get; set; } public override bool SendUpdatesToClient => true; public override bool IsExclusive => true; } diff --git a/src/NzbDrone.Core/Update/InstallUpdateService.cs b/src/NzbDrone.Core/Update/InstallUpdateService.cs index 91acb7952..0fe738bb5 100644 --- a/src/NzbDrone.Core/Update/InstallUpdateService.cs +++ b/src/NzbDrone.Core/Update/InstallUpdateService.cs @@ -231,7 +231,7 @@ namespace NzbDrone.Core.Update } } - private UpdatePackage GetUpdatePackage(CommandTrigger updateTrigger) + private UpdatePackage GetUpdatePackage(CommandTrigger updateTrigger, bool installMajorUpdate) { _logger.ProgressDebug("Checking for updates"); @@ -243,7 +243,13 @@ namespace NzbDrone.Core.Update return null; } - if (OsInfo.IsNotWindows && !_configFileProvider.UpdateAutomatically && updateTrigger != CommandTrigger.Manual) + if (latestAvailable.Version.Major > BuildInfo.Version.Major && !installMajorUpdate) + { + _logger.ProgressInfo("Unable to install major update, please update update manually from System: Updates"); + return null; + } + + if (!_configFileProvider.UpdateAutomatically && updateTrigger != CommandTrigger.Manual) { _logger.ProgressDebug("Auto-update not enabled, not installing available update."); return null; @@ -272,7 +278,7 @@ namespace NzbDrone.Core.Update public void Execute(ApplicationCheckUpdateCommand message) { - if (GetUpdatePackage(message.Trigger) != null) + if (GetUpdatePackage(message.Trigger, true) != null) { _commandQueueManager.Push(new ApplicationUpdateCommand(), trigger: message.Trigger); } @@ -280,7 +286,7 @@ namespace NzbDrone.Core.Update public void Execute(ApplicationUpdateCommand message) { - var latestAvailable = GetUpdatePackage(message.Trigger); + var latestAvailable = GetUpdatePackage(message.Trigger, message.InstallMajorUpdate); if (latestAvailable != null) { diff --git a/src/NzbDrone.Core/Update/UpdatePackageProvider.cs b/src/NzbDrone.Core/Update/UpdatePackageProvider.cs index 8d82fefcb..b0543366f 100644 --- a/src/NzbDrone.Core/Update/UpdatePackageProvider.cs +++ b/src/NzbDrone.Core/Update/UpdatePackageProvider.cs @@ -42,6 +42,7 @@ namespace NzbDrone.Core.Update .AddQueryParam("runtime", "netcore") .AddQueryParam("runtimeVer", _platformInfo.Version) .AddQueryParam("dbType", _mainDatabase.DatabaseType) + .AddQueryParam("includeMajorVersion", true) .SetSegment("branch", branch); if (_analyticsService.IsEnabled)