Support deleting existing databases
This commit is contained in:
parent
1f763dc155
commit
d081f328ab
7 changed files with 150 additions and 40 deletions
9
resources/scripts/api/server/deleteServerDatabase.ts
Normal file
9
resources/scripts/api/server/deleteServerDatabase.ts
Normal 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);
|
||||||
|
});
|
||||||
|
};
|
|
@ -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}>
|
||||||
|
|
|
@ -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)}
|
||||||
>
|
>
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -13,5 +13,5 @@ h1, h2, h3, h4, h5, h6 {
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@apply .text-neutral-200;
|
@apply .text-neutral-200 .leading-snug;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue