From 2eb6ab4d63787cd284f568125f6048c7b94f65d2 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Mon, 6 Apr 2020 22:25:54 -0700 Subject: [PATCH] Store backups in server state --- .../elements/ListRefreshIndicator.tsx | 19 ++++++++++++ .../server/backups/BackupContainer.tsx | 20 ++++++------ .../components/server/backups/BackupRow.tsx | 8 +++-- .../server/backups/CreateBackupButton.tsx | 16 +++++----- resources/scripts/state/server/backups.ts | 31 +++++++++++++++++++ resources/scripts/state/server/index.ts | 4 +++ 6 files changed, 75 insertions(+), 23 deletions(-) create mode 100644 resources/scripts/components/elements/ListRefreshIndicator.tsx create mode 100644 resources/scripts/state/server/backups.ts diff --git a/resources/scripts/components/elements/ListRefreshIndicator.tsx b/resources/scripts/components/elements/ListRefreshIndicator.tsx new file mode 100644 index 00000000..075f5363 --- /dev/null +++ b/resources/scripts/components/elements/ListRefreshIndicator.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import Spinner from '@/components/elements/Spinner'; +import { CSSTransition } from 'react-transition-group'; + +interface Props { + visible: boolean; + children?: React.ReactChild; +} + +const ListRefreshIndicator = ({ visible, children }: Props) => ( + +
+ +

{children || 'Refreshing listing...'}

+
+
+); + +export default ListRefreshIndicator; diff --git a/resources/scripts/components/server/backups/BackupContainer.tsx b/resources/scripts/components/server/backups/BackupContainer.tsx index d00f2999..f3841076 100644 --- a/resources/scripts/components/server/backups/BackupContainer.tsx +++ b/resources/scripts/components/server/backups/BackupContainer.tsx @@ -8,19 +8,21 @@ import Can from '@/components/elements/Can'; import CreateBackupButton from '@/components/server/backups/CreateBackupButton'; import FlashMessageRender from '@/components/FlashMessageRender'; import BackupRow from '@/components/server/backups/BackupRow'; +import { ServerContext } from '@/state/server'; +import ListRefreshIndicator from '@/components/elements/ListRefreshIndicator'; export default () => { const { uuid } = useServer(); const { addError, clearFlashes } = useFlash(); const [ loading, setLoading ] = useState(true); - const [ backups, setBackups ] = useState([]); + + const backups = ServerContext.useStoreState(state => state.backups.data); + const setBackups = ServerContext.useStoreActions(actions => actions.backups.setBackups); useEffect(() => { clearFlashes('backups'); getServerBackups(uuid) - .then(data => { - setBackups(data.items); - }) + .then(data => setBackups(data.items)) .catch(error => { console.error(error); addError({ key: 'backups', message: httpErrorToHuman(error) }); @@ -28,12 +30,13 @@ export default () => { .then(() => setLoading(false)); }, []); - if (loading) { + if (backups.length === 0 && loading) { return ; } return (
+ {!backups.length ?

@@ -44,18 +47,13 @@ export default () => { {backups.map((backup, index) => setBackups( - s => ([ ...s.map(b => b.uuid === data.uuid ? data : b) ]), - )} className={index !== (backups.length - 1) ? 'mb-2' : undefined} />)}

}
- setBackups(s => [ ...s, backup ])} - /> +
diff --git a/resources/scripts/components/server/backups/BackupRow.tsx b/resources/scripts/components/server/backups/BackupRow.tsx index 570fec64..7159c707 100644 --- a/resources/scripts/components/server/backups/BackupRow.tsx +++ b/resources/scripts/components/server/backups/BackupRow.tsx @@ -15,10 +15,10 @@ import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import useFlash from '@/plugins/useFlash'; import { httpErrorToHuman } from '@/api/http'; import useWebsocketEvent from '@/plugins/useWebsocketEvent'; +import { ServerContext } from '@/state/server'; interface Props { backup: ServerBackup; - onBackupUpdated: (backup: ServerBackup) => void; className?: string; } @@ -34,16 +34,18 @@ const DownloadModal = ({ checksum, ...props }: RequiredModalProps & { checksum: ); -export default ({ backup, onBackupUpdated, className }: Props) => { +export default ({ backup, className }: Props) => { const { uuid } = useServer(); const { addError, clearFlashes } = useFlash(); const [ loading, setLoading ] = useState(false); const [ visible, setVisible ] = useState(false); + const appendBackup = ServerContext.useStoreActions(actions => actions.backups.appendBackup); + useWebsocketEvent(`backup completed:${backup.uuid}`, data => { try { const parsed = JSON.parse(data); - onBackupUpdated({ + appendBackup({ ...backup, sha256Hash: parsed.sha256_hash || '', bytes: parsed.file_size || 0, diff --git a/resources/scripts/components/server/backups/CreateBackupButton.tsx b/resources/scripts/components/server/backups/CreateBackupButton.tsx index 256a8832..476ce0b7 100644 --- a/resources/scripts/components/server/backups/CreateBackupButton.tsx +++ b/resources/scripts/components/server/backups/CreateBackupButton.tsx @@ -9,17 +9,13 @@ import useServer from '@/plugins/useServer'; import createServerBackup from '@/api/server/backups/createServerBackup'; import { httpErrorToHuman } from '@/api/http'; import FlashMessageRender from '@/components/FlashMessageRender'; -import { ServerBackup } from '@/api/server/backups/getServerBackups'; +import { ServerContext } from '@/state/server'; interface Values { name: string; ignored: string; } -interface Props { - onBackupGenerated: (backup: ServerBackup) => void; -} - const ModalContent = ({ ...props }: RequiredModalProps) => { const { isSubmitting } = useFormikContext(); @@ -66,20 +62,22 @@ const ModalContent = ({ ...props }: RequiredModalProps) => { ); }; -export default ({ onBackupGenerated }: Props) => { +export default () => { const { uuid } = useServer(); const { addError, clearFlashes } = useFlash(); const [ visible, setVisible ] = useState(false); + const appendBackup = ServerContext.useStoreActions(actions => actions.backups.appendBackup); + useEffect(() => { clearFlashes('backups:create'); - }, [visible]); + }, [ visible ]); const submit = ({ name, ignored }: Values, { setSubmitting }: FormikHelpers) => { - clearFlashes('backups:create') + clearFlashes('backups:create'); createServerBackup(uuid, name, ignored) .then(backup => { - onBackupGenerated(backup); + appendBackup(backup); setVisible(false); }) .catch(error => { diff --git a/resources/scripts/state/server/backups.ts b/resources/scripts/state/server/backups.ts new file mode 100644 index 00000000..aa24bdf7 --- /dev/null +++ b/resources/scripts/state/server/backups.ts @@ -0,0 +1,31 @@ +import { ServerBackup } from '@/api/server/backups/getServerBackups'; +import { action, Action } from 'easy-peasy'; + +export interface ServerBackupStore { + data: ServerBackup[]; + setBackups: Action; + appendBackup: Action; + removeBackup: Action; +} + +const backups: ServerBackupStore = { + data: [], + + setBackups: action((state, payload) => { + state.data = payload; + }), + + appendBackup: action((state, payload) => { + if (state.data.find(backup => backup.uuid === payload.uuid)) { + state.data = state.data.map(backup => backup.uuid === payload.uuid ? payload : backup); + } else { + state.data = [ ...state.data, payload ]; + } + }), + + removeBackup: action((state, payload) => { + state.data = [ ...state.data.filter(backup => backup.uuid !== payload) ]; + }), +}; + +export default backups; diff --git a/resources/scripts/state/server/index.ts b/resources/scripts/state/server/index.ts index fb26a716..45acb7ea 100644 --- a/resources/scripts/state/server/index.ts +++ b/resources/scripts/state/server/index.ts @@ -5,6 +5,7 @@ import { ServerDatabase } from '@/api/server/getServerDatabases'; import files, { ServerFileStore } from '@/state/server/files'; import subusers, { ServerSubuserStore } from '@/state/server/subusers'; import { composeWithDevTools } from 'redux-devtools-extension'; +import backups, { ServerBackupStore } from '@/state/server/backups'; export type ServerStatus = 'offline' | 'starting' | 'stopping' | 'running'; @@ -73,6 +74,7 @@ export interface ServerStore { subusers: ServerSubuserStore; databases: ServerDatabaseStore; files: ServerFileStore; + backups: ServerBackupStore; socket: SocketStore; status: ServerStatusStore; clearServerState: Action; @@ -85,6 +87,7 @@ export const ServerContext = createContextStore({ databases, files, subusers, + backups, clearServerState: action(state => { state.server.data = undefined; state.server.permissions = []; @@ -92,6 +95,7 @@ export const ServerContext = createContextStore({ state.subusers.data = []; state.files.directory = '/'; state.files.contents = []; + state.backups.backups = []; if (state.socket.instance) { state.socket.instance.removeAllListeners();