Update database screens

This commit is contained in:
Dane Everitt 2020-07-04 17:15:49 -07:00
parent a288374027
commit 7e8a5f1271
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
5 changed files with 86 additions and 76 deletions

View file

@ -1,12 +1,10 @@
import styled from 'styled-components/macro'; import styled from 'styled-components/macro';
import tw from 'twin.macro'; import tw from 'twin.macro';
export default styled.div` export default styled.div<{ $hoverable?: boolean }>`
${tw`flex rounded no-underline text-neutral-200 items-center bg-neutral-700 p-4 border border-transparent transition-colors duration-150`}; ${tw`flex rounded no-underline text-neutral-200 items-center bg-neutral-700 p-4 border border-transparent transition-colors duration-150`};
&:not(.no-hover):hover { ${props => props.$hoverable !== false && tw`hover:border-neutral-500`};
${tw`border-neutral-500`};
}
& > div.icon { & > div.icon {
${tw`rounded-full bg-neutral-500 p-3`}; ${tw`rounded-full bg-neutral-500 p-3`};

View file

@ -9,6 +9,8 @@ import { httpErrorToHuman } from '@/api/http';
import FlashMessageRender from '@/components/FlashMessageRender'; import FlashMessageRender from '@/components/FlashMessageRender';
import useFlash from '@/plugins/useFlash'; import useFlash from '@/plugins/useFlash';
import useServer from '@/plugins/useServer'; import useServer from '@/plugins/useServer';
import Button from '@/components/elements/Button';
import tw from 'twin.macro';
interface Values { interface Values {
databaseName: string; databaseName: string;
@ -48,7 +50,7 @@ export default () => {
}; };
return ( return (
<React.Fragment> <>
<Formik <Formik
onSubmit={submit} onSubmit={submit}
initialValues={{ databaseName: '', connectionsFrom: '%' }} initialValues={{ databaseName: '', connectionsFrom: '%' }}
@ -65,9 +67,9 @@ export default () => {
setVisible(false); setVisible(false);
}} }}
> >
<FlashMessageRender byKey={'database:create'} className={'mb-6'}/> <FlashMessageRender byKey={'database:create'} css={tw`mb-6`}/>
<h3 className={'mb-6'}>Create new database</h3> <h2 css={tw`text-2xl mb-6`}>Create new database</h2>
<Form className={'m-0'}> <Form css={tw`m-0`}>
<Field <Field
type={'string'} type={'string'}
id={'database_name'} id={'database_name'}
@ -75,7 +77,7 @@ export default () => {
label={'Database Name'} label={'Database Name'}
description={'A descriptive name for your database instance.'} description={'A descriptive name for your database instance.'}
/> />
<div className={'mt-6'}> <div css={tw`mt-6`}>
<Field <Field
type={'string'} type={'string'}
id={'connections_from'} id={'connections_from'}
@ -84,26 +86,27 @@ export default () => {
description={'Where connections should be allowed from. Use % for wildcards.'} description={'Where connections should be allowed from. Use % for wildcards.'}
/> />
</div> </div>
<div className={'mt-6 text-right'}> <div css={tw`mt-6 text-right`}>
<button <Button
type={'button'} type={'button'}
className={'btn btn-sm btn-secondary mr-2'} isSecondary
css={tw`mr-2`}
onClick={() => setVisible(false)} onClick={() => setVisible(false)}
> >
Cancel Cancel
</button> </Button>
<button className={'btn btn-sm btn-primary'} type={'submit'}> <Button type={'submit'}>
Create Database Create Database
</button> </Button>
</div> </div>
</Form> </Form>
</Modal> </Modal>
) )
} }
</Formik> </Formik>
<button className={'btn btn-primary btn-sm'} onClick={() => setVisible(true)}> <Button onClick={() => setVisible(true)}>
New Database New Database
</button> </Button>
</React.Fragment> </>
); );
}; };

View file

@ -17,6 +17,11 @@ import Can from '@/components/elements/Can';
import { ServerDatabase } from '@/api/server/getServerDatabases'; import { ServerDatabase } from '@/api/server/getServerDatabases';
import useServer from '@/plugins/useServer'; import useServer from '@/plugins/useServer';
import useFlash from '@/plugins/useFlash'; import useFlash from '@/plugins/useFlash';
import tw from 'twin.macro';
import Button from '@/components/elements/Button';
import Label from '@/components/elements/Label';
import Input from '@/components/elements/Input';
import GreyRowBox from '@/components/elements/GreyRowBox';
interface Props { interface Props {
database: ServerDatabase; database: ServerDatabase;
@ -51,13 +56,14 @@ export default ({ database, className }: Props) => {
addError({ key: 'database:delete', message: httpErrorToHuman(error) }); addError({ key: 'database:delete', message: httpErrorToHuman(error) });
}); });
}; };
return ( return (
<React.Fragment> <>
<Formik <Formik
onSubmit={submit} onSubmit={submit}
initialValues={{ confirm: '' }} initialValues={{ confirm: '' }}
validationSchema={schema} validationSchema={schema}
isInitialValid={false}
> >
{ {
({ isSubmitting, isValid, resetForm }) => ( ({ isSubmitting, isValid, resetForm }) => (
@ -70,13 +76,13 @@ export default ({ database, className }: Props) => {
resetForm(); resetForm();
}} }}
> >
<FlashMessageRender byKey={'database:delete'} className={'mb-6'}/> <FlashMessageRender byKey={'database:delete'} css={tw`mb-6`}/>
<h3 className={'mb-6'}>Confirm database deletion</h3> <h2 css={tw`text-2xl mb-6`}>Confirm database deletion</h2>
<p className={'text-sm'}> <p css={tw`text-sm`}>
Deleting a database is a permanent action, it cannot be undone. This will permanetly 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. delete the <strong>{database.name}</strong> database and remove all associated data.
</p> </p>
<Form className={'m-0 mt-6'}> <Form css={tw`m-0 mt-6`}>
<Field <Field
type={'text'} type={'text'}
id={'confirm_name'} id={'confirm_name'}
@ -84,21 +90,22 @@ export default ({ database, className }: Props) => {
label={'Confirm Database Name'} label={'Confirm Database Name'}
description={'Enter the database name to confirm deletion.'} description={'Enter the database name to confirm deletion.'}
/> />
<div className={'mt-6 text-right'}> <div css={tw`mt-6 text-right`}>
<button <Button
type={'button'} type={'button'}
className={'btn btn-sm btn-secondary mr-2'} isSecondary
css={tw`mr-2`}
onClick={() => setVisible(false)} onClick={() => setVisible(false)}
> >
Cancel Cancel
</button> </Button>
<button <Button
type={'submit'} type={'submit'}
className={'btn btn-sm btn-red'} color={'red'}
disabled={!isValid} disabled={!isValid}
> >
Delete Database Delete Database
</button> </Button>
</div> </div>
</Form> </Form>
</Modal> </Modal>
@ -106,62 +113,61 @@ export default ({ database, className }: Props) => {
} }
</Formik> </Formik>
<Modal visible={connectionVisible} onDismissed={() => setConnectionVisible(false)}> <Modal visible={connectionVisible} onDismissed={() => setConnectionVisible(false)}>
<FlashMessageRender byKey={'database-connection-modal'} className={'mb-6'}/> <FlashMessageRender byKey={'database-connection-modal'} css={tw`mb-6`}/>
<h3 className={'mb-6'}>Database connection details</h3> <h3 css={tw`mb-6`}>Database connection details</h3>
<Can action={'database.view_password'}> <Can action={'database.view_password'}>
<div> <div>
<label className={'input-dark-label'}>Password</label> <Label>Password</Label>
<input type={'text'} className={'input-dark'} readOnly={true} value={database.password}/> <Input type={'text'} readOnly value={database.password}/>
</div> </div>
</Can> </Can>
<div className={'mt-6'}> <div css={tw`mt-6`}>
<label className={'input-dark-label'}>JBDC Connection String</label> <Label>JBDC Connection String</Label>
<input <Input
type={'text'} type={'text'}
className={'input-dark'} readOnly
readOnly={true}
value={`jdbc:mysql://${database.username}:${database.password}@${database.connectionString}/${database.name}`} value={`jdbc:mysql://${database.username}:${database.password}@${database.connectionString}/${database.name}`}
/> />
</div> </div>
<div className={'mt-6 text-right'}> <div css={tw`mt-6 text-right`}>
<Can action={'database.update'}> <Can action={'database.update'}>
<RotatePasswordButton databaseId={database.id} onUpdate={appendDatabase}/> <RotatePasswordButton databaseId={database.id} onUpdate={appendDatabase}/>
</Can> </Can>
<button className={'btn btn-sm btn-secondary'} onClick={() => setConnectionVisible(false)}> <Button isSecondary onClick={() => setConnectionVisible(false)}>
Close Close
</button> </Button>
</div> </div>
</Modal> </Modal>
<div className={classNames('grey-row-box no-hover', className)}> <GreyRowBox $hoverable={false} className={className}>
<div className={'icon'}> <div>
<FontAwesomeIcon icon={faDatabase} fixedWidth={true}/> <FontAwesomeIcon icon={faDatabase} fixedWidth/>
</div> </div>
<div className={'flex-1 ml-4'}> <div css={tw`flex-1 ml-4`}>
<p className={'text-lg'}>{database.name}</p> <p css={tw`text-lg`}>{database.name}</p>
</div> </div>
<div className={'ml-8 text-center'}> <div css={tw`ml-8 text-center`}>
<p className={'text-sm'}>{database.connectionString}</p> <p css={tw`text-sm`}>{database.connectionString}</p>
<p className={'mt-1 text-2xs text-neutral-500 uppercase select-none'}>Endpoint</p> <p css={tw`mt-1 text-2xs text-neutral-500 uppercase select-none`}>Endpoint</p>
</div> </div>
<div className={'ml-8 text-center'}> <div css={tw`ml-8 text-center`}>
<p className={'text-sm'}>{database.allowConnectionsFrom}</p> <p css={tw`text-sm`}>{database.allowConnectionsFrom}</p>
<p className={'mt-1 text-2xs text-neutral-500 uppercase select-none'}>Connections from</p> <p css={tw`mt-1 text-2xs text-neutral-500 uppercase select-none`}>Connections from</p>
</div> </div>
<div className={'ml-8 text-center'}> <div css={tw`ml-8 text-center`}>
<p className={'text-sm'}>{database.username}</p> <p css={tw`text-sm`}>{database.username}</p>
<p className={'mt-1 text-2xs text-neutral-500 uppercase select-none'}>Username</p> <p css={tw`mt-1 text-2xs text-neutral-500 uppercase select-none`}>Username</p>
</div> </div>
<div className={'ml-8'}> <div css={tw`ml-8`}>
<button className={'btn btn-sm btn-secondary mr-2'} onClick={() => setConnectionVisible(true)}> <Button isSecondary css={tw`mr-2`} onClick={() => setConnectionVisible(true)}>
<FontAwesomeIcon icon={faEye} fixedWidth={true}/> <FontAwesomeIcon icon={faEye} fixedWidth/>
</button> </Button>
<Can action={'database.delete'}> <Can action={'database.delete'}>
<button className={'btn btn-sm btn-secondary btn-red'} onClick={() => setVisible(true)}> <Button color={'red'} isSecondary onClick={() => setVisible(true)}>
<FontAwesomeIcon icon={faTrashAlt} fixedWidth={true}/> <FontAwesomeIcon icon={faTrashAlt} fixedWidth/>
</button> </Button>
</Can> </Can>
</div> </div>
</div> </GreyRowBox>
</React.Fragment> </>
); );
}; };

View file

@ -11,6 +11,8 @@ import Can from '@/components/elements/Can';
import useFlash from '@/plugins/useFlash'; import useFlash from '@/plugins/useFlash';
import useServer from '@/plugins/useServer'; import useServer from '@/plugins/useServer';
import PageContentBlock from '@/components/elements/PageContentBlock'; import PageContentBlock from '@/components/elements/PageContentBlock';
import tw from 'twin.macro';
import Fade from '@/components/elements/Fade';
export default () => { export default () => {
const { uuid, featureLimits } = useServer(); const { uuid, featureLimits } = useServer();
@ -35,11 +37,11 @@ export default () => {
return ( return (
<PageContentBlock> <PageContentBlock>
<FlashMessageRender byKey={'databases'} className={'mb-4'}/> <FlashMessageRender byKey={'databases'} css={tw`mb-4`}/>
{(!databases.length && loading) ? {(!databases.length && loading) ?
<Spinner size={'large'} centered={true}/> <Spinner size={'large'} centered/>
: :
<CSSTransition classNames={'fade'} timeout={250}> <Fade timeout={250}>
<> <>
{databases.length > 0 ? {databases.length > 0 ?
databases.map((database, index) => ( databases.map((database, index) => (
@ -50,28 +52,28 @@ export default () => {
/> />
)) ))
: :
<p className={'text-center text-sm text-neutral-400'}> <p css={tw`text-center text-sm text-neutral-400`}>
{featureLimits.databases > 0 ? {featureLimits.databases > 0 ?
`It looks like you have no databases.` 'It looks like you have no databases.'
: :
`Databases cannot be created for this server.` 'Databases cannot be created for this server.'
} }
</p> </p>
} }
<Can action={'database.create'}> <Can action={'database.create'}>
{(featureLimits.databases > 0 && databases.length > 0) && {(featureLimits.databases > 0 && databases.length > 0) &&
<p className="text-center text-xs text-neutral-400 mt-2"> <p css={tw`text-center text-xs text-neutral-400 mt-2`}>
{databases.length} of {featureLimits.databases} databases have been allocated to this server. {databases.length} of {featureLimits.databases} databases have been allocated to this server.
</p> </p>
} }
{featureLimits.databases > 0 && featureLimits.databases !== databases.length && {featureLimits.databases > 0 && featureLimits.databases !== databases.length &&
<div className={'mt-6 flex justify-end'}> <div css={tw`mt-6 flex justify-end`}>
<CreateDatabaseButton/> <CreateDatabaseButton/>
</div> </div>
} }
</Can> </Can>
</> </>
</CSSTransition> </Fade>
} }
</PageContentBlock> </PageContentBlock>
); );

View file

@ -6,6 +6,7 @@ import { ServerContext } from '@/state/server';
import { ServerDatabase } from '@/api/server/getServerDatabases'; import { ServerDatabase } from '@/api/server/getServerDatabases';
import { httpErrorToHuman } from '@/api/http'; import { httpErrorToHuman } from '@/api/http';
import Button from '@/components/elements/Button'; import Button from '@/components/elements/Button';
import tw from 'twin.macro';
export default ({ databaseId, onUpdate }: { export default ({ databaseId, onUpdate }: {
databaseId: string; databaseId: string;
@ -38,7 +39,7 @@ export default ({ databaseId, onUpdate }: {
}; };
return ( return (
<Button className={'btn-secondary mr-2'} onClick={rotate} isLoading={loading}> <Button isSecondary color={'primary'} css={tw`mr-2`} onClick={rotate} isLoading={loading}>
Rotate Password Rotate Password
</Button> </Button>
); );