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 Logs from 'System/Logs/Logs';
|
||||||
import Status from 'System/Status/Status';
|
import Status from 'System/Status/Status';
|
||||||
import Tasks from 'System/Tasks/Tasks';
|
import Tasks from 'System/Tasks/Tasks';
|
||||||
import UpdatesConnector from 'System/Updates/UpdatesConnector';
|
import Updates from 'System/Updates/Updates';
|
||||||
import getPathWithUrlBase from 'Utilities/getPathWithUrlBase';
|
import getPathWithUrlBase from 'Utilities/getPathWithUrlBase';
|
||||||
import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector';
|
import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector';
|
||||||
import MissingConnector from 'Wanted/Missing/MissingConnector';
|
import MissingConnector from 'Wanted/Missing/MissingConnector';
|
||||||
@ -228,7 +228,7 @@ function AppRoutes(props) {
|
|||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/system/updates"
|
path="/system/updates"
|
||||||
component={UpdatesConnector}
|
component={Updates}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
|
@ -2,18 +2,21 @@ import DiskSpace from 'typings/DiskSpace';
|
|||||||
import Health from 'typings/Health';
|
import Health from 'typings/Health';
|
||||||
import SystemStatus from 'typings/SystemStatus';
|
import SystemStatus from 'typings/SystemStatus';
|
||||||
import Task from 'typings/Task';
|
import Task from 'typings/Task';
|
||||||
|
import Update from 'typings/Update';
|
||||||
import AppSectionState, { AppSectionItemState } from './AppSectionState';
|
import AppSectionState, { AppSectionItemState } from './AppSectionState';
|
||||||
|
|
||||||
export type DiskSpaceAppState = AppSectionState<DiskSpace>;
|
export type DiskSpaceAppState = AppSectionState<DiskSpace>;
|
||||||
export type HealthAppState = AppSectionState<Health>;
|
export type HealthAppState = AppSectionState<Health>;
|
||||||
export type SystemStatusAppState = AppSectionItemState<SystemStatus>;
|
export type SystemStatusAppState = AppSectionItemState<SystemStatus>;
|
||||||
export type TaskAppState = AppSectionState<Task>;
|
export type TaskAppState = AppSectionState<Task>;
|
||||||
|
export type UpdateAppState = AppSectionState<Update>;
|
||||||
|
|
||||||
interface SystemAppState {
|
interface SystemAppState {
|
||||||
diskSpace: DiskSpaceAppState;
|
diskSpace: DiskSpaceAppState;
|
||||||
health: HealthAppState;
|
health: HealthAppState;
|
||||||
status: SystemStatusAppState;
|
status: SystemStatusAppState;
|
||||||
tasks: TaskAppState;
|
tasks: TaskAppState;
|
||||||
|
updates: UpdateAppState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SystemAppState;
|
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": "غير محدود",
|
"Unlimited": "غير محدود",
|
||||||
"Ungroup": "فك التجميع",
|
"Ungroup": "فك التجميع",
|
||||||
"Unavailable": "غير متوفره",
|
"Unavailable": "غير متوفره",
|
||||||
"UpdateRadarrDirectlyLoadError": "تعذر تحديث {appName} مباشرة ،",
|
"UpdateAppDirectlyLoadError": "تعذر تحديث {appName} مباشرة ،",
|
||||||
"UiSettingsLoadError": "تعذر تحميل إعدادات واجهة المستخدم",
|
"UiSettingsLoadError": "تعذر تحميل إعدادات واجهة المستخدم",
|
||||||
"CalendarLoadError": "تعذر تحميل التقويم",
|
"CalendarLoadError": "تعذر تحميل التقويم",
|
||||||
"TagsLoadError": "تعذر تحميل العلامات",
|
"TagsLoadError": "تعذر تحميل العلامات",
|
||||||
|
@ -621,7 +621,7 @@
|
|||||||
"UnableToLoadRootFolders": "Не може да се заредят коренови папки",
|
"UnableToLoadRootFolders": "Не може да се заредят коренови папки",
|
||||||
"TagsLoadError": "Не може да се заредят маркери",
|
"TagsLoadError": "Не може да се заредят маркери",
|
||||||
"CalendarLoadError": "Календарът не може да се зареди",
|
"CalendarLoadError": "Календарът не може да се зареди",
|
||||||
"UpdateRadarrDirectlyLoadError": "Не може да се актуализира {appName} директно,",
|
"UpdateAppDirectlyLoadError": "Не може да се актуализира {appName} директно,",
|
||||||
"Ungroup": "Разгрупиране",
|
"Ungroup": "Разгрупиране",
|
||||||
"Unlimited": "Неограничен",
|
"Unlimited": "Неограничен",
|
||||||
"UnmappedFilesOnly": "Само немапирани файлове",
|
"UnmappedFilesOnly": "Само немапирани файлове",
|
||||||
|
@ -909,7 +909,7 @@
|
|||||||
"TagsLoadError": "No es poden carregar les etiquetes",
|
"TagsLoadError": "No es poden carregar les etiquetes",
|
||||||
"CalendarLoadError": "No es pot carregar el calendari",
|
"CalendarLoadError": "No es pot carregar el calendari",
|
||||||
"UiSettingsLoadError": "No es pot carregar la configuració de la IU",
|
"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",
|
"Unreleased": "No disponible",
|
||||||
"UnselectAll": "Desseleccioneu-ho tot",
|
"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}'.",
|
"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",
|
"QualityProfilesLoadError": "Nelze načíst profily kvality",
|
||||||
"UnableToLoadRootFolders": "Nelze načíst kořenové složky",
|
"UnableToLoadRootFolders": "Nelze načíst kořenové složky",
|
||||||
"CalendarLoadError": "Kalendář nelze načíst",
|
"CalendarLoadError": "Kalendář nelze načíst",
|
||||||
"UpdateRadarrDirectlyLoadError": "{appName} nelze aktualizovat přímo,",
|
"UpdateAppDirectlyLoadError": "{appName} nelze aktualizovat přímo,",
|
||||||
"UnmappedFilesOnly": "Pouze nezmapované soubory",
|
"UnmappedFilesOnly": "Pouze nezmapované soubory",
|
||||||
"UnmappedFolders": "Nezmapované složky",
|
"UnmappedFolders": "Nezmapované složky",
|
||||||
"Unmonitored": "Nemonitorováno",
|
"Unmonitored": "Nemonitorováno",
|
||||||
|
@ -159,7 +159,7 @@
|
|||||||
"TagsSettingsSummary": "Se alle tags og hvordan de bruges. Ubrugte tags kan fjernes",
|
"TagsSettingsSummary": "Se alle tags og hvordan de bruges. Ubrugte tags kan fjernes",
|
||||||
"Time": "Tid",
|
"Time": "Tid",
|
||||||
"MediaManagementSettingsLoadError": "Kan ikke indlæse indstillinger for mediestyring",
|
"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",
|
"BindAddressHelpText": "Gyldig IP4-adresse, 'localhost' eller '*' for alle grænseflader",
|
||||||
"CreateEmptyMovieFoldersHelpText": "Opret manglende filmmapper under diskscanning",
|
"CreateEmptyMovieFoldersHelpText": "Opret manglende filmmapper under diskscanning",
|
||||||
"CouldNotConnectSignalR": "Kunne ikke oprette forbindelse til SignalR, UI opdateres ikke",
|
"CouldNotConnectSignalR": "Kunne ikke oprette forbindelse til SignalR, UI opdateres ikke",
|
||||||
|
@ -774,7 +774,7 @@
|
|||||||
"UpgradesAllowed": "Upgrades erlaubt",
|
"UpgradesAllowed": "Upgrades erlaubt",
|
||||||
"UnmappedFilesOnly": "Nur nicht zugeordnete Dateien",
|
"UnmappedFilesOnly": "Nur nicht zugeordnete Dateien",
|
||||||
"Unlimited": "Unlimitiert",
|
"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",
|
"UnableToLoadManualImportItems": "Einträge für manuelles importieren konnten nicht geladen werden",
|
||||||
"AlternativeTitlesLoadError": "Alternative Titel konnten nicht geladen werden.",
|
"AlternativeTitlesLoadError": "Alternative Titel konnten nicht geladen werden.",
|
||||||
"Trigger": "Auslöser",
|
"Trigger": "Auslöser",
|
||||||
|
@ -893,7 +893,7 @@
|
|||||||
"UnableToLoadRootFolders": "Δεν είναι δυνατή η φόρτωση ριζικών φακέλων",
|
"UnableToLoadRootFolders": "Δεν είναι δυνατή η φόρτωση ριζικών φακέλων",
|
||||||
"TagsLoadError": "Δεν είναι δυνατή η φόρτωση ετικετών",
|
"TagsLoadError": "Δεν είναι δυνατή η φόρτωση ετικετών",
|
||||||
"CalendarLoadError": "Δεν είναι δυνατή η φόρτωση του ημερολογίου",
|
"CalendarLoadError": "Δεν είναι δυνατή η φόρτωση του ημερολογίου",
|
||||||
"UpdateRadarrDirectlyLoadError": "Δεν είναι δυνατή η απευθείας ενημέρωση του {appName},",
|
"UpdateAppDirectlyLoadError": "Δεν είναι δυνατή η απευθείας ενημέρωση του {appName},",
|
||||||
"Ungroup": "Κατάργηση ομάδας",
|
"Ungroup": "Κατάργηση ομάδας",
|
||||||
"Unlimited": "Απεριόριστος",
|
"Unlimited": "Απεριόριστος",
|
||||||
"UnmappedFilesOnly": "Μόνο μη αντιστοιχισμένα αρχεία",
|
"UnmappedFilesOnly": "Μόνο μη αντιστοιχισμένα αρχεία",
|
||||||
|
@ -795,7 +795,11 @@
|
|||||||
"IndexersSettingsSummary": "Indexers and release restrictions",
|
"IndexersSettingsSummary": "Indexers and release restrictions",
|
||||||
"Info": "Info",
|
"Info": "Info",
|
||||||
"InfoUrl": "Info URL",
|
"InfoUrl": "Info URL",
|
||||||
|
"Install": "Install",
|
||||||
"InstallLatest": "Install Latest",
|
"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",
|
"InstanceName": "Instance Name",
|
||||||
"InstanceNameHelpText": "Instance name in tab and for Syslog app name",
|
"InstanceNameHelpText": "Instance name in tab and for Syslog app name",
|
||||||
"InteractiveImport": "Interactive Import",
|
"InteractiveImport": "Interactive Import",
|
||||||
@ -1774,6 +1778,7 @@
|
|||||||
"UnsavedChanges": "Unsaved Changes",
|
"UnsavedChanges": "Unsaved Changes",
|
||||||
"UnselectAll": "Unselect All",
|
"UnselectAll": "Unselect All",
|
||||||
"UpdateAll": "Update 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",
|
"UpdateAutomaticallyHelpText": "Automatically download and install updates. You will still be able to install from System: Updates",
|
||||||
"UpdateAvailableHealthCheckMessage": "New update is available: {version}",
|
"UpdateAvailableHealthCheckMessage": "New update is available: {version}",
|
||||||
"UpdateCheckStartupNotWritableMessage": "Cannot install update because startup folder '{startupFolder}' is not writable by the user '{userName}'.",
|
"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}'.",
|
"UpdateCheckUINotWritableMessage": "Cannot install update because UI folder '{uiFolder}' is not writable by the user '{userName}'.",
|
||||||
"UpdateFiltered": "Update Filtered",
|
"UpdateFiltered": "Update Filtered",
|
||||||
"UpdateMechanismHelpText": "Use {appName}'s built-in updater or a script",
|
"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",
|
"UpdateScriptPathHelpText": "Path to a custom script that takes an extracted update package and handle the remainder of the update process",
|
||||||
"UpdateSelected": "Update Selected",
|
"UpdateSelected": "Update Selected",
|
||||||
"UpdaterLogFiles": "Updater Log Files",
|
"UpdaterLogFiles": "Updater Log Files",
|
||||||
|
@ -926,7 +926,7 @@
|
|||||||
"Trigger": "Desencadenar",
|
"Trigger": "Desencadenar",
|
||||||
"Unlimited": "Ilimitado",
|
"Unlimited": "Ilimitado",
|
||||||
"UnableToLoadManualImportItems": "No se pueden cargar elementos de importación manual",
|
"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",
|
"UnmappedFilesOnly": "Solo archivos sin mapear",
|
||||||
"UpgradeUntilCustomFormatScore": "Actualizar hasta la puntuación de formato personalizado",
|
"UpgradeUntilCustomFormatScore": "Actualizar hasta la puntuación de formato personalizado",
|
||||||
"UpgradeUntil": "Actualizar hasta",
|
"UpgradeUntil": "Actualizar hasta",
|
||||||
|
@ -896,7 +896,7 @@
|
|||||||
"UnableToLoadRootFolders": "Juurikansioiden lataus epäonnistui.",
|
"UnableToLoadRootFolders": "Juurikansioiden lataus epäonnistui.",
|
||||||
"TagsLoadError": "Tunnisteiden lataus ei onnistu",
|
"TagsLoadError": "Tunnisteiden lataus ei onnistu",
|
||||||
"CalendarLoadError": "Kalenterin lataus epäonnistui.",
|
"CalendarLoadError": "Kalenterin lataus epäonnistui.",
|
||||||
"UpdateRadarrDirectlyLoadError": "{appName}ia ei voida päivittää suoraan,",
|
"UpdateAppDirectlyLoadError": "{appName}ia ei voida päivittää suoraan,",
|
||||||
"Ungroup": "Pura ryhmä",
|
"Ungroup": "Pura ryhmä",
|
||||||
"UnmappedFilesOnly": "Vain kohdistamattomat tiedostot",
|
"UnmappedFilesOnly": "Vain kohdistamattomat tiedostot",
|
||||||
"UnmappedFolders": "Kohdistamattomat kansiot",
|
"UnmappedFolders": "Kohdistamattomat kansiot",
|
||||||
|
@ -934,7 +934,7 @@
|
|||||||
"Trakt": "Trakt",
|
"Trakt": "Trakt",
|
||||||
"Trigger": "Déclencheur",
|
"Trigger": "Déclencheur",
|
||||||
"UnableToLoadManualImportItems": "Impossible de charger les éléments d'importation manuelle",
|
"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é",
|
"Unlimited": "Illimité",
|
||||||
"UnmappedFilesOnly": "Fichiers non mappés uniquement",
|
"UnmappedFilesOnly": "Fichiers non mappés uniquement",
|
||||||
"UpgradeUntilCustomFormatScore": "Mise à niveau jusqu'au score de format personnalisé",
|
"UpgradeUntilCustomFormatScore": "Mise à niveau jusqu'au score de format personnalisé",
|
||||||
|
@ -898,7 +898,7 @@
|
|||||||
"UnableToLoadRootFolders": "לא ניתן לטעון תיקיות שורש",
|
"UnableToLoadRootFolders": "לא ניתן לטעון תיקיות שורש",
|
||||||
"CalendarLoadError": "לא ניתן לטעון את היומן",
|
"CalendarLoadError": "לא ניתן לטעון את היומן",
|
||||||
"Unlimited": "ללא הגבלה",
|
"Unlimited": "ללא הגבלה",
|
||||||
"UpdateRadarrDirectlyLoadError": "לא ניתן לעדכן את {appName} ישירות,",
|
"UpdateAppDirectlyLoadError": "לא ניתן לעדכן את {appName} ישירות,",
|
||||||
"Ungroup": "בטל קבוצה",
|
"Ungroup": "בטל קבוצה",
|
||||||
"UnmappedFilesOnly": "קבצים שלא ממופים בלבד",
|
"UnmappedFilesOnly": "קבצים שלא ממופים בלבד",
|
||||||
"UnmappedFolders": "תיקיות לא ממופות",
|
"UnmappedFolders": "תיקיות לא ממופות",
|
||||||
|
@ -241,7 +241,7 @@
|
|||||||
"QualityProfilesLoadError": "गुणवत्ता प्रोफ़ाइल लोड करने में असमर्थ",
|
"QualityProfilesLoadError": "गुणवत्ता प्रोफ़ाइल लोड करने में असमर्थ",
|
||||||
"RemotePathMappingsLoadError": "दूरस्थ पथ मैपिंग लोड करने में असमर्थ",
|
"RemotePathMappingsLoadError": "दूरस्थ पथ मैपिंग लोड करने में असमर्थ",
|
||||||
"CalendarLoadError": "कैलेंडर लोड करने में असमर्थ",
|
"CalendarLoadError": "कैलेंडर लोड करने में असमर्थ",
|
||||||
"UpdateRadarrDirectlyLoadError": "सीधे {appName} अद्यतन करने में असमर्थ,",
|
"UpdateAppDirectlyLoadError": "सीधे {appName} अद्यतन करने में असमर्थ,",
|
||||||
"UnmappedFolders": "बिना मोड़े हुए फोल्डर",
|
"UnmappedFolders": "बिना मोड़े हुए फोल्डर",
|
||||||
"ICalIncludeUnmonitoredMoviesHelpText": "ICal फीड में अनऑमिटर की गई फिल्में शामिल करें",
|
"ICalIncludeUnmonitoredMoviesHelpText": "ICal फीड में अनऑमिटर की गई फिल्में शामिल करें",
|
||||||
"UnselectAll": "सभी का चयन रद्द",
|
"UnselectAll": "सभी का चयन रद्द",
|
||||||
|
@ -846,7 +846,7 @@
|
|||||||
"UpgradesAllowed": "Frissítések Engedélyezve",
|
"UpgradesAllowed": "Frissítések Engedélyezve",
|
||||||
"UnmappedFilesOnly": "Kizárólag fel nem térképezett fájlokat",
|
"UnmappedFilesOnly": "Kizárólag fel nem térképezett fájlokat",
|
||||||
"Unlimited": "korlátlan",
|
"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",
|
"UnableToLoadManualImportItems": "Nem lehetséges betölteni a manuálisan importált elemeket",
|
||||||
"AlternativeTitlesLoadError": "Nem lehetséges betölteni az alternatív címeket.",
|
"AlternativeTitlesLoadError": "Nem lehetséges betölteni az alternatív címeket.",
|
||||||
"Trigger": "Trigger",
|
"Trigger": "Trigger",
|
||||||
|
@ -899,7 +899,7 @@
|
|||||||
"UnableToLoadRootFolders": "Ekki er hægt að hlaða rótarmöppum",
|
"UnableToLoadRootFolders": "Ekki er hægt að hlaða rótarmöppum",
|
||||||
"TagsLoadError": "Ekki er hægt að hlaða merkin",
|
"TagsLoadError": "Ekki er hægt að hlaða merkin",
|
||||||
"CalendarLoadError": "Ekki er hægt að hlaða dagatalið",
|
"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",
|
"Ungroup": "Aftengja hópinn",
|
||||||
"Unlimited": "Ótakmarkað",
|
"Unlimited": "Ótakmarkað",
|
||||||
"UnmappedFilesOnly": "Aðeins ókortlagðar skrár",
|
"UnmappedFilesOnly": "Aðeins ókortlagðar skrár",
|
||||||
|
@ -932,7 +932,7 @@
|
|||||||
"Trigger": "Trigger",
|
"Trigger": "Trigger",
|
||||||
"UnableToLoadManualImportItems": "Impossibile caricare gli elementi di importazione manuale",
|
"UnableToLoadManualImportItems": "Impossibile caricare gli elementi di importazione manuale",
|
||||||
"Unlimited": "Illimitato",
|
"Unlimited": "Illimitato",
|
||||||
"UpdateRadarrDirectlyLoadError": "Impossibile aggiornare {appName} direttamente,",
|
"UpdateAppDirectlyLoadError": "Impossibile aggiornare {appName} direttamente,",
|
||||||
"UnmappedFilesOnly": "Solo file non mappati",
|
"UnmappedFilesOnly": "Solo file non mappati",
|
||||||
"UpgradeUntilCustomFormatScore": "Aggiorna fino al punteggio formato personalizzato",
|
"UpgradeUntilCustomFormatScore": "Aggiorna fino al punteggio formato personalizzato",
|
||||||
"UpgradeUntil": "Upgrade fino alla qualità",
|
"UpgradeUntil": "Upgrade fino alla qualità",
|
||||||
|
@ -896,7 +896,7 @@
|
|||||||
"UnableToLoadRootFolders": "ルートフォルダを読み込めません",
|
"UnableToLoadRootFolders": "ルートフォルダを読み込めません",
|
||||||
"TagsLoadError": "タグを読み込めません",
|
"TagsLoadError": "タグを読み込めません",
|
||||||
"CalendarLoadError": "カレンダーを読み込めません",
|
"CalendarLoadError": "カレンダーを読み込めません",
|
||||||
"UpdateRadarrDirectlyLoadError": "{appName}を直接更新できません。",
|
"UpdateAppDirectlyLoadError": "{appName}を直接更新できません。",
|
||||||
"Ungroup": "グループ化を解除",
|
"Ungroup": "グループ化を解除",
|
||||||
"UnmappedFilesOnly": "マップされていないファイルのみ",
|
"UnmappedFilesOnly": "マップされていないファイルのみ",
|
||||||
"UnmappedFolders": "マップされていないフォルダ",
|
"UnmappedFolders": "マップされていないフォルダ",
|
||||||
|
@ -899,7 +899,7 @@
|
|||||||
"UnableToLoadRestrictions": "제한을 불러올 수 없습니다.",
|
"UnableToLoadRestrictions": "제한을 불러올 수 없습니다.",
|
||||||
"UnableToLoadRootFolders": "루트 폴더를 불러올 수 없습니다.",
|
"UnableToLoadRootFolders": "루트 폴더를 불러올 수 없습니다.",
|
||||||
"CalendarLoadError": "달력을 불러올 수 없습니다.",
|
"CalendarLoadError": "달력을 불러올 수 없습니다.",
|
||||||
"UpdateRadarrDirectlyLoadError": "{appName}를 직접 업데이트 할 수 없습니다.",
|
"UpdateAppDirectlyLoadError": "{appName}를 직접 업데이트 할 수 없습니다.",
|
||||||
"Ungroup": "그룹 해제",
|
"Ungroup": "그룹 해제",
|
||||||
"UnmappedFilesOnly": "매핑되지 않은 파일 만",
|
"UnmappedFilesOnly": "매핑되지 않은 파일 만",
|
||||||
"UnmappedFolders": "매핑되지 않은 폴더",
|
"UnmappedFolders": "매핑되지 않은 폴더",
|
||||||
|
@ -934,7 +934,7 @@
|
|||||||
"Trakt": "Trakt",
|
"Trakt": "Trakt",
|
||||||
"Trigger": "In gang zetten",
|
"Trigger": "In gang zetten",
|
||||||
"UnableToLoadManualImportItems": "Kan items voor handmatig importeren niet laden",
|
"UnableToLoadManualImportItems": "Kan items voor handmatig importeren niet laden",
|
||||||
"UpdateRadarrDirectlyLoadError": "Kan {appName} niet rechtstreeks updaten,",
|
"UpdateAppDirectlyLoadError": "Kan {appName} niet rechtstreeks updaten,",
|
||||||
"Unlimited": "Onbeperkt",
|
"Unlimited": "Onbeperkt",
|
||||||
"UnmappedFilesOnly": "Alleen niet-toegewezen bestanden",
|
"UnmappedFilesOnly": "Alleen niet-toegewezen bestanden",
|
||||||
"UpgradeUntilCustomFormatScore": "Upgraden tot Score aangepast formaat",
|
"UpgradeUntilCustomFormatScore": "Upgraden tot Score aangepast formaat",
|
||||||
|
@ -901,7 +901,7 @@
|
|||||||
"UnableToLoadRootFolders": "Nie można załadować folderów głównych",
|
"UnableToLoadRootFolders": "Nie można załadować folderów głównych",
|
||||||
"TagsLoadError": "Nie można załadować tagów",
|
"TagsLoadError": "Nie można załadować tagów",
|
||||||
"CalendarLoadError": "Nie można załadować kalendarza",
|
"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",
|
"Ungroup": "Rozgrupuj",
|
||||||
"Unlimited": "Nieograniczony",
|
"Unlimited": "Nieograniczony",
|
||||||
"Unmonitored": "Niemonitorowane",
|
"Unmonitored": "Niemonitorowane",
|
||||||
|
@ -934,7 +934,7 @@
|
|||||||
"Trakt": "Trakt",
|
"Trakt": "Trakt",
|
||||||
"Trigger": "Acionador",
|
"Trigger": "Acionador",
|
||||||
"UnableToLoadManualImportItems": "Não foi possível carregar os itens de importação manual",
|
"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",
|
"Unlimited": "Ilimitado",
|
||||||
"UnmappedFilesOnly": "Somente ficheiros não mapeados",
|
"UnmappedFilesOnly": "Somente ficheiros não mapeados",
|
||||||
"UpgradeUntilCustomFormatScore": "Atualizar até a pontuação do formato personalizado",
|
"UpgradeUntilCustomFormatScore": "Atualizar até a pontuação do formato personalizado",
|
||||||
|
@ -906,7 +906,7 @@
|
|||||||
"Unlimited": "Ilimitado",
|
"Unlimited": "Ilimitado",
|
||||||
"Ungroup": "Desagrupar",
|
"Ungroup": "Desagrupar",
|
||||||
"Unavailable": "Indisponível",
|
"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",
|
"UiSettingsLoadError": "Não foi possível carregar as configurações da interface",
|
||||||
"CalendarLoadError": "Não foi possível carregar o calendário",
|
"CalendarLoadError": "Não foi possível carregar o calendário",
|
||||||
"TagsLoadError": "Não foi possível carregar as tags",
|
"TagsLoadError": "Não foi possível carregar as tags",
|
||||||
|
@ -912,7 +912,7 @@
|
|||||||
"UnableToLoadRootFolders": "Imposibil de încărcat folderele rădăcină",
|
"UnableToLoadRootFolders": "Imposibil de încărcat folderele rădăcină",
|
||||||
"TagsLoadError": "Nu se pot încărca etichete",
|
"TagsLoadError": "Nu se pot încărca etichete",
|
||||||
"CalendarLoadError": "Calendarul nu poate fi încărcat",
|
"CalendarLoadError": "Calendarul nu poate fi încărcat",
|
||||||
"UpdateRadarrDirectlyLoadError": "Imposibil de actualizat direct {appName},",
|
"UpdateAppDirectlyLoadError": "Imposibil de actualizat direct {appName},",
|
||||||
"Ungroup": "Dezgrupează",
|
"Ungroup": "Dezgrupează",
|
||||||
"Unlimited": "Nelimitat",
|
"Unlimited": "Nelimitat",
|
||||||
"UnmappedFilesOnly": "Numai fișiere nemapate",
|
"UnmappedFilesOnly": "Numai fișiere nemapate",
|
||||||
|
@ -720,7 +720,7 @@
|
|||||||
"Unlimited": "Неограниченно",
|
"Unlimited": "Неограниченно",
|
||||||
"Ungroup": "Разгруппировать",
|
"Ungroup": "Разгруппировать",
|
||||||
"Unavailable": "Недоступно",
|
"Unavailable": "Недоступно",
|
||||||
"UpdateRadarrDirectlyLoadError": "Невозможно обновить {appName} напрямую,",
|
"UpdateAppDirectlyLoadError": "Невозможно обновить {appName} напрямую,",
|
||||||
"UiSettingsLoadError": "Не удалось загрузить настройки пользовательского интерфейса",
|
"UiSettingsLoadError": "Не удалось загрузить настройки пользовательского интерфейса",
|
||||||
"CalendarLoadError": "Не удалось загрузить календарь",
|
"CalendarLoadError": "Не удалось загрузить календарь",
|
||||||
"TagsLoadError": "Невозможно загрузить теги",
|
"TagsLoadError": "Невозможно загрузить теги",
|
||||||
|
@ -935,7 +935,7 @@
|
|||||||
"UnableToLoadRestrictions": "Det gick inte att ladda begränsningar",
|
"UnableToLoadRestrictions": "Det gick inte att ladda begränsningar",
|
||||||
"UnableToLoadRootFolders": "Det gick inte att ladda rotmappar",
|
"UnableToLoadRootFolders": "Det gick inte att ladda rotmappar",
|
||||||
"CalendarLoadError": "Det gick inte att ladda kalendern",
|
"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",
|
"UpgradeUntilCustomFormatScore": "Uppgradera tills anpassat formatpoäng",
|
||||||
"UpgradeUntil": "Uppgradera tills kvalitet",
|
"UpgradeUntil": "Uppgradera tills kvalitet",
|
||||||
"UpgradeUntilThisQualityIsMetOrExceeded": "Uppgradera tills den här kvaliteten uppfylls eller överskrids",
|
"UpgradeUntilThisQualityIsMetOrExceeded": "Uppgradera tills den här kvaliteten uppfylls eller överskrids",
|
||||||
|
@ -907,7 +907,7 @@
|
|||||||
"UnableToLoadRestrictions": "ไม่สามารถโหลดข้อ จำกัด",
|
"UnableToLoadRestrictions": "ไม่สามารถโหลดข้อ จำกัด",
|
||||||
"UnableToLoadRootFolders": "ไม่สามารถโหลดโฟลเดอร์รูท",
|
"UnableToLoadRootFolders": "ไม่สามารถโหลดโฟลเดอร์รูท",
|
||||||
"CalendarLoadError": "ไม่สามารถโหลดปฏิทิน",
|
"CalendarLoadError": "ไม่สามารถโหลดปฏิทิน",
|
||||||
"UpdateRadarrDirectlyLoadError": "ไม่สามารถอัปเดต {appName} ได้โดยตรง",
|
"UpdateAppDirectlyLoadError": "ไม่สามารถอัปเดต {appName} ได้โดยตรง",
|
||||||
"Ungroup": "ยกเลิกการจัดกลุ่ม",
|
"Ungroup": "ยกเลิกการจัดกลุ่ม",
|
||||||
"Unlimited": "ไม่ จำกัด",
|
"Unlimited": "ไม่ จำกัด",
|
||||||
"UnmappedFilesOnly": "ไฟล์ที่ไม่ได้แมปเท่านั้น",
|
"UnmappedFilesOnly": "ไฟล์ที่ไม่ได้แมปเท่านั้น",
|
||||||
|
@ -338,7 +338,7 @@
|
|||||||
"UnableToLoadRestrictions": "Kısıtlamalar yüklenemiyor",
|
"UnableToLoadRestrictions": "Kısıtlamalar yüklenemiyor",
|
||||||
"UnableToLoadRootFolders": "Kök klasörler yüklenemiyor",
|
"UnableToLoadRootFolders": "Kök klasörler yüklenemiyor",
|
||||||
"TagsLoadError": "Etiketler yüklenemiyor",
|
"TagsLoadError": "Etiketler yüklenemiyor",
|
||||||
"UpdateRadarrDirectlyLoadError": "{appName} doğrudan güncellenemiyor,",
|
"UpdateAppDirectlyLoadError": "{appName} doğrudan güncellenemiyor,",
|
||||||
"Ungroup": "Grubu çöz",
|
"Ungroup": "Grubu çöz",
|
||||||
"Unlimited": "Sınırsız",
|
"Unlimited": "Sınırsız",
|
||||||
"UnmappedFilesOnly": "Yalnızca Eşlenmemiş Dosyalar",
|
"UnmappedFilesOnly": "Yalnızca Eşlenmemiş Dosyalar",
|
||||||
|
@ -892,7 +892,7 @@
|
|||||||
"QualityDefinitionsLoadError": "Не вдалося завантажити визначення якості",
|
"QualityDefinitionsLoadError": "Не вдалося завантажити визначення якості",
|
||||||
"RemotePathMappingsLoadError": "Неможливо завантажити віддалені відображення шляхів",
|
"RemotePathMappingsLoadError": "Неможливо завантажити віддалені відображення шляхів",
|
||||||
"UnableToLoadRootFolders": "Не вдалося завантажити кореневі папки",
|
"UnableToLoadRootFolders": "Не вдалося завантажити кореневі папки",
|
||||||
"UpdateRadarrDirectlyLoadError": "Неможливо оновити {appName} безпосередньо,",
|
"UpdateAppDirectlyLoadError": "Неможливо оновити {appName} безпосередньо,",
|
||||||
"Unavailable": "Недоступний",
|
"Unavailable": "Недоступний",
|
||||||
"Unlimited": "Необмежений",
|
"Unlimited": "Необмежений",
|
||||||
"UnmappedFilesOnly": "Лише незіставлені файли",
|
"UnmappedFilesOnly": "Лише незіставлені файли",
|
||||||
|
@ -912,7 +912,7 @@
|
|||||||
"QualityProfilesLoadError": "Không thể tải Hồ sơ chất lượng",
|
"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",
|
"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",
|
"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",
|
"Ungroup": "Bỏ nhóm",
|
||||||
"Unlimited": "Vô hạn",
|
"Unlimited": "Vô hạn",
|
||||||
"UnmappedFilesOnly": "Chỉ các tệp chưa được ánh xạ",
|
"UnmappedFilesOnly": "Chỉ các tệp chưa được ánh xạ",
|
||||||
|
@ -127,7 +127,7 @@
|
|||||||
"Unmonitored": "未追踪项",
|
"Unmonitored": "未追踪项",
|
||||||
"Unlimited": "无限制",
|
"Unlimited": "无限制",
|
||||||
"Unavailable": "不可用",
|
"Unavailable": "不可用",
|
||||||
"UpdateRadarrDirectlyLoadError": "无法直接更新{appName}",
|
"UpdateAppDirectlyLoadError": "无法直接更新{appName}",
|
||||||
"UiSettingsLoadError": "无法加载UI设置",
|
"UiSettingsLoadError": "无法加载UI设置",
|
||||||
"CalendarLoadError": "无法加载日历",
|
"CalendarLoadError": "无法加载日历",
|
||||||
"TagsLoadError": "无法加载标签",
|
"TagsLoadError": "无法加载标签",
|
||||||
|
@ -7,5 +7,7 @@ public class ApplicationCheckUpdateCommand : Command
|
|||||||
public override bool SendUpdatesToClient => true;
|
public override bool SendUpdatesToClient => true;
|
||||||
|
|
||||||
public override string CompletionMessage => null;
|
public override string CompletionMessage => null;
|
||||||
|
|
||||||
|
public bool InstallMajorUpdate { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ namespace NzbDrone.Core.Update.Commands
|
|||||||
{
|
{
|
||||||
public class ApplicationUpdateCommand : Command
|
public class ApplicationUpdateCommand : Command
|
||||||
{
|
{
|
||||||
|
public bool InstallMajorUpdate { get; set; }
|
||||||
public override bool SendUpdatesToClient => true;
|
public override bool SendUpdatesToClient => true;
|
||||||
public override bool IsExclusive => 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");
|
_logger.ProgressDebug("Checking for updates");
|
||||||
|
|
||||||
@ -243,7 +243,13 @@ private UpdatePackage GetUpdatePackage(CommandTrigger updateTrigger)
|
|||||||
return null;
|
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.");
|
_logger.ProgressDebug("Auto-update not enabled, not installing available update.");
|
||||||
return null;
|
return null;
|
||||||
@ -272,7 +278,7 @@ private UpdatePackage GetUpdatePackage(CommandTrigger updateTrigger)
|
|||||||
|
|
||||||
public void Execute(ApplicationCheckUpdateCommand message)
|
public void Execute(ApplicationCheckUpdateCommand message)
|
||||||
{
|
{
|
||||||
if (GetUpdatePackage(message.Trigger) != null)
|
if (GetUpdatePackage(message.Trigger, true) != null)
|
||||||
{
|
{
|
||||||
_commandQueueManager.Push(new ApplicationUpdateCommand(), trigger: message.Trigger);
|
_commandQueueManager.Push(new ApplicationUpdateCommand(), trigger: message.Trigger);
|
||||||
}
|
}
|
||||||
@ -280,7 +286,7 @@ public void Execute(ApplicationCheckUpdateCommand message)
|
|||||||
|
|
||||||
public void Execute(ApplicationUpdateCommand message)
|
public void Execute(ApplicationUpdateCommand message)
|
||||||
{
|
{
|
||||||
var latestAvailable = GetUpdatePackage(message.Trigger);
|
var latestAvailable = GetUpdatePackage(message.Trigger, message.InstallMajorUpdate);
|
||||||
|
|
||||||
if (latestAvailable != null)
|
if (latestAvailable != null)
|
||||||
{
|
{
|
||||||
|
@ -42,6 +42,7 @@ public UpdatePackage GetLatestUpdate(string branch, Version currentVersion)
|
|||||||
.AddQueryParam("runtime", "netcore")
|
.AddQueryParam("runtime", "netcore")
|
||||||
.AddQueryParam("runtimeVer", _platformInfo.Version)
|
.AddQueryParam("runtimeVer", _platformInfo.Version)
|
||||||
.AddQueryParam("dbType", _mainDatabase.DatabaseType)
|
.AddQueryParam("dbType", _mainDatabase.DatabaseType)
|
||||||
|
.AddQueryParam("includeMajorVersion", true)
|
||||||
.SetSegment("branch", branch);
|
.SetSegment("branch", branch);
|
||||||
|
|
||||||
if (_analyticsService.IsEnabled)
|
if (_analyticsService.IsEnabled)
|
||||||
|
Loading…
Reference in New Issue
Block a user