Support deleting existing databases

This commit is contained in:
Dane Everitt 2019-07-16 22:15:14 -07:00
parent 1f763dc155
commit d081f328ab
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
7 changed files with 150 additions and 40 deletions

View file

@ -0,0 +1,9 @@
import http from '@/api/http';
export default (uuid: string, database: string): Promise<void> => {
return new Promise((resolve, reject) => {
http.delete(`/api/client/servers/${uuid}/databases/${database}`)
.then(() => resolve())
.catch(reject);
});
};

View file

@ -6,10 +6,10 @@ import { ApplicationStore } from '@/state';
type Props = Readonly<{ type Props = Readonly<{
byKey?: string; byKey?: string;
spacerClass?: string; spacerClass?: string;
withBottomSpace?: boolean; className?: string;
}>; }>;
export default ({ withBottomSpace, spacerClass, byKey }: Props) => { export default ({ className, spacerClass, byKey }: Props) => {
const flashes = useStoreState((state: State<ApplicationStore>) => state.flashes.items); const flashes = useStoreState((state: State<ApplicationStore>) => state.flashes.items);
let filtered = flashes; let filtered = flashes;
@ -21,9 +21,8 @@ export default ({ withBottomSpace, spacerClass, byKey }: Props) => {
return null; return null;
} }
// noinspection PointlessBooleanExpressionJS
return ( return (
<div className={withBottomSpace === false ? undefined : 'mb-2'}> <div className={className}>
{ {
filtered.map((flash, index) => ( filtered.map((flash, index) => (
<React.Fragment key={flash.id || flash.type + flash.message}> <React.Fragment key={flash.id || flash.type + flash.message}>

View file

@ -69,7 +69,7 @@ export default ({ onCreated }: { onCreated: (database: ServerDatabase) => void }
setVisible(false); setVisible(false);
}} }}
> >
<FlashMessageRender byKey={'create-database-modal'}/> <FlashMessageRender byKey={'create-database-modal'} className={'mb-6'}/>
<h3 className={'mb-6'}>Create new database</h3> <h3 className={'mb-6'}>Create new database</h3>
<Form className={'m-0'}> <Form className={'m-0'}>
<Field <Field
@ -90,6 +90,7 @@ export default ({ onCreated }: { onCreated: (database: ServerDatabase) => void }
</div> </div>
<div className={'mt-6 text-right'}> <div className={'mt-6 text-right'}>
<button <button
type={'button'}
className={'btn btn-sm btn-secondary mr-2'} className={'btn btn-sm btn-secondary mr-2'}
onClick={() => setVisible(false)} onClick={() => setVisible(false)}
> >

View file

@ -1,40 +1,136 @@
import React from 'react'; import React, { useState } from 'react';
import { ServerDatabase } from '@/api/server/getServerDatabases'; import { ServerDatabase } from '@/api/server/getServerDatabases';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faDatabase } from '@fortawesome/free-solid-svg-icons/faDatabase'; import { faDatabase } from '@fortawesome/free-solid-svg-icons/faDatabase';
import { faTrashAlt } from '@fortawesome/free-solid-svg-icons/faTrashAlt'; import { faTrashAlt } from '@fortawesome/free-solid-svg-icons/faTrashAlt';
import { faEye } from '@fortawesome/free-solid-svg-icons/faEye'; import { faEye } from '@fortawesome/free-solid-svg-icons/faEye';
import classNames from 'classnames'; import classNames from 'classnames';
import Modal from '@/components/elements/Modal';
import { Form, Formik, FormikActions } 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';
interface Props {
database: ServerDatabase;
className?: string;
onDelete: () => void;
}
export default ({ database, className, onDelete }: Props) => {
const [visible, setVisible] = useState(false);
const { addFlash, clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
const server = ServerContext.useStoreState(state => state.server.data!);
const schema = object().shape({
confirm: string()
.required('The database name must be provided.')
.oneOf([database.name.split('_', 2)[1], database.name], 'The database name must be provided.'),
});
const submit = (values: { confirm: string }, { setSubmitting }: FormikActions<{ confirm: string }>) => {
clearFlashes();
deleteServerDatabase(server.uuid, database.id)
.then(() => {
setVisible(false);
setTimeout(() => onDelete(), 150);
})
.catch(error => {
console.error(error);
setSubmitting(false);
addFlash({
key: 'delete-database-modal',
type: 'error',
title: 'Error',
message: httpErrorToHuman(error),
});
});
};
export default ({ database, className }: { database: ServerDatabase; className?: string }) => {
return ( return (
<div className={classNames('grey-row-box no-hover', className)}> <React.Fragment>
<div className={'icon'}> <Formik
<FontAwesomeIcon icon={faDatabase}/> onSubmit={submit}
initialValues={{ confirm: '' }}
validationSchema={schema}
>
{
({ isSubmitting, isValid, resetForm }) => (
<Modal
visible={visible}
dismissable={!isSubmitting}
showSpinnerOverlay={isSubmitting}
onDismissed={() => { setVisible(false); resetForm(); }}
>
<FlashMessageRender byKey={'delete-database-modal'} 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
delete the <strong>{database.name}</strong> database and remove all associated data.
</p>
<Form className={'m-0 mt-6'}>
<Field
type={'text'}
id={'confirm_name'}
name={'confirm'}
label={'Confirm Database Name'}
description={'Enter the database name to confirm deletion.'}
/>
<div className={'mt-6 text-right'}>
<button
type={'button'}
className={'btn btn-sm btn-secondary mr-2'}
onClick={() => setVisible(false)}
>
Cancel
</button>
<button
type={'submit'}
className={'btn btn-sm btn-red'}
disabled={!isValid}
>
Delete Database
</button>
</div>
</Form>
</Modal>
)
}
</Formik>
<div className={classNames('grey-row-box no-hover', className)}>
<div className={'icon'}>
<FontAwesomeIcon icon={faDatabase}/>
</div>
<div className={'flex-1 ml-4'}>
<p className={'text-lg'}>{database.name}</p>
</div>
<div className={'ml-6'}>
<p className={'text-center text-xs text-neutral-500 uppercase mb-1 select-none'}>Endpoint:</p>
<p className={'text-center text-sm'}>{database.connectionString}</p>
</div>
<div className={'ml-6'}>
<p className={'text-center text-xs text-neutral-500 uppercase mb-1 select-none'}>Connections
From:</p>
<p className={'text-center text-sm'}>{database.allowConnectionsFrom}</p>
</div>
<div className={'ml-6'}>
<p className={'text-center text-xs text-neutral-500 uppercase mb-1 select-none'}>Username:</p>
<p className={'text-center text-sm'}>{database.username}</p>
</div>
<div className={'ml-6'}>
<button className={'btn btn-sm btn-secondary mr-2'}>
<FontAwesomeIcon icon={faEye} fixedWidth={true}/>
</button>
<button className={'btn btn-sm btn-secondary btn-red'} onClick={() => setVisible(true)}>
<FontAwesomeIcon icon={faTrashAlt} fixedWidth={true}/>
</button>
</div>
</div> </div>
<div className={'flex-1 ml-4'}> </React.Fragment>
<p className={'text-lg'}>{database.name}</p>
</div>
<div className={'ml-6'}>
<p className={'text-center text-xs text-neutral-500 uppercase mb-1 select-none'}>Endpoint:</p>
<p className={'text-center text-sm'}>{database.connectionString}</p>
</div>
<div className={'ml-6'}>
<p className={'text-center text-xs text-neutral-500 uppercase mb-1 select-none'}>Connections From:</p>
<p className={'text-center text-sm'}>{database.allowConnectionsFrom}</p>
</div>
<div className={'ml-6'}>
<p className={'text-center text-xs text-neutral-500 uppercase mb-1 select-none'}>Username:</p>
<p className={'text-center text-sm'}>{database.username}</p>
</div>
<div className={'ml-6'}>
<button className={'btn btn-sm btn-secondary mr-2'}>
<FontAwesomeIcon icon={faEye} fixedWidth={true}/>
</button>
<button className={'btn btn-sm btn-secondary btn-red'}>
<FontAwesomeIcon icon={faTrashAlt} fixedWidth={true}/>
</button>
</div>
</div>
); );
}; };

View file

@ -40,11 +40,14 @@ export default () => {
<CSSTransition classNames={'fade'} timeout={250}> <CSSTransition classNames={'fade'} timeout={250}>
<React.Fragment> <React.Fragment>
{databases.length > 0 ? {databases.length > 0 ?
databases.map((database, index) => <DatabaseRow databases.map((database, index) => (
key={database.id} <DatabaseRow
database={database} key={database.id}
className={index > 0 ? 'mt-1' : undefined} database={database}
/>) onDelete={() => setDatabases(s => [ ...s.filter(d => d.id !== database.id) ])}
className={index > 0 ? 'mt-1' : undefined}
/>
))
: :
<p className={'text-center text-sm text-neutral-200'}> <p className={'text-center text-sm text-neutral-200'}>
It looks like you have no databases. Click the button below to create one now. It looks like you have no databases. Click the button below to create one now.

View file

@ -13,5 +13,5 @@ h1, h2, h3, h4, h5, h6 {
} }
p { p {
@apply .text-neutral-200; @apply .text-neutral-200 .leading-snug;
} }

View file

@ -290,7 +290,9 @@ module.exports = {
leading: { leading: {
'none': 1, 'none': 1,
'tight': 1.25, 'tight': 1.25,
'snug': 1.375,
'normal': 1.5, 'normal': 1.5,
'relaxed': 1.625,
'loose': 2, 'loose': 2,
}, },