Support pagination of server backups, closes #2787

This commit is contained in:
Dane Everitt 2020-12-06 12:01:42 -08:00
parent 8a97b73a6c
commit a8d9eccf9c
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
6 changed files with 53 additions and 22 deletions

View File

@ -60,7 +60,8 @@ class BackupController extends ClientApiController
*/ */
public function index(GetBackupsRequest $request, Server $server) public function index(GetBackupsRequest $request, Server $server)
{ {
return $this->fractal->collection($server->backups()->paginate(20)) $limit = min($request->query('per_page') ?? 20, 50);
return $this->fractal->collection($server->backups()->paginate($limit))
->transformWith($this->getTransformer(BackupTransformer::class)) ->transformWith($this->getTransformer(BackupTransformer::class))
->toArray(); ->toArray();
} }

View File

@ -3,8 +3,17 @@ import http, { getPaginationSet, PaginatedResult } from '@/api/http';
import { ServerBackup } from '@/api/server/types'; import { ServerBackup } from '@/api/server/types';
import { rawDataToServerBackup } from '@/api/transformers'; import { rawDataToServerBackup } from '@/api/transformers';
import { ServerContext } from '@/state/server'; import { ServerContext } from '@/state/server';
import { createContext, useContext } from 'react';
export default (page?: number | string) => { interface ctx {
page: number;
setPage: (value: number | ((s: number) => number)) => void;
}
export const Context = createContext<ctx>({ page: 1, setPage: () => 1 });
export default () => {
const { page } = useContext(Context);
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
return useSWR<PaginatedResult<ServerBackup>>([ 'server:backups', uuid, page ], async () => { return useSWR<PaginatedResult<ServerBackup>>([ 'server:backups', uuid, page ], async () => {

View File

@ -13,7 +13,7 @@ const ApiKeyModal = ({ apiKey }: Props) => {
return ( return (
<> <>
<h3 css={tw`mb-6`}>Your API Key</h3> <h3 css={tw`mb-6 text-2xl`}>Your API Key</h3>
<p css={tw`text-sm mb-6`}> <p css={tw`text-sm mb-6`}>
The API key you have requested is shown below. Please store this in a safe location, it will not be The API key you have requested is shown below. Please store this in a safe location, it will not be
shown again. shown again.

View File

@ -1,4 +1,4 @@
import React, { useEffect } from 'react'; import React, { useContext, useEffect, useState } from 'react';
import Spinner from '@/components/elements/Spinner'; import Spinner from '@/components/elements/Spinner';
import useFlash from '@/plugins/useFlash'; import useFlash from '@/plugins/useFlash';
import Can from '@/components/elements/Can'; import Can from '@/components/elements/Can';
@ -6,11 +6,13 @@ import CreateBackupButton from '@/components/server/backups/CreateBackupButton';
import FlashMessageRender from '@/components/FlashMessageRender'; import FlashMessageRender from '@/components/FlashMessageRender';
import BackupRow from '@/components/server/backups/BackupRow'; import BackupRow from '@/components/server/backups/BackupRow';
import tw from 'twin.macro'; import tw from 'twin.macro';
import getServerBackups from '@/api/swr/getServerBackups'; import getServerBackups, { Context as ServerBackupContext } from '@/api/swr/getServerBackups';
import { ServerContext } from '@/state/server'; import { ServerContext } from '@/state/server';
import ServerContentBlock from '@/components/elements/ServerContentBlock'; import ServerContentBlock from '@/components/elements/ServerContentBlock';
import Pagination from '@/components/elements/Pagination';
export default () => { const BackupContainer = () => {
const { page, setPage } = useContext(ServerBackupContext);
const { clearFlashes, clearAndAddHttpError } = useFlash(); const { clearFlashes, clearAndAddHttpError } = useFlash();
const { data: backups, error, isValidating } = getServerBackups(); const { data: backups, error, isValidating } = getServerBackups();
@ -33,19 +35,29 @@ export default () => {
return ( return (
<ServerContentBlock title={'Backups'}> <ServerContentBlock title={'Backups'}>
<FlashMessageRender byKey={'backups'} css={tw`mb-4`}/> <FlashMessageRender byKey={'backups'} css={tw`mb-4`}/>
{!backups.items.length ? <Pagination data={backups} onPageSelect={setPage}>
<p css={tw`text-center text-sm text-neutral-300`}> {({ items }) => (
There are no backups stored for this server. !items.length ?
</p> // Don't show any error messages if the server has no backups and the user cannot
: // create additional ones for the server.
<div> !backupLimit ?
{backups.items.map((backup, index) => <BackupRow null
key={backup.uuid} :
backup={backup} <p css={tw`text-center text-sm text-neutral-300`}>
css={index > 0 ? tw`mt-2` : undefined} {page > 1 ?
/>)} 'Looks like we\'ve run out of backups to show you, try going back a page.'
</div> :
} '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}
/>)
)}
</Pagination>
{backupLimit === 0 && {backupLimit === 0 &&
<p css={tw`text-center text-sm text-neutral-300`}> <p css={tw`text-center text-sm text-neutral-300`}>
Backups cannot be created for this server. Backups cannot be created for this server.
@ -59,10 +71,19 @@ export default () => {
</p> </p>
} }
{backupLimit > 0 && backupLimit !== backups.items.length && {backupLimit > 0 && backupLimit !== backups.items.length &&
<CreateBackupButton css={tw`w-full sm:w-auto`}/> <CreateBackupButton css={tw`w-full sm:w-auto`}/>
} }
</div> </div>
</Can> </Can>
</ServerContentBlock> </ServerContentBlock>
); );
}; };
export default () => {
const [ page, setPage ] = useState<number>(1);
return (
<ServerBackupContext.Provider value={{ page, setPage }}>
<BackupContainer/>
</ServerBackupContext.Provider>
);
};

View File

@ -4,7 +4,7 @@ import tw from 'twin.macro';
const ChecksumModal = ({ checksum, ...props }: RequiredModalProps & { checksum: string }) => ( const ChecksumModal = ({ checksum, ...props }: RequiredModalProps & { checksum: string }) => (
<Modal {...props}> <Modal {...props}>
<h3 css={tw`mb-6`}>Verify file checksum</h3> <h3 css={tw`mb-6 text-2xl`}>Verify file checksum</h3>
<p css={tw`text-sm`}> <p css={tw`text-sm`}>
The checksum of this file is: The checksum of this file is:
</p> </p>

View File

@ -111,7 +111,7 @@ export default ({ database, className }: Props) => {
</Formik> </Formik>
<Modal visible={connectionVisible} onDismissed={() => setConnectionVisible(false)}> <Modal visible={connectionVisible} onDismissed={() => setConnectionVisible(false)}>
<FlashMessageRender byKey={'database-connection-modal'} css={tw`mb-6`}/> <FlashMessageRender byKey={'database-connection-modal'} css={tw`mb-6`}/>
<h3 css={tw`mb-6`}>Database connection details</h3> <h3 css={tw`mb-6 text-2xl`}>Database connection details</h3>
<div> <div>
<Label>Endpoint</Label> <Label>Endpoint</Label>
<CopyOnClick text={database.connectionString}><Input type={'text'} readOnly value={database.connectionString} /></CopyOnClick> <CopyOnClick text={database.connectionString}><Input type={'text'} readOnly value={database.connectionString} /></CopyOnClick>