Skip to content

Commit 1f763dc

Browse files
committed
Finish support for creating databases in the UI
1 parent 61dc864 commit 1f763dc

File tree

7 files changed

+136
-12
lines changed

7 files changed

+136
-12
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { rawDataToServerDatabase, ServerDatabase } from '@/api/server/getServerDatabases';
2+
import http from '@/api/http';
3+
4+
export default (uuid: string, data: { connectionsFrom: string; databaseName: string }): Promise<ServerDatabase> => {
5+
return new Promise((resolve, reject) => {
6+
http.post(`/api/client/servers/${uuid}/databases`, {
7+
database: data.databaseName,
8+
remote: data.connectionsFrom,
9+
}, {
10+
params: { include: 'password' },
11+
})
12+
.then(response => resolve(rawDataToServerDatabase(response.data.attributes)))
13+
.catch(reject);
14+
});
15+
};

resources/scripts/components/elements/Modal.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ import React, { useEffect, useState } from 'react';
22
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
33
import { faTimes } from '@fortawesome/free-solid-svg-icons/faTimes';
44
import { CSSTransition } from 'react-transition-group';
5+
import Spinner from '@/components/elements/Spinner';
56

67
interface Props {
78
visible: boolean;
89
onDismissed: () => void;
910
dismissable?: boolean;
1011
closeOnEscape?: boolean;
1112
closeOnBackground?: boolean;
12-
children: React.ReactChild;
13+
showSpinnerOverlay?: boolean;
14+
children: React.ReactNode;
1315
}
1416

1517
export default (props: Props) => {
@@ -51,6 +53,14 @@ export default (props: Props) => {
5153
<FontAwesomeIcon icon={faTimes}/>
5254
</div>
5355
}
56+
{props.showSpinnerOverlay &&
57+
<div
58+
className={'absolute w-full h-full rounded flex items-center justify-center'}
59+
style={{ background: 'hsla(211, 10%, 53%, 0.25)' }}
60+
>
61+
<Spinner large={false}/>
62+
</div>
63+
}
5464
<div className={'modal-content p-6'}>
5565
{props.children}
5666
</div>

resources/scripts/components/server/databases/CreateDatabaseButton.tsx

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,111 @@
11
import React, { useState } from 'react';
22
import { ServerDatabase } from '@/api/server/getServerDatabases';
33
import Modal from '@/components/elements/Modal';
4+
import { Form, Formik, FormikActions } from 'formik';
5+
import Field from '@/components/elements/Field';
6+
import { object, string } from 'yup';
7+
import createServerDatabase from '@/api/server/createServerDatabase';
8+
import { ServerContext } from '@/state/server';
9+
import { Actions, useStoreActions } from 'easy-peasy';
10+
import { ApplicationStore } from '@/state';
11+
import { httpErrorToHuman } from '@/api/http';
12+
import FlashMessageRender from '@/components/FlashMessageRender';
13+
14+
interface Values {
15+
databaseName: string;
16+
connectionsFrom: string;
17+
}
18+
19+
const schema = object().shape({
20+
databaseName: string()
21+
.required('A database name must be provided.')
22+
.min(5, 'Database name must be at least 5 characters.')
23+
.max(64, 'Database name must not exceed 64 characters.')
24+
.matches(/^[A-Za-z0-9_\-.]{5,64}$/, 'Database name should only contain alphanumeric characters, underscores, dashes, and/or periods.'),
25+
connectionsFrom: string()
26+
.required('A connection value must be provided.')
27+
.matches(/^([1-9]{1,3}|%)(\.([0-9]{1,3}|%))?(\.([0-9]{1,3}|%))?(\.([0-9]{1,3}|%))?$/, 'A valid connection address must be provided.'),
28+
});
429

530
export default ({ onCreated }: { onCreated: (database: ServerDatabase) => void }) => {
631
const [ visible, setVisible ] = useState(false);
32+
const { addFlash, clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
33+
const server = ServerContext.useStoreState(state => state.server.data!);
34+
35+
const submit = (values: Values, { setSubmitting }: FormikActions<Values>) => {
36+
clearFlashes();
37+
createServerDatabase(server.uuid, { ...values })
38+
.then(database => {
39+
onCreated(database);
40+
setVisible(false);
41+
})
42+
.catch(error => {
43+
console.log(error);
44+
addFlash({
45+
key: 'create-database-modal',
46+
type: 'error',
47+
title: 'Error',
48+
message: httpErrorToHuman(error),
49+
});
50+
})
51+
.then(() => setSubmitting(false));
52+
};
753

854
return (
955
<React.Fragment>
10-
<Modal visible={visible} onDismissed={() => setVisible(false)}>
11-
<p>Testing</p>
12-
</Modal>
56+
<Formik
57+
onSubmit={submit}
58+
initialValues={{ databaseName: '', connectionsFrom: '%' }}
59+
validationSchema={schema}
60+
>
61+
{
62+
({ isSubmitting, resetForm }) => (
63+
<Modal
64+
visible={visible}
65+
dismissable={!isSubmitting}
66+
showSpinnerOverlay={isSubmitting}
67+
onDismissed={() => {
68+
resetForm();
69+
setVisible(false);
70+
}}
71+
>
72+
<FlashMessageRender byKey={'create-database-modal'}/>
73+
<h3 className={'mb-6'}>Create new database</h3>
74+
<Form className={'m-0'}>
75+
<Field
76+
type={'string'}
77+
id={'database_name'}
78+
name={'databaseName'}
79+
label={'Database Name'}
80+
description={'A descriptive name for your database instance.'}
81+
/>
82+
<div className={'mt-6'}>
83+
<Field
84+
type={'string'}
85+
id={'connections_from'}
86+
name={'connectionsFrom'}
87+
label={'Connections From'}
88+
description={'Where connections should be allowed from. Use % for wildcards.'}
89+
/>
90+
</div>
91+
<div className={'mt-6 text-right'}>
92+
<button
93+
className={'btn btn-sm btn-secondary mr-2'}
94+
onClick={() => setVisible(false)}
95+
>
96+
Cancel
97+
</button>
98+
<button className={'btn btn-sm btn-primary'} type={'submit'}>
99+
Create Database
100+
</button>
101+
</div>
102+
</Form>
103+
</Modal>
104+
)
105+
}
106+
</Formik>
13107
<button className={'btn btn-primary btn-lg'} onClick={() => setVisible(true)}>
14-
Create Database
108+
New Database
15109
</button>
16110
</React.Fragment>
17111
);

resources/scripts/components/server/databases/DatabaseRow.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
44
import { faDatabase } from '@fortawesome/free-solid-svg-icons/faDatabase';
55
import { faTrashAlt } from '@fortawesome/free-solid-svg-icons/faTrashAlt';
66
import { faEye } from '@fortawesome/free-solid-svg-icons/faEye';
7+
import classNames from 'classnames';
78

8-
export default ({ database }: { database: ServerDatabase }) => {
9+
export default ({ database, className }: { database: ServerDatabase; className?: string }) => {
910
return (
10-
<div className={'grey-row-box no-hover'}>
11+
<div className={classNames('grey-row-box no-hover', className)}>
1112
<div className={'icon'}>
1213
<FontAwesomeIcon icon={faDatabase}/>
1314
</div>

resources/scripts/components/server/databases/DatabasesContainer.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,18 @@ export default () => {
4040
<CSSTransition classNames={'fade'} timeout={250}>
4141
<React.Fragment>
4242
{databases.length > 0 ?
43-
databases.map(database => <DatabaseRow key={database.id} database={database}/>)
43+
databases.map((database, index) => <DatabaseRow
44+
key={database.id}
45+
database={database}
46+
className={index > 0 ? 'mt-1' : undefined}
47+
/>)
4448
:
4549
<p className={'text-center text-sm text-neutral-200'}>
4650
It looks like you have no databases. Click the button below to create one now.
4751
</p>
4852
}
4953
<div className={'mt-6 flex justify-end'}>
50-
<CreateDatabaseButton onCreated={database => setDatabases(s => [...s, database])}/>
54+
<CreateDatabaseButton onCreated={database => setDatabases(s => [ ...s, database ])}/>
5155
</div>
5256
</React.Fragment>
5357
</CSSTransition>

resources/styles/components/modal.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.modal-mask {
22
@apply .fixed .pin .z-50 .overflow-auto .flex;
3-
background: rgba(0, 0, 0, 0.7);
3+
background: rgba(0, 0, 0, 0.70);
44
transition: opacity 250ms ease;
55

66
& > .modal-container {
@@ -22,7 +22,7 @@
2222
}
2323

2424
& > .modal-content {
25-
@apply .bg-neutral-900 .rounded .shadow-md;
25+
@apply .bg-neutral-800 .rounded .shadow-md;
2626
transition: all 250ms ease;
2727
}
2828

webpack.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ module.exports = {
135135
},
136136
plugins: plugins,
137137
optimization: {
138-
minimize: true,
138+
minimize: isProduction,
139139
minimizer: [
140140
new TerserPlugin({
141141
cache: true,

0 commit comments

Comments
 (0)