mirror of
https://github.com/Sonarr/Sonarr
synced 2025-12-23 16:55:47 +01:00
210 lines
5.6 KiB
TypeScript
210 lines
5.6 KiB
TypeScript
import React, { useCallback, useEffect, useState } from 'react';
|
|
import { useDispatch, useSelector } from 'react-redux';
|
|
import { Error } from 'App/State/AppSectionState';
|
|
import AppState from 'App/State/AppState';
|
|
import TextInput from 'Components/Form/TextInput';
|
|
import Icon, { IconName, IconProps } from 'Components/Icon';
|
|
import Button from 'Components/Link/Button';
|
|
import SpinnerButton from 'Components/Link/SpinnerButton';
|
|
import ModalBody from 'Components/Modal/ModalBody';
|
|
import ModalContent from 'Components/Modal/ModalContent';
|
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
|
import usePrevious from 'Helpers/Hooks/usePrevious';
|
|
import { icons, kinds } from 'Helpers/Props';
|
|
import { useRestart } from 'System/useSystem';
|
|
import { FileInputChanged } from 'typings/inputs';
|
|
import translate from 'Utilities/String/translate';
|
|
import { useRestoreBackup, useRestoreBackupUpload } from './useBackups';
|
|
import styles from './RestoreBackupModalContent.css';
|
|
|
|
function getErrorMessage(error: Error) {
|
|
if (
|
|
!error ||
|
|
!error.responseJSON ||
|
|
!('message' in error.responseJSON) ||
|
|
!error.responseJSON.message
|
|
) {
|
|
return translate('ErrorRestoringBackup');
|
|
}
|
|
|
|
return error.responseJSON.message;
|
|
}
|
|
|
|
function getStepIconProps(
|
|
isExecuting: boolean,
|
|
hasExecuted: boolean,
|
|
error?: Error
|
|
): {
|
|
name: IconName;
|
|
kind?: IconProps['kind'];
|
|
title?: string;
|
|
isSpinning?: boolean;
|
|
} {
|
|
if (isExecuting) {
|
|
return {
|
|
name: icons.SPINNER,
|
|
isSpinning: true,
|
|
};
|
|
}
|
|
|
|
if (hasExecuted) {
|
|
return {
|
|
name: icons.CHECK,
|
|
kind: 'success',
|
|
};
|
|
}
|
|
|
|
if (error) {
|
|
return {
|
|
name: icons.FATAL,
|
|
kind: 'danger',
|
|
title: getErrorMessage(error),
|
|
};
|
|
}
|
|
|
|
return {
|
|
name: icons.PENDING,
|
|
};
|
|
}
|
|
|
|
export interface RestoreBackupModalContentProps {
|
|
id?: number;
|
|
name?: string;
|
|
onModalClose: () => void;
|
|
}
|
|
|
|
function RestoreBackupModalContent({
|
|
id,
|
|
name,
|
|
onModalClose,
|
|
}: RestoreBackupModalContentProps) {
|
|
const { isRestarting } = useSelector((state: AppState) => state.app);
|
|
const dispatch = useDispatch();
|
|
|
|
const { restoreBackupById, isRestoringBackup, restoreBackupError } =
|
|
useRestoreBackup(id || 0);
|
|
const { uploadBackup, isUploadingBackup, uploadBackupError } =
|
|
useRestoreBackupUpload();
|
|
const { mutate: restart } = useRestart();
|
|
|
|
const [path, setPath] = useState('');
|
|
const [file, setFile] = useState<File | null>(null);
|
|
const [isRestored, setIsRestored] = useState(false);
|
|
const [isRestarted, setIsRestarted] = useState(false);
|
|
const [isReloading, setIsReloading] = useState(false);
|
|
|
|
const isRestoring = isRestoringBackup || isUploadingBackup;
|
|
const restoreError = restoreBackupError || uploadBackupError;
|
|
const wasRestoring = usePrevious(isRestoring);
|
|
const wasRestarting = usePrevious(isRestarting);
|
|
|
|
const isRestoreDisabled =
|
|
(!id && !path) || isRestoring || isRestarting || isReloading;
|
|
|
|
const handlePathChange = useCallback(({ value, files }: FileInputChanged) => {
|
|
if (!files?.length) {
|
|
return;
|
|
}
|
|
|
|
setPath(value);
|
|
setFile(files[0]);
|
|
}, []);
|
|
|
|
const handleRestorePress = useCallback(() => {
|
|
if (id) {
|
|
restoreBackupById();
|
|
} else if (file) {
|
|
const formData = new FormData();
|
|
formData.append('restore', file);
|
|
uploadBackup(formData);
|
|
}
|
|
}, [id, file, restoreBackupById, uploadBackup]);
|
|
|
|
useEffect(() => {
|
|
if (wasRestoring && !isRestoring && !restoreError) {
|
|
setIsRestored(true);
|
|
restart();
|
|
}
|
|
}, [isRestoring, wasRestoring, restoreError, restart]);
|
|
|
|
useEffect(() => {
|
|
if (wasRestarting && !isRestarting) {
|
|
setIsRestarted(true);
|
|
setIsReloading(true);
|
|
window.location.reload();
|
|
}
|
|
}, [isRestarting, wasRestarting, dispatch]);
|
|
|
|
return (
|
|
<ModalContent onModalClose={onModalClose}>
|
|
<ModalHeader>Restore Backup</ModalHeader>
|
|
|
|
<ModalBody>
|
|
{id && name ? (
|
|
translate('WouldYouLikeToRestoreBackup', {
|
|
name,
|
|
})
|
|
) : (
|
|
<TextInput
|
|
type="file"
|
|
name="path"
|
|
value={path}
|
|
onChange={handlePathChange}
|
|
/>
|
|
)}
|
|
|
|
<div className={styles.steps}>
|
|
<div className={styles.step}>
|
|
<div className={styles.stepState}>
|
|
<Icon
|
|
size={20}
|
|
{...getStepIconProps(isRestoring, isRestored, undefined)}
|
|
/>
|
|
</div>
|
|
|
|
<div>{translate('Restore')}</div>
|
|
</div>
|
|
|
|
<div className={styles.step}>
|
|
<div className={styles.stepState}>
|
|
<Icon
|
|
size={20}
|
|
{...getStepIconProps(isRestarting, isRestarted)}
|
|
/>
|
|
</div>
|
|
|
|
<div>{translate('Restart')}</div>
|
|
</div>
|
|
|
|
<div className={styles.step}>
|
|
<div className={styles.stepState}>
|
|
<Icon size={20} {...getStepIconProps(isReloading, false)} />
|
|
</div>
|
|
|
|
<div>{translate('Reload')}</div>
|
|
</div>
|
|
</div>
|
|
</ModalBody>
|
|
|
|
<ModalFooter>
|
|
<div className={styles.additionalInfo}>
|
|
{translate('RestartReloadNote')}
|
|
</div>
|
|
|
|
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
|
|
|
|
<SpinnerButton
|
|
kind={kinds.WARNING}
|
|
isDisabled={isRestoreDisabled}
|
|
isSpinning={isRestoring}
|
|
onPress={handleRestorePress}
|
|
>
|
|
{translate('Restore')}
|
|
</SpinnerButton>
|
|
</ModalFooter>
|
|
</ModalContent>
|
|
);
|
|
}
|
|
|
|
export default RestoreBackupModalContent;
|