Skip to content

Commit d081f32

Browse files
committed
Support deleting existing databases
1 parent 1f763dc commit d081f32

File tree

7 files changed

+150
-40
lines changed

7 files changed

+150
-40
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import http from '@/api/http';
2+
3+
export default (uuid: string, database: string): Promise<void> => {
4+
return new Promise((resolve, reject) => {
5+
http.delete(`/api/client/servers/${uuid}/databases/${database}`)
6+
.then(() => resolve())
7+
.catch(reject);
8+
});
9+
};

resources/scripts/components/FlashMessageRender.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import { ApplicationStore } from '@/state';
66
type Props = Readonly<{
77
byKey?: string;
88
spacerClass?: string;
9-
withBottomSpace?: boolean;
9+
className?: string;
1010
}>;
1111

12-
export default ({ withBottomSpace, spacerClass, byKey }: Props) => {
12+
export default ({ className, spacerClass, byKey }: Props) => {
1313
const flashes = useStoreState((state: State<ApplicationStore>) => state.flashes.items);
1414

1515
let filtered = flashes;
@@ -21,9 +21,8 @@ export default ({ withBottomSpace, spacerClass, byKey }: Props) => {
2121
return null;
2222
}
2323

24-
// noinspection PointlessBooleanExpressionJS
2524
return (
26-
<div className={withBottomSpace === false ? undefined : 'mb-2'}>
25+
<div className={className}>
2726
{
2827
filtered.map((flash, index) => (
2928
<React.Fragment key={flash.id || flash.type + flash.message}>

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export default ({ onCreated }: { onCreated: (database: ServerDatabase) => void }
6969
setVisible(false);
7070
}}
7171
>
72-
<FlashMessageRender byKey={'create-database-modal'}/>
72+
<FlashMessageRender byKey={'create-database-modal'} className={'mb-6'}/>
7373
<h3 className={'mb-6'}>Create new database</h3>
7474
<Form className={'m-0'}>
7575
<Field
@@ -90,6 +90,7 @@ export default ({ onCreated }: { onCreated: (database: ServerDatabase) => void }
9090
</div>
9191
<div className={'mt-6 text-right'}>
9292
<button
93+
type={'button'}
9394
className={'btn btn-sm btn-secondary mr-2'}
9495
onClick={() => setVisible(false)}
9596
>
Lines changed: 125 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,136 @@
1-
import React from 'react';
1+
import React, { useState } from 'react';
22
import { ServerDatabase } from '@/api/server/getServerDatabases';
33
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';
77
import classNames from 'classnames';
8+
import Modal from '@/components/elements/Modal';
9+
import { Form, Formik, FormikActions } from 'formik';
10+
import Field from '@/components/elements/Field';
11+
import { object, string } from 'yup';
12+
import FlashMessageRender from '@/components/FlashMessageRender';
13+
import { Actions, useStoreActions } from 'easy-peasy';
14+
import { ApplicationStore } from '@/state';
15+
import { ServerContext } from '@/state/server';
16+
import deleteServerDatabase from '@/api/server/deleteServerDatabase';
17+
import { httpErrorToHuman } from '@/api/http';
18+
19+
interface Props {
20+
database: ServerDatabase;
21+
className?: string;
22+
onDelete: () => void;
23+
}
24+
25+
export default ({ database, className, onDelete }: Props) => {
26+
const [visible, setVisible] = useState(false);
27+
const { addFlash, clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
28+
const server = ServerContext.useStoreState(state => state.server.data!);
29+
30+
const schema = object().shape({
31+
confirm: string()
32+
.required('The database name must be provided.')
33+
.oneOf([database.name.split('_', 2)[1], database.name], 'The database name must be provided.'),
34+
});
35+
36+
const submit = (values: { confirm: string }, { setSubmitting }: FormikActions<{ confirm: string }>) => {
37+
clearFlashes();
38+
deleteServerDatabase(server.uuid, database.id)
39+
.then(() => {
40+
setVisible(false);
41+
setTimeout(() => onDelete(), 150);
42+
})
43+
.catch(error => {
44+
console.error(error);
45+
setSubmitting(false);
46+
addFlash({
47+
key: 'delete-database-modal',
48+
type: 'error',
49+
title: 'Error',
50+
message: httpErrorToHuman(error),
51+
});
52+
});
53+
};
854

9-
export default ({ database, className }: { database: ServerDatabase; className?: string }) => {
1055
return (
11-
<div className={classNames('grey-row-box no-hover', className)}>
12-
<div className={'icon'}>
13-
<FontAwesomeIcon icon={faDatabase}/>
14-
</div>
15-
<div className={'flex-1 ml-4'}>
16-
<p className={'text-lg'}>{database.name}</p>
17-
</div>
18-
<div className={'ml-6'}>
19-
<p className={'text-center text-xs text-neutral-500 uppercase mb-1 select-none'}>Endpoint:</p>
20-
<p className={'text-center text-sm'}>{database.connectionString}</p>
21-
</div>
22-
<div className={'ml-6'}>
23-
<p className={'text-center text-xs text-neutral-500 uppercase mb-1 select-none'}>Connections From:</p>
24-
<p className={'text-center text-sm'}>{database.allowConnectionsFrom}</p>
25-
</div>
26-
<div className={'ml-6'}>
27-
<p className={'text-center text-xs text-neutral-500 uppercase mb-1 select-none'}>Username:</p>
28-
<p className={'text-center text-sm'}>{database.username}</p>
29-
</div>
30-
<div className={'ml-6'}>
31-
<button className={'btn btn-sm btn-secondary mr-2'}>
32-
<FontAwesomeIcon icon={faEye} fixedWidth={true}/>
33-
</button>
34-
<button className={'btn btn-sm btn-secondary btn-red'}>
35-
<FontAwesomeIcon icon={faTrashAlt} fixedWidth={true}/>
36-
</button>
56+
<React.Fragment>
57+
<Formik
58+
onSubmit={submit}
59+
initialValues={{ confirm: '' }}
60+
validationSchema={schema}
61+
>
62+
{
63+
({ isSubmitting, isValid, resetForm }) => (
64+
<Modal
65+
visible={visible}
66+
dismissable={!isSubmitting}
67+
showSpinnerOverlay={isSubmitting}
68+
onDismissed={() => { setVisible(false); resetForm(); }}
69+
>
70+
<FlashMessageRender byKey={'delete-database-modal'} className={'mb-6'}/>
71+
<h3 className={'mb-6'}>Confirm database deletion</h3>
72+
<p className={'text-sm'}>
73+
Deleting a database is a permanent action, it cannot be undone. This will permanetly
74+
delete the <strong>{database.name}</strong> database and remove all associated data.
75+
</p>
76+
<Form className={'m-0 mt-6'}>
77+
<Field
78+
type={'text'}
79+
id={'confirm_name'}
80+
name={'confirm'}
81+
label={'Confirm Database Name'}
82+
description={'Enter the database name to confirm deletion.'}
83+
/>
84+
<div className={'mt-6 text-right'}>
85+
<button
86+
type={'button'}
87+
className={'btn btn-sm btn-secondary mr-2'}
88+
onClick={() => setVisible(false)}
89+
>
90+
Cancel
91+
</button>
92+
<button
93+
type={'submit'}
94+
className={'btn btn-sm btn-red'}
95+
disabled={!isValid}
96+
>
97+
Delete Database
98+
</button>
99+
</div>
100+
</Form>
101+
</Modal>
102+
)
103+
}
104+
</Formik>
105+
<div className={classNames('grey-row-box no-hover', className)}>
106+
<div className={'icon'}>
107+
<FontAwesomeIcon icon={faDatabase}/>
108+
</div>
109+
<div className={'flex-1 ml-4'}>
110+
<p className={'text-lg'}>{database.name}</p>
111+
</div>
112+
<div className={'ml-6'}>
113+
<p className={'text-center text-xs text-neutral-500 uppercase mb-1 select-none'}>Endpoint:</p>
114+
<p className={'text-center text-sm'}>{database.connectionString}</p>
115+
</div>
116+
<div className={'ml-6'}>
117+
<p className={'text-center text-xs text-neutral-500 uppercase mb-1 select-none'}>Connections
118+
From:</p>
119+
<p className={'text-center text-sm'}>{database.allowConnectionsFrom}</p>
120+
</div>
121+
<div className={'ml-6'}>
122+
<p className={'text-center text-xs text-neutral-500 uppercase mb-1 select-none'}>Username:</p>
123+
<p className={'text-center text-sm'}>{database.username}</p>
124+
</div>
125+
<div className={'ml-6'}>
126+
<button className={'btn btn-sm btn-secondary mr-2'}>
127+
<FontAwesomeIcon icon={faEye} fixedWidth={true}/>
128+
</button>
129+
<button className={'btn btn-sm btn-secondary btn-red'} onClick={() => setVisible(true)}>
130+
<FontAwesomeIcon icon={faTrashAlt} fixedWidth={true}/>
131+
</button>
132+
</div>
37133
</div>
38-
</div>
134+
</React.Fragment>
39135
);
40136
};

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,14 @@ export default () => {
4040
<CSSTransition classNames={'fade'} timeout={250}>
4141
<React.Fragment>
4242
{databases.length > 0 ?
43-
databases.map((database, index) => <DatabaseRow
44-
key={database.id}
45-
database={database}
46-
className={index > 0 ? 'mt-1' : undefined}
47-
/>)
43+
databases.map((database, index) => (
44+
<DatabaseRow
45+
key={database.id}
46+
database={database}
47+
onDelete={() => setDatabases(s => [ ...s.filter(d => d.id !== database.id) ])}
48+
className={index > 0 ? 'mt-1' : undefined}
49+
/>
50+
))
4851
:
4952
<p className={'text-center text-sm text-neutral-200'}>
5053
It looks like you have no databases. Click the button below to create one now.

resources/styles/components/typography.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ h1, h2, h3, h4, h5, h6 {
1313
}
1414

1515
p {
16-
@apply .text-neutral-200;
16+
@apply .text-neutral-200 .leading-snug;
1717
}

tailwind.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,9 @@ module.exports = {
290290
leading: {
291291
'none': 1,
292292
'tight': 1.25,
293+
'snug': 1.375,
293294
'normal': 1.5,
295+
'relaxed': 1.625,
294296
'loose': 2,
295297
},
296298

0 commit comments

Comments
 (0)