Apply new eslint rules; default to prettier for styling
This commit is contained in:
parent
f22cce8881
commit
dc84af9937
218 changed files with 3876 additions and 3564 deletions
|
@ -16,7 +16,7 @@ const BackupContainer = () => {
|
|||
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
||||
const { data: backups, error, isValidating } = getServerBackups();
|
||||
|
||||
const backupLimit = ServerContext.useStoreState(state => state.server.data!.featureLimits.backups);
|
||||
const backupLimit = ServerContext.useStoreState((state) => state.server.data!.featureLimits.backups);
|
||||
|
||||
useEffect(() => {
|
||||
if (!error) {
|
||||
|
@ -26,53 +26,49 @@ const BackupContainer = () => {
|
|||
}
|
||||
|
||||
clearAndAddHttpError({ error, key: 'backups' });
|
||||
}, [ error ]);
|
||||
}, [error]);
|
||||
|
||||
if (!backups || (error && isValidating)) {
|
||||
return <Spinner size={'large'} centered/>;
|
||||
return <Spinner size={'large'} centered />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ServerContentBlock title={'Backups'}>
|
||||
<FlashMessageRender byKey={'backups'} css={tw`mb-4`}/>
|
||||
<FlashMessageRender byKey={'backups'} css={tw`mb-4`} />
|
||||
<Pagination data={backups} onPageSelect={setPage}>
|
||||
{({ items }) => (
|
||||
!items.length ?
|
||||
{({ items }) =>
|
||||
!items.length ? (
|
||||
// Don't show any error messages if the server has no backups and the user cannot
|
||||
// create additional ones for the server.
|
||||
!backupLimit ?
|
||||
null
|
||||
:
|
||||
!backupLimit ? null : (
|
||||
<p css={tw`text-center text-sm text-neutral-300`}>
|
||||
{page > 1 ?
|
||||
'Looks like we\'ve run out of backups to show you, try going back a page.'
|
||||
:
|
||||
'It looks like there are no backups currently stored for this server.'
|
||||
}
|
||||
{page > 1
|
||||
? "Looks like we've run out of backups to show you, try going back a page."
|
||||
: 'It looks like there are no backups currently stored for this server.'}
|
||||
</p>
|
||||
:
|
||||
items.map((backup, index) => <BackupRow
|
||||
key={backup.uuid}
|
||||
backup={backup}
|
||||
css={index > 0 ? tw`mt-2` : undefined}
|
||||
/>)
|
||||
)}
|
||||
)
|
||||
) : (
|
||||
items.map((backup, index) => (
|
||||
<BackupRow key={backup.uuid} backup={backup} css={index > 0 ? tw`mt-2` : undefined} />
|
||||
))
|
||||
)
|
||||
}
|
||||
</Pagination>
|
||||
{backupLimit === 0 &&
|
||||
<p css={tw`text-center text-sm text-neutral-300`}>
|
||||
Backups cannot be created for this server because the backup limit is set to 0.
|
||||
</p>
|
||||
}
|
||||
{backupLimit === 0 && (
|
||||
<p css={tw`text-center text-sm text-neutral-300`}>
|
||||
Backups cannot be created for this server because the backup limit is set to 0.
|
||||
</p>
|
||||
)}
|
||||
<Can action={'backup.create'}>
|
||||
<div css={tw`mt-6 sm:flex items-center justify-end`}>
|
||||
{(backupLimit > 0 && backups.backupCount > 0) &&
|
||||
<p css={tw`text-sm text-neutral-300 mb-4 sm:mr-6 sm:mb-0`}>
|
||||
{backups.backupCount} of {backupLimit} backups have been created for this server.
|
||||
</p>
|
||||
}
|
||||
{backupLimit > 0 && backupLimit > backups.backupCount &&
|
||||
<CreateBackupButton css={tw`w-full sm:w-auto`}/>
|
||||
}
|
||||
{backupLimit > 0 && backups.backupCount > 0 && (
|
||||
<p css={tw`text-sm text-neutral-300 mb-4 sm:mr-6 sm:mb-0`}>
|
||||
{backups.backupCount} of {backupLimit} backups have been created for this server.
|
||||
</p>
|
||||
)}
|
||||
{backupLimit > 0 && backupLimit > backups.backupCount && (
|
||||
<CreateBackupButton css={tw`w-full sm:w-auto`} />
|
||||
)}
|
||||
</div>
|
||||
</Can>
|
||||
</ServerContentBlock>
|
||||
|
@ -80,10 +76,10 @@ const BackupContainer = () => {
|
|||
};
|
||||
|
||||
export default () => {
|
||||
const [ page, setPage ] = useState<number>(1);
|
||||
const [page, setPage] = useState<number>(1);
|
||||
return (
|
||||
<ServerBackupContext.Provider value={{ page, setPage }}>
|
||||
<BackupContainer/>
|
||||
<BackupContainer />
|
||||
</ServerBackupContext.Provider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -28,11 +28,11 @@ interface Props {
|
|||
}
|
||||
|
||||
export default ({ backup }: Props) => {
|
||||
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
|
||||
const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState);
|
||||
const [ modal, setModal ] = useState('');
|
||||
const [ loading, setLoading ] = useState(false);
|
||||
const [ truncate, setTruncate ] = useState(false);
|
||||
const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid);
|
||||
const setServerFromState = ServerContext.useStoreActions((actions) => actions.server.setServerFromState);
|
||||
const [modal, setModal] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [truncate, setTruncate] = useState(false);
|
||||
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
||||
const { mutate } = getServerBackups();
|
||||
|
||||
|
@ -40,11 +40,11 @@ export default ({ backup }: Props) => {
|
|||
setLoading(true);
|
||||
clearFlashes('backups');
|
||||
getBackupDownloadUrl(uuid, backup.uuid)
|
||||
.then(url => {
|
||||
.then((url) => {
|
||||
// @ts-ignore
|
||||
window.location = url;
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
clearAndAddHttpError({ key: 'backups', error });
|
||||
})
|
||||
|
@ -55,12 +55,17 @@ export default ({ backup }: Props) => {
|
|||
setLoading(true);
|
||||
clearFlashes('backups');
|
||||
deleteBackup(uuid, backup.uuid)
|
||||
.then(() => mutate(data => ({
|
||||
...data,
|
||||
items: data.items.filter(b => b.uuid !== backup.uuid),
|
||||
backupCount: data.backupCount - 1,
|
||||
}), false))
|
||||
.catch(error => {
|
||||
.then(() =>
|
||||
mutate(
|
||||
(data) => ({
|
||||
...data,
|
||||
items: data.items.filter((b) => b.uuid !== backup.uuid),
|
||||
backupCount: data.backupCount - 1,
|
||||
}),
|
||||
false
|
||||
)
|
||||
)
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
clearAndAddHttpError({ key: 'backups', error });
|
||||
setLoading(false);
|
||||
|
@ -72,11 +77,13 @@ export default ({ backup }: Props) => {
|
|||
setLoading(true);
|
||||
clearFlashes('backups');
|
||||
restoreServerBackup(uuid, backup.uuid, truncate)
|
||||
.then(() => setServerFromState(s => ({
|
||||
...s,
|
||||
status: 'restoring_backup',
|
||||
})))
|
||||
.catch(error => {
|
||||
.then(() =>
|
||||
setServerFromState((s) => ({
|
||||
...s,
|
||||
status: 'restoring_backup',
|
||||
}))
|
||||
)
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
clearAndAddHttpError({ key: 'backups', error });
|
||||
})
|
||||
|
@ -90,14 +97,23 @@ export default ({ backup }: Props) => {
|
|||
}
|
||||
|
||||
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(() =>
|
||||
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(''));
|
||||
};
|
||||
|
||||
|
@ -123,17 +139,14 @@ export default ({ backup }: Props) => {
|
|||
manager, or create additional backups until completed.
|
||||
</p>
|
||||
<p css={tw`mt-4 -mb-2 bg-gray-700 p-3 rounded`}>
|
||||
<label
|
||||
htmlFor={'restore_truncate'}
|
||||
css={tw`text-base flex items-center cursor-pointer`}
|
||||
>
|
||||
<label htmlFor={'restore_truncate'} css={tw`text-base flex items-center cursor-pointer`}>
|
||||
<Input
|
||||
type={'checkbox'}
|
||||
css={tw`text-red-500! w-5! h-5! mr-2`}
|
||||
id={'restore_truncate'}
|
||||
value={'true'}
|
||||
checked={truncate}
|
||||
onChange={() => setTruncate(s => !s)}
|
||||
onChange={() => setTruncate((s) => !s)}
|
||||
/>
|
||||
Delete all files before restoring backup.
|
||||
</label>
|
||||
|
@ -148,28 +161,28 @@ export default ({ backup }: Props) => {
|
|||
>
|
||||
This is a permanent operation. The backup cannot be recovered once deleted.
|
||||
</Dialog.Confirm>
|
||||
<SpinnerOverlay visible={loading} fixed/>
|
||||
{backup.isSuccessful ?
|
||||
<SpinnerOverlay visible={loading} fixed />
|
||||
{backup.isSuccessful ? (
|
||||
<DropdownMenu
|
||||
renderToggle={onClick => (
|
||||
renderToggle={(onClick) => (
|
||||
<button
|
||||
onClick={onClick}
|
||||
css={tw`text-gray-200 transition-colors duration-150 hover:text-gray-100 p-2`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faEllipsisH}/>
|
||||
<FontAwesomeIcon icon={faEllipsisH} />
|
||||
</button>
|
||||
)}
|
||||
>
|
||||
<div css={tw`text-sm`}>
|
||||
<Can action={'backup.download'}>
|
||||
<DropdownButtonRow onClick={doDownload}>
|
||||
<FontAwesomeIcon fixedWidth icon={faCloudDownloadAlt} css={tw`text-xs`}/>
|
||||
<FontAwesomeIcon fixedWidth icon={faCloudDownloadAlt} css={tw`text-xs`} />
|
||||
<span css={tw`ml-2`}>Download</span>
|
||||
</DropdownButtonRow>
|
||||
</Can>
|
||||
<Can action={'backup.restore'}>
|
||||
<DropdownButtonRow onClick={() => setModal('restore')}>
|
||||
<FontAwesomeIcon fixedWidth icon={faBoxOpen} css={tw`text-xs`}/>
|
||||
<FontAwesomeIcon fixedWidth icon={faBoxOpen} css={tw`text-xs`} />
|
||||
<span css={tw`ml-2`}>Restore</span>
|
||||
</DropdownButtonRow>
|
||||
</Can>
|
||||
|
@ -183,24 +196,24 @@ export default ({ backup }: Props) => {
|
|||
/>
|
||||
{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>
|
||||
}
|
||||
{!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
|
||||
onClick={() => setModal('delete')}
|
||||
css={tw`text-gray-200 transition-colors duration-150 hover:text-gray-100 p-2`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrashAlt}/>
|
||||
<FontAwesomeIcon icon={faTrashAlt} />
|
||||
</button>
|
||||
}
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -21,20 +21,27 @@ interface Props {
|
|||
export default ({ backup, className }: Props) => {
|
||||
const { mutate } = getServerBackups();
|
||||
|
||||
useWebsocketEvent(`${SocketEvent.BACKUP_COMPLETED}:${backup.uuid}` as SocketEvent, data => {
|
||||
useWebsocketEvent(`${SocketEvent.BACKUP_COMPLETED}:${backup.uuid}` as SocketEvent, (data) => {
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
|
||||
mutate(data => ({
|
||||
...data,
|
||||
items: data.items.map(b => b.uuid !== backup.uuid ? b : ({
|
||||
...b,
|
||||
isSuccessful: parsed.is_successful || true,
|
||||
checksum: (parsed.checksum_type || '') + ':' + (parsed.checksum || ''),
|
||||
bytes: parsed.file_size || 0,
|
||||
completedAt: new Date(),
|
||||
})),
|
||||
}), false);
|
||||
mutate(
|
||||
(data) => ({
|
||||
...data,
|
||||
items: data.items.map((b) =>
|
||||
b.uuid !== backup.uuid
|
||||
? b
|
||||
: {
|
||||
...b,
|
||||
isSuccessful: parsed.is_successful || true,
|
||||
checksum: (parsed.checksum_type || '') + ':' + (parsed.checksum || ''),
|
||||
bytes: parsed.file_size || 0,
|
||||
completedAt: new Date(),
|
||||
}
|
||||
),
|
||||
}),
|
||||
false
|
||||
);
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
|
@ -44,52 +51,50 @@ export default ({ backup, className }: Props) => {
|
|||
<GreyRowBox css={tw`flex-wrap md:flex-nowrap items-center`} className={className}>
|
||||
<div css={tw`flex items-center truncate w-full md:flex-1`}>
|
||||
<div css={tw`mr-4`}>
|
||||
{backup.completedAt !== null ?
|
||||
backup.isLocked ?
|
||||
<FontAwesomeIcon icon={faLock} css={tw`text-yellow-500`}/>
|
||||
:
|
||||
<FontAwesomeIcon icon={faArchive} css={tw`text-neutral-300`}/>
|
||||
:
|
||||
<Spinner size={'small'}/>
|
||||
}
|
||||
{backup.completedAt !== null ? (
|
||||
backup.isLocked ? (
|
||||
<FontAwesomeIcon icon={faLock} css={tw`text-yellow-500`} />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faArchive} css={tw`text-neutral-300`} />
|
||||
)
|
||||
) : (
|
||||
<Spinner size={'small'} />
|
||||
)}
|
||||
</div>
|
||||
<div css={tw`flex flex-col truncate`}>
|
||||
<div css={tw`flex items-center text-sm mb-1`}>
|
||||
{backup.completedAt !== null && !backup.isSuccessful &&
|
||||
<span css={tw`bg-red-500 py-px px-2 rounded-full text-white text-xs uppercase border border-red-600 mr-2`}>
|
||||
Failed
|
||||
</span>
|
||||
}
|
||||
<p css={tw`break-words truncate`}>
|
||||
{backup.name}
|
||||
</p>
|
||||
{(backup.completedAt !== null && backup.isSuccessful) &&
|
||||
<span css={tw`ml-3 text-neutral-300 text-xs font-extralight hidden sm:inline`}>{bytesToString(backup.bytes)}</span>
|
||||
}
|
||||
{backup.completedAt !== null && !backup.isSuccessful && (
|
||||
<span
|
||||
css={tw`bg-red-500 py-px px-2 rounded-full text-white text-xs uppercase border border-red-600 mr-2`}
|
||||
>
|
||||
Failed
|
||||
</span>
|
||||
)}
|
||||
<p css={tw`break-words truncate`}>{backup.name}</p>
|
||||
{backup.completedAt !== null && backup.isSuccessful && (
|
||||
<span css={tw`ml-3 text-neutral-300 text-xs font-extralight hidden sm:inline`}>
|
||||
{bytesToString(backup.bytes)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p css={tw`mt-1 md:mt-0 text-xs text-neutral-400 font-mono truncate`}>
|
||||
{backup.checksum}
|
||||
</p>
|
||||
<p css={tw`mt-1 md:mt-0 text-xs text-neutral-400 font-mono truncate`}>{backup.checksum}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div css={tw`flex-1 md:flex-none md:w-48 mt-4 md:mt-0 md:ml-8 md:text-center`}>
|
||||
<p
|
||||
title={format(backup.createdAt, 'ddd, MMMM do, yyyy HH:mm:ss')}
|
||||
css={tw`text-sm`}
|
||||
>
|
||||
<p title={format(backup.createdAt, 'ddd, MMMM do, yyyy HH:mm:ss')} css={tw`text-sm`}>
|
||||
{formatDistanceToNow(backup.createdAt, { includeSeconds: true, addSuffix: true })}
|
||||
</p>
|
||||
<p css={tw`text-2xs text-neutral-500 uppercase mt-1`}>Created</p>
|
||||
</div>
|
||||
<Can action={[ 'backup.download', 'backup.restore', 'backup.delete' ]} matchAny>
|
||||
<Can action={['backup.download', 'backup.restore', 'backup.delete']} matchAny>
|
||||
<div css={tw`mt-4 md:mt-0 ml-6`} style={{ marginRight: '-0.5rem' }}>
|
||||
{!backup.completedAt ?
|
||||
{!backup.completedAt ? (
|
||||
<div css={tw`p-2 invisible`}>
|
||||
<FontAwesomeIcon icon={faEllipsisH}/>
|
||||
<FontAwesomeIcon icon={faEllipsisH} />
|
||||
</div>
|
||||
:
|
||||
<BackupContextMenu backup={backup}/>
|
||||
}
|
||||
) : (
|
||||
<BackupContextMenu backup={backup} />
|
||||
)}
|
||||
</div>
|
||||
</Can>
|
||||
</GreyRowBox>
|
||||
|
|
|
@ -27,7 +27,7 @@ const ModalContent = ({ ...props }: RequiredModalProps) => {
|
|||
return (
|
||||
<Modal {...props} showSpinnerOverlay={isSubmitting}>
|
||||
<Form>
|
||||
<FlashMessageRender byKey={'backups:create'} css={tw`mb-4`}/>
|
||||
<FlashMessageRender byKey={'backups:create'} css={tw`mb-4`} />
|
||||
<h2 css={tw`text-2xl mb-6`}>Create server backup</h2>
|
||||
<Field
|
||||
name={'name'}
|
||||
|
@ -45,7 +45,7 @@ const ModalContent = ({ ...props }: RequiredModalProps) => {
|
|||
prefixing the path with an exclamation point.
|
||||
`}
|
||||
>
|
||||
<FormikField as={Textarea} name={'ignored'} rows={6}/>
|
||||
<FormikField as={Textarea} name={'ignored'} rows={6} />
|
||||
</FormikFieldWrapper>
|
||||
</div>
|
||||
<Can action={'backup.delete'}>
|
||||
|
@ -68,23 +68,26 @@ const ModalContent = ({ ...props }: RequiredModalProps) => {
|
|||
};
|
||||
|
||||
export default () => {
|
||||
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
|
||||
const uuid = ServerContext.useStoreState((state) => state.server.data!.uuid);
|
||||
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
||||
const [ visible, setVisible ] = useState(false);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { mutate } = getServerBackups();
|
||||
|
||||
useEffect(() => {
|
||||
clearFlashes('backups:create');
|
||||
}, [ visible ]);
|
||||
}, [visible]);
|
||||
|
||||
const submit = (values: Values, { setSubmitting }: FormikHelpers<Values>) => {
|
||||
clearFlashes('backups:create');
|
||||
createServerBackup(uuid, values)
|
||||
.then(backup => {
|
||||
mutate(data => ({ ...data, items: data.items.concat(backup), backupCount: data.backupCount + 1 }), false);
|
||||
.then((backup) => {
|
||||
mutate(
|
||||
(data) => ({ ...data, items: data.items.concat(backup), backupCount: data.backupCount + 1 }),
|
||||
false
|
||||
);
|
||||
setVisible(false);
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
clearAndAddHttpError({ key: 'backups:create', error });
|
||||
setSubmitting(false);
|
||||
});
|
||||
|
@ -92,19 +95,19 @@ export default () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
{visible &&
|
||||
<Formik
|
||||
onSubmit={submit}
|
||||
initialValues={{ name: '', ignored: '', isLocked: false }}
|
||||
validationSchema={object().shape({
|
||||
name: string().max(191),
|
||||
ignored: string(),
|
||||
isLocked: boolean(),
|
||||
})}
|
||||
>
|
||||
<ModalContent appear visible={visible} onDismissed={() => setVisible(false)}/>
|
||||
</Formik>
|
||||
}
|
||||
{visible && (
|
||||
<Formik
|
||||
onSubmit={submit}
|
||||
initialValues={{ name: '', ignored: '', isLocked: false }}
|
||||
validationSchema={object().shape({
|
||||
name: string().max(191),
|
||||
ignored: string(),
|
||||
isLocked: boolean(),
|
||||
})}
|
||||
>
|
||||
<ModalContent appear visible={visible} onDismissed={() => setVisible(false)} />
|
||||
</Formik>
|
||||
)}
|
||||
<Button css={tw`w-full sm:w-auto`} onClick={() => setVisible(true)}>
|
||||
Create backup
|
||||
</Button>
|
||||
|
|
Reference in a new issue