parent
c023fc7008
commit
0e95ba2021
@ -1,46 +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;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className={styles.title}>{title}</div>
|
|
||||||
<ul>
|
|
||||||
{
|
|
||||||
changes.map((change, index) => {
|
|
||||||
return (
|
|
||||||
<li key={index}>
|
|
||||||
<InlineMarkdown data={change} />
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateChanges.propTypes = {
|
|
||||||
title: PropTypes.string.isRequired,
|
|
||||||
changes: PropTypes.arrayOf(PropTypes.string)
|
|
||||||
};
|
|
||||||
|
|
||||||
export default UpdateChanges;
|
|
@ -0,0 +1,33 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={styles.title}>{title}</div>
|
||||||
|
<ul>
|
||||||
|
{changes.map((change, index) => {
|
||||||
|
return (
|
||||||
|
<li key={index}>
|
||||||
|
<InlineMarkdown data={change} />
|
||||||
|
</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('UpdateSonarrDirectlyLoadError');
|
|
||||||
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 === '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>
|
|
||||||
|
|
||||||
{
|
|
||||||
!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;
|
|
@ -0,0 +1,305 @@
|
|||||||
|
import React, {
|
||||||
|
Fragment,
|
||||||
|
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('UpdateSonarrDirectlyLoadError');
|
||||||
|
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>
|
||||||
|
) : (
|
||||||
|
<Fragment>
|
||||||
|
<Icon name={icons.WARNING} kind={kinds.WARNING} size={30} />
|
||||||
|
|
||||||
|
<div className={styles.message}>
|
||||||
|
{externalUpdaterPrefix}{' '}
|
||||||
|
<InlineMarkdown
|
||||||
|
data={
|
||||||
|
packageUpdateMechanismMessage ||
|
||||||
|
externalUpdaterMessages[updateMechanism] ||
|
||||||
|
externalUpdaterMessages.external
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{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) => {
|
||||||
|
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 === '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>
|
||||||
|
|
||||||
|
{hasChanges ? (
|
||||||
|
<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 ? <div>{translate('FailedToFetchUpdates')}</div> : null}
|
||||||
|
|
||||||
|
{generalSettingsError ? (
|
||||||
|
<div>{translate('FailedToUpdateSettings')}</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
isOpen={isMajorUpdateModalOpen}
|
||||||
|
kind={kinds.WARNING}
|
||||||
|
title={translate('InstallMajorVersionUpdate')}
|
||||||
|
message={
|
||||||
|
<div>
|
||||||
|
<div>{translate('InstallMajorVersionUpdateMessage')}</div>
|
||||||
|
<div>
|
||||||
|
<InlineMarkdown
|
||||||
|
data={translate('InstallMajorVersionUpdateMessageLink', {
|
||||||
|
domain: 'sonarr.tv',
|
||||||
|
url: 'https://sonarr.tv/#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);
|
|
@ -0,0 +1,45 @@
|
|||||||
|
export type UpdateMechanism =
|
||||||
|
| 'builtIn'
|
||||||
|
| 'script'
|
||||||
|
| 'external'
|
||||||
|
| 'apt'
|
||||||
|
| 'docker';
|
||||||
|
|
||||||
|
export default interface General {
|
||||||
|
bindAddress: string;
|
||||||
|
port: number;
|
||||||
|
sslPort: number;
|
||||||
|
enableSsl: boolean;
|
||||||
|
launchBrowser: boolean;
|
||||||
|
authenticationMethod: string;
|
||||||
|
authenticationRequired: string;
|
||||||
|
analyticsEnabled: boolean;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
passwordConfirmation: string;
|
||||||
|
logLevel: string;
|
||||||
|
consoleLogLevel: string;
|
||||||
|
branch: string;
|
||||||
|
apiKey: string;
|
||||||
|
sslCertPath: string;
|
||||||
|
sslCertPassword: string;
|
||||||
|
urlBase: string;
|
||||||
|
instanceName: string;
|
||||||
|
applicationUrl: string;
|
||||||
|
updateAutomatically: boolean;
|
||||||
|
updateMechanism: UpdateMechanism;
|
||||||
|
updateScriptPath: string;
|
||||||
|
proxyEnabled: boolean;
|
||||||
|
proxyType: string;
|
||||||
|
proxyHostname: string;
|
||||||
|
proxyPort: number;
|
||||||
|
proxyUsername: string;
|
||||||
|
proxyPassword: string;
|
||||||
|
proxyBypassFilter: string;
|
||||||
|
proxyBypassLocalAddresses: boolean;
|
||||||
|
certificateValidation: string;
|
||||||
|
backupFolder: string;
|
||||||
|
backupInterval: number;
|
||||||
|
backupRetention: number;
|
||||||
|
id: number;
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
export interface UiSettings {
|
export default interface UiSettings {
|
||||||
theme: 'auto' | 'dark' | 'light';
|
theme: 'auto' | 'dark' | 'light';
|
||||||
showRelativeDates: boolean;
|
showRelativeDates: boolean;
|
||||||
shortDateFormat: string;
|
shortDateFormat: string;
|
@ -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;
|
||||||
|
hash: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Update;
|
Loading…
Reference in new issue