mirror of
https://github.com/Radarr/Radarr.git
synced 2024-10-29 23:22:39 +01:00
New: Allow major version updates to be installed
(cherry picked from commit 0e95ba2021b23cc65bce0a0620dd48e355250dab)
This commit is contained in:
parent
84b507faf3
commit
f900d623dc
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
43
frontend/src/System/Updates/UpdateChanges.tsx
Normal file
43
frontend/src/System/Updates/UpdateChanges.tsx
Normal file
@ -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;
|
@ -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;
|
303
frontend/src/System/Updates/Updates.tsx
Normal file
303
frontend/src/System/Updates/Updates.tsx
Normal file
@ -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;
|
@ -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);
|
20
frontend/src/typings/Update.ts
Normal file
20
frontend/src/typings/Update.ts
Normal file
@ -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;
|
@ -56,7 +56,7 @@
|
||||
"Unlimited": "غير محدود",
|
||||
"Ungroup": "فك التجميع",
|
||||
"Unavailable": "غير متوفره",
|
||||
"UpdateRadarrDirectlyLoadError": "تعذر تحديث {appName} مباشرة ،",
|
||||
"UpdateAppDirectlyLoadError": "تعذر تحديث {appName} مباشرة ،",
|
||||
"UiSettingsLoadError": "تعذر تحميل إعدادات واجهة المستخدم",
|
||||
"CalendarLoadError": "تعذر تحميل التقويم",
|
||||
"TagsLoadError": "تعذر تحميل العلامات",
|
||||
|
@ -621,7 +621,7 @@
|
||||
"UnableToLoadRootFolders": "Не може да се заредят коренови папки",
|
||||
"TagsLoadError": "Не може да се заредят маркери",
|
||||
"CalendarLoadError": "Календарът не може да се зареди",
|
||||
"UpdateRadarrDirectlyLoadError": "Не може да се актуализира {appName} директно,",
|
||||
"UpdateAppDirectlyLoadError": "Не може да се актуализира {appName} директно,",
|
||||
"Ungroup": "Разгрупиране",
|
||||
"Unlimited": "Неограничен",
|
||||
"UnmappedFilesOnly": "Само немапирани файлове",
|
||||
|
@ -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}'.",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -893,7 +893,7 @@
|
||||
"UnableToLoadRootFolders": "Δεν είναι δυνατή η φόρτωση ριζικών φακέλων",
|
||||
"TagsLoadError": "Δεν είναι δυνατή η φόρτωση ετικετών",
|
||||
"CalendarLoadError": "Δεν είναι δυνατή η φόρτωση του ημερολογίου",
|
||||
"UpdateRadarrDirectlyLoadError": "Δεν είναι δυνατή η απευθείας ενημέρωση του {appName},",
|
||||
"UpdateAppDirectlyLoadError": "Δεν είναι δυνατή η απευθείας ενημέρωση του {appName},",
|
||||
"Ungroup": "Κατάργηση ομάδας",
|
||||
"Unlimited": "Απεριόριστος",
|
||||
"UnmappedFilesOnly": "Μόνο μη αντιστοιχισμένα αρχεία",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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é",
|
||||
|
@ -898,7 +898,7 @@
|
||||
"UnableToLoadRootFolders": "לא ניתן לטעון תיקיות שורש",
|
||||
"CalendarLoadError": "לא ניתן לטעון את היומן",
|
||||
"Unlimited": "ללא הגבלה",
|
||||
"UpdateRadarrDirectlyLoadError": "לא ניתן לעדכן את {appName} ישירות,",
|
||||
"UpdateAppDirectlyLoadError": "לא ניתן לעדכן את {appName} ישירות,",
|
||||
"Ungroup": "בטל קבוצה",
|
||||
"UnmappedFilesOnly": "קבצים שלא ממופים בלבד",
|
||||
"UnmappedFolders": "תיקיות לא ממופות",
|
||||
|
@ -241,7 +241,7 @@
|
||||
"QualityProfilesLoadError": "गुणवत्ता प्रोफ़ाइल लोड करने में असमर्थ",
|
||||
"RemotePathMappingsLoadError": "दूरस्थ पथ मैपिंग लोड करने में असमर्थ",
|
||||
"CalendarLoadError": "कैलेंडर लोड करने में असमर्थ",
|
||||
"UpdateRadarrDirectlyLoadError": "सीधे {appName} अद्यतन करने में असमर्थ,",
|
||||
"UpdateAppDirectlyLoadError": "सीधे {appName} अद्यतन करने में असमर्थ,",
|
||||
"UnmappedFolders": "बिना मोड़े हुए फोल्डर",
|
||||
"ICalIncludeUnmonitoredMoviesHelpText": "ICal फीड में अनऑमिटर की गई फिल्में शामिल करें",
|
||||
"UnselectAll": "सभी का चयन रद्द",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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à",
|
||||
|
@ -896,7 +896,7 @@
|
||||
"UnableToLoadRootFolders": "ルートフォルダを読み込めません",
|
||||
"TagsLoadError": "タグを読み込めません",
|
||||
"CalendarLoadError": "カレンダーを読み込めません",
|
||||
"UpdateRadarrDirectlyLoadError": "{appName}を直接更新できません。",
|
||||
"UpdateAppDirectlyLoadError": "{appName}を直接更新できません。",
|
||||
"Ungroup": "グループ化を解除",
|
||||
"UnmappedFilesOnly": "マップされていないファイルのみ",
|
||||
"UnmappedFolders": "マップされていないフォルダ",
|
||||
|
@ -899,7 +899,7 @@
|
||||
"UnableToLoadRestrictions": "제한을 불러올 수 없습니다.",
|
||||
"UnableToLoadRootFolders": "루트 폴더를 불러올 수 없습니다.",
|
||||
"CalendarLoadError": "달력을 불러올 수 없습니다.",
|
||||
"UpdateRadarrDirectlyLoadError": "{appName}를 직접 업데이트 할 수 없습니다.",
|
||||
"UpdateAppDirectlyLoadError": "{appName}를 직접 업데이트 할 수 없습니다.",
|
||||
"Ungroup": "그룹 해제",
|
||||
"UnmappedFilesOnly": "매핑되지 않은 파일 만",
|
||||
"UnmappedFolders": "매핑되지 않은 폴더",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -720,7 +720,7 @@
|
||||
"Unlimited": "Неограниченно",
|
||||
"Ungroup": "Разгруппировать",
|
||||
"Unavailable": "Недоступно",
|
||||
"UpdateRadarrDirectlyLoadError": "Невозможно обновить {appName} напрямую,",
|
||||
"UpdateAppDirectlyLoadError": "Невозможно обновить {appName} напрямую,",
|
||||
"UiSettingsLoadError": "Не удалось загрузить настройки пользовательского интерфейса",
|
||||
"CalendarLoadError": "Не удалось загрузить календарь",
|
||||
"TagsLoadError": "Невозможно загрузить теги",
|
||||
|
@ -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",
|
||||
|
@ -907,7 +907,7 @@
|
||||
"UnableToLoadRestrictions": "ไม่สามารถโหลดข้อ จำกัด",
|
||||
"UnableToLoadRootFolders": "ไม่สามารถโหลดโฟลเดอร์รูท",
|
||||
"CalendarLoadError": "ไม่สามารถโหลดปฏิทิน",
|
||||
"UpdateRadarrDirectlyLoadError": "ไม่สามารถอัปเดต {appName} ได้โดยตรง",
|
||||
"UpdateAppDirectlyLoadError": "ไม่สามารถอัปเดต {appName} ได้โดยตรง",
|
||||
"Ungroup": "ยกเลิกการจัดกลุ่ม",
|
||||
"Unlimited": "ไม่ จำกัด",
|
||||
"UnmappedFilesOnly": "ไฟล์ที่ไม่ได้แมปเท่านั้น",
|
||||
|
@ -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",
|
||||
|
@ -892,7 +892,7 @@
|
||||
"QualityDefinitionsLoadError": "Не вдалося завантажити визначення якості",
|
||||
"RemotePathMappingsLoadError": "Неможливо завантажити віддалені відображення шляхів",
|
||||
"UnableToLoadRootFolders": "Не вдалося завантажити кореневі папки",
|
||||
"UpdateRadarrDirectlyLoadError": "Неможливо оновити {appName} безпосередньо,",
|
||||
"UpdateAppDirectlyLoadError": "Неможливо оновити {appName} безпосередньо,",
|
||||
"Unavailable": "Недоступний",
|
||||
"Unlimited": "Необмежений",
|
||||
"UnmappedFilesOnly": "Лише незіставлені файли",
|
||||
|
@ -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ạ",
|
||||
|
@ -127,7 +127,7 @@
|
||||
"Unmonitored": "未追踪项",
|
||||
"Unlimited": "无限制",
|
||||
"Unavailable": "不可用",
|
||||
"UpdateRadarrDirectlyLoadError": "无法直接更新{appName}",
|
||||
"UpdateAppDirectlyLoadError": "无法直接更新{appName}",
|
||||
"UiSettingsLoadError": "无法加载UI设置",
|
||||
"CalendarLoadError": "无法加载日历",
|
||||
"TagsLoadError": "无法加载标签",
|
||||
|
@ -7,5 +7,7 @@ public class ApplicationCheckUpdateCommand : Command
|
||||
public override bool SendUpdatesToClient => true;
|
||||
|
||||
public override string CompletionMessage => null;
|
||||
|
||||
public bool InstallMajorUpdate { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -231,7 +231,7 @@ private void EnsureAppDataSafety()
|
||||
}
|
||||
}
|
||||
|
||||
private UpdatePackage GetUpdatePackage(CommandTrigger updateTrigger)
|
||||
private UpdatePackage GetUpdatePackage(CommandTrigger updateTrigger, bool installMajorUpdate)
|
||||
{
|
||||
_logger.ProgressDebug("Checking for updates");
|
||||
|
||||
@ -243,7 +243,13 @@ private UpdatePackage GetUpdatePackage(CommandTrigger updateTrigger)
|
||||
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 @@ private UpdatePackage GetUpdatePackage(CommandTrigger updateTrigger)
|
||||
|
||||
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 @@ public void Execute(ApplicationCheckUpdateCommand message)
|
||||
|
||||
public void Execute(ApplicationUpdateCommand message)
|
||||
{
|
||||
var latestAvailable = GetUpdatePackage(message.Trigger);
|
||||
var latestAvailable = GetUpdatePackage(message.Trigger, message.InstallMajorUpdate);
|
||||
|
||||
if (latestAvailable != null)
|
||||
{
|
||||
|
@ -42,6 +42,7 @@ public UpdatePackage GetLatestUpdate(string branch, Version currentVersion)
|
||||
.AddQueryParam("runtime", "netcore")
|
||||
.AddQueryParam("runtimeVer", _platformInfo.Version)
|
||||
.AddQueryParam("dbType", _mainDatabase.DatabaseType)
|
||||
.AddQueryParam("includeMajorVersion", true)
|
||||
.SetSegment("branch", branch);
|
||||
|
||||
if (_analyticsService.IsEnabled)
|
||||
|
Loading…
Reference in New Issue
Block a user