misc_pterodactyl-panel/resources/scripts/components/server/backups/BackupContextMenu.tsx

207 lines
8.8 KiB
TypeScript
Raw Normal View History

import React, { useState } from 'react';
import { faBoxOpen, faCloudDownloadAlt, faEllipsisH, faLock, faTrashAlt, faUnlock } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import DropdownMenu, { DropdownButtonRow } from '@/components/elements/DropdownMenu';
import getBackupDownloadUrl from '@/api/server/backups/getBackupDownloadUrl';
import useFlash from '@/plugins/useFlash';
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
import deleteBackup from '@/api/server/backups/deleteBackup';
import ConfirmationModal from '@/components/elements/ConfirmationModal';
2020-04-10 17:11:15 +00:00
import Can from '@/components/elements/Can';
2020-07-04 23:26:07 +00:00
import tw from 'twin.macro';
import getServerBackups from '@/api/swr/getServerBackups';
import { ServerBackup } from '@/api/server/types';
2020-08-26 05:09:54 +00:00
import { ServerContext } from '@/state/server';
import Input from '@/components/elements/Input';
2021-01-31 02:01:32 +00:00
import { restoreServerBackup } from '@/api/server/backups';
import http, { httpErrorToHuman } from '@/api/http';
interface Props {
backup: ServerBackup;
}
export default ({ backup }: Props) => {
2020-08-26 05:09:54 +00:00
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
2021-01-31 02:01:32 +00:00
const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState);
const [ modal, setModal ] = useState('');
const [ loading, setLoading ] = useState(false);
2021-03-12 21:47:49 +00:00
const [ truncate, setTruncate ] = useState(false);
const { clearFlashes, clearAndAddHttpError } = useFlash();
const { mutate } = getServerBackups();
const doDownload = () => {
setLoading(true);
clearFlashes('backups');
getBackupDownloadUrl(uuid, backup.uuid)
.then(url => {
// @ts-ignore
window.location = url;
})
.catch(error => {
console.error(error);
clearAndAddHttpError({ key: 'backups', error });
})
.then(() => setLoading(false));
};
const doDeletion = () => {
setLoading(true);
clearFlashes('backups');
deleteBackup(uuid, backup.uuid)
2021-01-31 02:01:32 +00:00
.then(() => mutate(data => ({
...data!,
items: data!.items.filter(b => b.uuid !== backup.uuid),
2021-08-05 03:40:38 +00:00
backupCount: data!.backupCount - 1,
2021-01-31 02:01:32 +00:00
}), false))
.catch(error => {
console.error(error);
clearAndAddHttpError({ key: 'backups', error });
setLoading(false);
2021-01-31 02:01:32 +00:00
setModal('');
});
};
2021-03-12 22:40:45 +00:00
const doRestorationAction = () => {
2021-01-31 02:01:32 +00:00
setLoading(true);
clearFlashes('backups');
2021-03-12 21:47:49 +00:00
restoreServerBackup(uuid, backup.uuid, truncate)
2021-01-31 02:01:32 +00:00
.then(() => setServerFromState(s => ({
...s,
status: 'restoring_backup',
})))
.catch(error => {
console.error(error);
clearAndAddHttpError({ key: 'backups', error });
})
.then(() => setLoading(false))
.then(() => setModal(''));
2021-01-31 02:01:32 +00:00
};
const onLockToggle = () => {
if (backup.isLocked && modal !== 'unlock') {
return setModal('unlock');
}
http.post(`/api/client/servers/${uuid}/backups/${backup.uuid}/lock`)
.then(() => mutate(data => ({
...data!,
items: data!.items.map(b => b.uuid !== backup.uuid ? b : {
...b,
isLocked: !b.isLocked,
}),
}), false))
.catch(error => alert(httpErrorToHuman(error)))
.then(() => setModal(''));
};
return (
<>
<ConfirmationModal
visible={modal === 'unlock'}
title={'Unlock this backup?'}
onConfirmed={onLockToggle}
onModalDismissed={() => setModal('')}
buttonText={'Yes, unlock'}
>
Are you sure you want to unlock this backup? It will no longer be protected from automated or
accidental deletions.
</ConfirmationModal>
<ConfirmationModal
2021-01-31 02:01:32 +00:00
visible={modal === 'restore'}
title={'Restore this backup?'}
buttonText={'Restore backup'}
2021-03-12 22:40:45 +00:00
onConfirmed={() => doRestorationAction()}
2021-01-31 02:01:32 +00:00
onModalDismissed={() => setModal('')}
>
<p css={tw`text-neutral-300`}>
This server will be stopped in order to restore the backup. Once the backup has started you will
not be able to control the server power state, access the file manager, or create additional backups
until it has completed.
</p>
<p css={tw`mt-4 text-neutral-300`}>
Are you sure you want to continue?
</p>
<p css={tw`p-3 mt-4 -mb-2 rounded bg-neutral-900`}>
2021-01-31 02:01:32 +00:00
<label
htmlFor={'restore_truncate'}
css={tw`flex items-center text-base cursor-pointer text-neutral-200`}
2021-01-31 02:01:32 +00:00
>
<Input
type={'checkbox'}
css={tw`text-red-500! w-5! h-5! mr-2`}
id={'restore_truncate'}
value={'true'}
2021-03-12 21:47:49 +00:00
checked={truncate}
2021-03-12 22:40:45 +00:00
onChange={() => setTruncate(s => !s)}
/>
Remove all files and folders before restoring this backup.
</label>
</p>
</ConfirmationModal>
<ConfirmationModal
2021-01-31 02:01:32 +00:00
visible={modal === 'delete'}
title={'Delete this backup?'}
buttonText={'Yes, delete backup'}
onConfirmed={() => doDeletion()}
2021-01-31 02:01:32 +00:00
onModalDismissed={() => setModal('')}
>
Are you sure you wish to delete this backup? This is a permanent operation and the backup cannot
be recovered once deleted.
</ConfirmationModal>
2020-07-05 01:30:50 +00:00
<SpinnerOverlay visible={loading} fixed/>
{backup.isSuccessful ?
<DropdownMenu
renderToggle={onClick => (
<button
onClick={onClick}
css={tw`p-2 transition-colors duration-150 text-neutral-200 hover:text-neutral-100`}
>
<FontAwesomeIcon icon={faEllipsisH}/>
</button>
)}
>
<div css={tw`text-sm`}>
<Can action={'backup.download'}>
2021-01-31 02:01:32 +00:00
<DropdownButtonRow onClick={doDownload}>
<FontAwesomeIcon fixedWidth icon={faCloudDownloadAlt} css={tw`text-xs`}/>
<span css={tw`ml-2`}>Download</span>
</DropdownButtonRow>
</Can>
<Can action={'backup.restore'}>
2021-01-31 02:01:32 +00:00
<DropdownButtonRow onClick={() => setModal('restore')}>
<FontAwesomeIcon fixedWidth icon={faBoxOpen} css={tw`text-xs`}/>
<span css={tw`ml-2`}>Restore</span>
</DropdownButtonRow>
</Can>
<Can action={'backup.delete'}>
<>
<DropdownButtonRow onClick={onLockToggle}>
<FontAwesomeIcon
fixedWidth
icon={backup.isLocked ? faUnlock : faLock}
css={tw`mr-2 text-xs`}
/>
{backup.isLocked ? 'Unlock' : 'Lock'}
</DropdownButtonRow>
{!backup.isLocked &&
<DropdownButtonRow danger onClick={() => setModal('delete')}>
<FontAwesomeIcon fixedWidth icon={faTrashAlt} css={tw`text-xs`}/>
<span css={tw`ml-2`}>Delete</span>
</DropdownButtonRow>
}
</>
</Can>
</div>
</DropdownMenu>
:
<button
2021-01-31 02:01:32 +00:00
onClick={() => setModal('delete')}
css={tw`p-2 transition-colors duration-150 text-neutral-200 hover:text-neutral-100`}
>
<FontAwesomeIcon icon={faTrashAlt}/>
</button>
}
</>
);
};