diff --git a/resources/scripts/api/server/createServerDatabase.ts b/resources/scripts/api/server/createServerDatabase.ts new file mode 100644 index 00000000..90103337 --- /dev/null +++ b/resources/scripts/api/server/createServerDatabase.ts @@ -0,0 +1,15 @@ +import { rawDataToServerDatabase, ServerDatabase } from '@/api/server/getServerDatabases'; +import http from '@/api/http'; + +export default (uuid: string, data: { connectionsFrom: string; databaseName: string }): Promise => { + return new Promise((resolve, reject) => { + http.post(`/api/client/servers/${uuid}/databases`, { + database: data.databaseName, + remote: data.connectionsFrom, + }, { + params: { include: 'password' }, + }) + .then(response => resolve(rawDataToServerDatabase(response.data.attributes))) + .catch(reject); + }); +}; diff --git a/resources/scripts/components/elements/Modal.tsx b/resources/scripts/components/elements/Modal.tsx index ce15572f..be1e62e2 100644 --- a/resources/scripts/components/elements/Modal.tsx +++ b/resources/scripts/components/elements/Modal.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faTimes } from '@fortawesome/free-solid-svg-icons/faTimes'; import { CSSTransition } from 'react-transition-group'; +import Spinner from '@/components/elements/Spinner'; interface Props { visible: boolean; @@ -9,7 +10,8 @@ interface Props { dismissable?: boolean; closeOnEscape?: boolean; closeOnBackground?: boolean; - children: React.ReactChild; + showSpinnerOverlay?: boolean; + children: React.ReactNode; } export default (props: Props) => { @@ -51,6 +53,14 @@ export default (props: Props) => { } + {props.showSpinnerOverlay && +
+ +
+ }
{props.children}
diff --git a/resources/scripts/components/server/databases/CreateDatabaseButton.tsx b/resources/scripts/components/server/databases/CreateDatabaseButton.tsx index 30d353e9..8430e492 100644 --- a/resources/scripts/components/server/databases/CreateDatabaseButton.tsx +++ b/resources/scripts/components/server/databases/CreateDatabaseButton.tsx @@ -1,17 +1,111 @@ import React, { useState } from 'react'; import { ServerDatabase } from '@/api/server/getServerDatabases'; import Modal from '@/components/elements/Modal'; +import { Form, Formik, FormikActions } 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'; + +interface Values { + databaseName: string; + connectionsFrom: string; +} + +const schema = object().shape({ + databaseName: string() + .required('A database name must be provided.') + .min(5, 'Database name must be at least 5 characters.') + .max(64, 'Database name must not exceed 64 characters.') + .matches(/^[A-Za-z0-9_\-.]{5,64}$/, 'Database name should only contain alphanumeric characters, underscores, dashes, and/or periods.'), + connectionsFrom: string() + .required('A connection value must be provided.') + .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 }) => { const [ visible, setVisible ] = useState(false); + const { addFlash, clearFlashes } = useStoreActions((actions: Actions) => actions.flashes); + const server = ServerContext.useStoreState(state => state.server.data!); + + const submit = (values: Values, { setSubmitting }: FormikActions) => { + clearFlashes(); + createServerDatabase(server.uuid, { ...values }) + .then(database => { + onCreated(database); + setVisible(false); + }) + .catch(error => { + console.log(error); + addFlash({ + key: 'create-database-modal', + type: 'error', + title: 'Error', + message: httpErrorToHuman(error), + }); + }) + .then(() => setSubmitting(false)); + }; return ( - setVisible(false)}> -

Testing

-
+ + { + ({ isSubmitting, resetForm }) => ( + { + resetForm(); + setVisible(false); + }} + > + +

Create new database

+
+ +
+ +
+
+ + +
+ +
+ ) + } +
); diff --git a/resources/scripts/components/server/databases/DatabaseRow.tsx b/resources/scripts/components/server/databases/DatabaseRow.tsx index 7e5150e9..b385d594 100644 --- a/resources/scripts/components/server/databases/DatabaseRow.tsx +++ b/resources/scripts/components/server/databases/DatabaseRow.tsx @@ -4,10 +4,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faDatabase } from '@fortawesome/free-solid-svg-icons/faDatabase'; import { faTrashAlt } from '@fortawesome/free-solid-svg-icons/faTrashAlt'; import { faEye } from '@fortawesome/free-solid-svg-icons/faEye'; +import classNames from 'classnames'; -export default ({ database }: { database: ServerDatabase }) => { +export default ({ database, className }: { database: ServerDatabase; className?: string }) => { return ( -
+
diff --git a/resources/scripts/components/server/databases/DatabasesContainer.tsx b/resources/scripts/components/server/databases/DatabasesContainer.tsx index 680efc39..66b14e99 100644 --- a/resources/scripts/components/server/databases/DatabasesContainer.tsx +++ b/resources/scripts/components/server/databases/DatabasesContainer.tsx @@ -40,14 +40,18 @@ export default () => { {databases.length > 0 ? - databases.map(database => ) + databases.map((database, index) => 0 ? 'mt-1' : undefined} + />) :

It looks like you have no databases. Click the button below to create one now.

}
- setDatabases(s => [...s, database])}/> + setDatabases(s => [ ...s, database ])}/>
diff --git a/resources/styles/components/modal.css b/resources/styles/components/modal.css index 650a7ec8..7c3a6ae9 100644 --- a/resources/styles/components/modal.css +++ b/resources/styles/components/modal.css @@ -1,6 +1,6 @@ .modal-mask { @apply .fixed .pin .z-50 .overflow-auto .flex; - background: rgba(0, 0, 0, 0.7); + background: rgba(0, 0, 0, 0.70); transition: opacity 250ms ease; & > .modal-container { @@ -22,7 +22,7 @@ } & > .modal-content { - @apply .bg-neutral-900 .rounded .shadow-md; + @apply .bg-neutral-800 .rounded .shadow-md; transition: all 250ms ease; } diff --git a/webpack.config.js b/webpack.config.js index 0054d91d..10bdb1c2 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -135,7 +135,7 @@ module.exports = { }, plugins: plugins, optimization: { - minimize: true, + minimize: isProduction, minimizer: [ new TerserPlugin({ cache: true,