Make database rows use context better

This commit is contained in:
Dane Everitt 2020-04-10 10:56:25 -07:00
parent 0ebf842757
commit 708c15eba8
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
5 changed files with 83 additions and 86 deletions

View file

@ -1,15 +1,14 @@
import React, { useState } from 'react';
import { ServerDatabase } from '@/api/server/getServerDatabases';
import Modal from '@/components/elements/Modal';
import { Form, Formik, FormikHelpers } from 'formik';
import Field from '@/components/elements/Field';
import { object, string } from 'yup';
import createServerDatabase from '@/api/server/createServerDatabase';
import { ServerContext } from '@/state/server';
import { Actions, useStoreActions } from 'easy-peasy';
import { ApplicationStore } from '@/state';
import { httpErrorToHuman } from '@/api/http';
import FlashMessageRender from '@/components/FlashMessageRender';
import useFlash from '@/plugins/useFlash';
import useServer from '@/plugins/useServer';
interface Values {
databaseName: string;
@ -27,28 +26,25 @@ const schema = object().shape({
.matches(/^([1-9]{1,3}|%)(\.([0-9]{1,3}|%))?(\.([0-9]{1,3}|%))?(\.([0-9]{1,3}|%))?$/, 'A valid connection address must be provided.'),
});
export default ({ onCreated }: { onCreated: (database: ServerDatabase) => void }) => {
export default () => {
const { uuid } = useServer();
const { addError, clearFlashes } = useFlash();
const [ visible, setVisible ] = useState(false);
const { addFlash, clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
const server = ServerContext.useStoreState(state => state.server.data!);
const appendDatabase = ServerContext.useStoreActions(actions => actions.databases.appendDatabase);
const submit = (values: Values, { setSubmitting }: FormikHelpers<Values>) => {
clearFlashes();
createServerDatabase(server.uuid, { ...values })
clearFlashes('database:create');
createServerDatabase(uuid, { ...values })
.then(database => {
onCreated(database);
appendDatabase(database);
setVisible(false);
})
.catch(error => {
console.log(error);
addFlash({
key: 'create-database-modal',
type: 'error',
title: 'Error',
message: httpErrorToHuman(error),
addError({ key: 'database:create', message: httpErrorToHuman(error) });
setSubmitting(false);
});
})
.then(() => setSubmitting(false));
};
return (
@ -69,7 +65,7 @@ export default ({ onCreated }: { onCreated: (database: ServerDatabase) => void }
setVisible(false);
}}
>
<FlashMessageRender byKey={'create-database-modal'} className={'mb-6'}/>
<FlashMessageRender byKey={'database:create'} className={'mb-6'}/>
<h3 className={'mb-6'}>Create new database</h3>
<Form className={'m-0'}>
<Field
@ -105,7 +101,7 @@ export default ({ onCreated }: { onCreated: (database: ServerDatabase) => void }
)
}
</Formik>
<button className={'btn btn-primary btn-lg'} onClick={() => setVisible(true)}>
<button className={'btn btn-primary btn-sm'} onClick={() => setVisible(true)}>
New Database
</button>
</React.Fragment>

View file

@ -9,31 +9,28 @@ import { Form, Formik, FormikHelpers } from 'formik';
import Field from '@/components/elements/Field';
import { object, string } from 'yup';
import FlashMessageRender from '@/components/FlashMessageRender';
import { Actions, useStoreActions } from 'easy-peasy';
import { ApplicationStore } from '@/state';
import { ServerContext } from '@/state/server';
import deleteServerDatabase from '@/api/server/deleteServerDatabase';
import { httpErrorToHuman } from '@/api/http';
import RotatePasswordButton from '@/components/server/databases/RotatePasswordButton';
import Can from '@/components/elements/Can';
import { ServerDatabase } from '@/api/server/getServerDatabases';
import useServer from '@/plugins/useServer';
import useFlash from '@/plugins/useFlash';
interface Props {
databaseId: string | number;
database: ServerDatabase;
className?: string;
onDelete: () => void;
}
export default ({ databaseId, className, onDelete }: Props) => {
export default ({ database, className }: Props) => {
const { uuid } = useServer();
const { addError, clearFlashes } = useFlash();
const [ visible, setVisible ] = useState(false);
const database = ServerContext.useStoreState(state => state.databases.items.find(item => item.id === databaseId));
const appendDatabase = ServerContext.useStoreActions(actions => actions.databases.appendDatabase);
const [ connectionVisible, setConnectionVisible ] = useState(false);
const { addFlash, clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
const server = ServerContext.useStoreState(state => state.server.data!);
if (!database) {
return null;
}
const appendDatabase = ServerContext.useStoreActions(actions => actions.databases.appendDatabase);
const removeDatabase = ServerContext.useStoreActions(actions => actions.databases.removeDatabase);
const schema = object().shape({
confirm: string()
@ -43,20 +40,15 @@ export default ({ databaseId, className, onDelete }: Props) => {
const submit = (values: { confirm: string }, { setSubmitting }: FormikHelpers<{ confirm: string }>) => {
clearFlashes();
deleteServerDatabase(server.uuid, database.id)
deleteServerDatabase(uuid, database.id)
.then(() => {
setVisible(false);
setTimeout(() => onDelete(), 150);
setTimeout(() => removeDatabase(database.id), 150);
})
.catch(error => {
console.error(error);
setSubmitting(false);
addFlash({
key: 'delete-database-modal',
type: 'error',
title: 'Error',
message: httpErrorToHuman(error),
});
addError({ key: 'database:delete', message: httpErrorToHuman(error) });
});
};
@ -78,7 +70,7 @@ export default ({ databaseId, className, onDelete }: Props) => {
resetForm();
}}
>
<FlashMessageRender byKey={'delete-database-modal'} className={'mb-6'}/>
<FlashMessageRender byKey={'database:delete'} className={'mb-6'}/>
<h3 className={'mb-6'}>Confirm database deletion</h3>
<p className={'text-sm'}>
Deleting a database is a permanent action, it cannot be undone. This will permanetly

View file

@ -1,8 +1,6 @@
import React, { useEffect, useState } from 'react';
import getServerDatabases from '@/api/server/getServerDatabases';
import { ServerContext } from '@/state/server';
import { Actions, useStoreActions } from 'easy-peasy';
import { ApplicationStore } from '@/state';
import { httpErrorToHuman } from '@/api/http';
import FlashMessageRender from '@/components/FlashMessageRender';
import DatabaseRow from '@/components/server/databases/DatabaseRow';
@ -10,51 +8,51 @@ import Spinner from '@/components/elements/Spinner';
import { CSSTransition } from 'react-transition-group';
import CreateDatabaseButton from '@/components/server/databases/CreateDatabaseButton';
import Can from '@/components/elements/Can';
import useFlash from '@/plugins/useFlash';
import useServer from '@/plugins/useServer';
import ListRefreshIndicator from '@/components/elements/ListRefreshIndicator';
export default () => {
const { uuid, featureLimits } = useServer();
const { addError, clearFlashes } = useFlash();
const [ loading, setLoading ] = useState(true);
const server = ServerContext.useStoreState(state => state.server.data!);
const databases = ServerContext.useStoreState(state => state.databases.items);
const { setDatabases, appendDatabase, removeDatabase } = ServerContext.useStoreActions(state => state.databases);
const { addFlash, clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
const databases = ServerContext.useStoreState(state => state.databases.data);
const setDatabases = ServerContext.useStoreActions(state => state.databases.setDatabases);
useEffect(() => {
setLoading(!databases.length);
clearFlashes('databases');
getServerDatabases(server.uuid)
.then(databases => {
setDatabases(databases);
setLoading(false);
getServerDatabases(uuid)
.then(databases => setDatabases(databases))
.catch(error => {
console.error(error);
addError({ key: 'databases', message: httpErrorToHuman(error) });
})
.catch(error => addFlash({
key: 'databases',
title: 'Error',
message: httpErrorToHuman(error),
type: 'error',
}));
.then(() => setLoading(false));
}, []);
return (
<div className={'my-10 mb-6'}>
<FlashMessageRender byKey={'databases'}/>
{loading ?
<FlashMessageRender byKey={'databases'} className={'mb-4'}/>
{(!databases.length && loading) ?
<Spinner size={'large'} centered={true}/>
:
<CSSTransition classNames={'fade'} timeout={250}>
<>
<ListRefreshIndicator visible={loading}/>
{databases.length > 0 ?
databases.map((database, index) => (
<DatabaseRow
key={database.id}
databaseId={database.id}
onDelete={() => removeDatabase(database)}
database={database}
className={index > 0 ? 'mt-1' : undefined}
/>
))
:
<p className={'text-center text-sm text-neutral-400'}>
{server.featureLimits.databases > 0 ?
{featureLimits.databases > 0 ?
`It looks like you have no databases.`
:
`Databases cannot be created for this server.`
@ -62,9 +60,9 @@ export default () => {
</p>
}
<Can action={'database.create'}>
{server.featureLimits.databases > 0 &&
{featureLimits.databases > 0 &&
<div className={'mt-6 flex justify-end'}>
<CreateDatabaseButton onCreated={appendDatabase}/>
<CreateDatabaseButton/>
</div>
}
</Can>

View file

@ -0,0 +1,31 @@
import { action, Action } from 'easy-peasy';
import { ServerDatabase } from '@/api/server/getServerDatabases';
export interface ServerDatabaseStore {
data: ServerDatabase[];
setDatabases: Action<ServerDatabaseStore, ServerDatabase[]>;
appendDatabase: Action<ServerDatabaseStore, ServerDatabase>;
removeDatabase: Action<ServerDatabaseStore, string>;
}
const databases: ServerDatabaseStore = {
data: [],
setDatabases: action((state, payload) => {
state.data = payload;
}),
appendDatabase: action((state, payload) => {
if (state.data.find(database => database.id === payload.id)) {
state.data = state.data.map(database => database.id === payload.id ? payload : database);
} else {
state.data = [ ...state.data, payload ];
}
}),
removeDatabase: action((state, payload) => {
state.data = [ ...state.data.filter(database => database.id !== payload) ];
}),
};
export default databases;

View file

@ -1,12 +1,12 @@
import getServer, { Server } from '@/api/server/getServer';
import { action, Action, createContextStore, thunk, Thunk } from 'easy-peasy';
import socket, { SocketStore } from './socket';
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';
import schedules, { ServerScheduleStore } from '@/state/server/schedules';
import databases, { ServerDatabaseStore } from '@/state/server/databases';
export type ServerStatus = 'offline' | 'starting' | 'stopping' | 'running';
@ -23,7 +23,7 @@ const server: ServerDataStore = {
permissions: [],
getServer: thunk(async (actions, payload) => {
const [server, permissions] = await getServer(payload);
const [ server, permissions ] = await getServer(payload);
actions.setServer(server);
actions.setPermissions(permissions);
@ -50,26 +50,6 @@ const status: ServerStatusStore = {
}),
};
interface ServerDatabaseStore {
items: ServerDatabase[];
setDatabases: Action<ServerDatabaseStore, ServerDatabase[]>;
appendDatabase: Action<ServerDatabaseStore, ServerDatabase>;
removeDatabase: Action<ServerDatabaseStore, ServerDatabase>;
}
const databases: ServerDatabaseStore = {
items: [],
setDatabases: action((state, payload) => {
state.items = payload;
}),
appendDatabase: action((state, payload) => {
state.items = state.items.filter(item => item.id !== payload.id).concat(payload);
}),
removeDatabase: action((state, payload) => {
state.items = state.items.filter(item => item.id !== payload.id);
}),
};
export interface ServerStore {
server: ServerDataStore;
subusers: ServerSubuserStore;
@ -94,7 +74,7 @@ export const ServerContext = createContextStore<ServerStore>({
clearServerState: action(state => {
state.server.data = undefined;
state.server.permissions = [];
state.databases.items = [];
state.databases.data = [];
state.subusers.data = [];
state.files.directory = '/';
state.files.contents = [];