diff --git a/resources/scripts/components/server/databases/CreateDatabaseButton.tsx b/resources/scripts/components/server/databases/CreateDatabaseButton.tsx index 25b488e08..cf5b8c63d 100644 --- a/resources/scripts/components/server/databases/CreateDatabaseButton.tsx +++ b/resources/scripts/components/server/databases/CreateDatabaseButton.tsx @@ -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) => actions.flashes); - const server = ServerContext.useStoreState(state => state.server.data!); + + const appendDatabase = ServerContext.useStoreActions(actions => actions.databases.appendDatabase); const submit = (values: Values, { setSubmitting }: FormikHelpers) => { - 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), - }); - }) - .then(() => setSubmitting(false)); + addError({ key: 'database:create', message: httpErrorToHuman(error) }); + setSubmitting(false); + }); }; return ( @@ -69,7 +65,7 @@ export default ({ onCreated }: { onCreated: (database: ServerDatabase) => void } setVisible(false); }} > - +

Create new database

void } ) } - diff --git a/resources/scripts/components/server/databases/DatabaseRow.tsx b/resources/scripts/components/server/databases/DatabaseRow.tsx index 1fd07774d..a80b304c3 100644 --- a/resources/scripts/components/server/databases/DatabaseRow.tsx +++ b/resources/scripts/components/server/databases/DatabaseRow.tsx @@ -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) => 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(); }} > - +

Confirm database deletion

Deleting a database is a permanent action, it cannot be undone. This will permanetly diff --git a/resources/scripts/components/server/databases/DatabasesContainer.tsx b/resources/scripts/components/server/databases/DatabasesContainer.tsx index a5335f5c0..0a90f985a 100644 --- a/resources/scripts/components/server/databases/DatabasesContainer.tsx +++ b/resources/scripts/components/server/databases/DatabasesContainer.tsx @@ -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) => 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 (

- - {loading ? + + {(!databases.length && loading) ? : <> + {databases.length > 0 ? databases.map((database, index) => ( removeDatabase(database)} + database={database} className={index > 0 ? 'mt-1' : undefined} /> )) :

- {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 () => {

} - {server.featureLimits.databases > 0 && + {featureLimits.databases > 0 &&
- +
}
diff --git a/resources/scripts/state/server/databases.ts b/resources/scripts/state/server/databases.ts new file mode 100644 index 000000000..7fa697dbd --- /dev/null +++ b/resources/scripts/state/server/databases.ts @@ -0,0 +1,31 @@ +import { action, Action } from 'easy-peasy'; +import { ServerDatabase } from '@/api/server/getServerDatabases'; + +export interface ServerDatabaseStore { + data: ServerDatabase[]; + setDatabases: Action; + appendDatabase: Action; + removeDatabase: Action; +} + +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; diff --git a/resources/scripts/state/server/index.ts b/resources/scripts/state/server/index.ts index 32b3a7c54..e6d389668 100644 --- a/resources/scripts/state/server/index.ts +++ b/resources/scripts/state/server/index.ts @@ -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; - appendDatabase: Action; - removeDatabase: Action; -} - -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({ 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 = [];