admin(ui): add blank 'create' views

This commit is contained in:
Matthew Penner 2021-01-06 15:39:23 -07:00
parent e7021dfc39
commit d81aef68b5
12 changed files with 201 additions and 16 deletions

View file

@ -0,0 +1,12 @@
import http from '@/api/http';
import { Location } from '@/api/admin/locations/getLocations';
export default (short: string, long?: string): Promise<Location> => {
return new Promise((resolve, reject) => {
http.post('/api/application/locations', {
short, long,
})
.then(({ data }) => resolve(data.attributes))
.catch(reject);
});
};

View file

@ -1,5 +1,5 @@
import { Role } from '@/api/admin/roles/getRoles';
import http from '@/api/http';
import { Role } from '@/api/admin/roles/getRoles';
export default (name: string, description?: string): Promise<Role> => {
return new Promise((resolve, reject) => {

View file

@ -64,13 +64,15 @@ const DatabasesContainer = () => {
<AdminContentBlock>
<div css={tw`w-full flex flex-row items-center mb-8`}>
<div css={tw`flex flex-col`}>
<h2 css={tw`text-2xl text-neutral-50 font-header font-medium`}>Databases</h2>
<h2 css={tw`text-2xl text-neutral-50 font-header font-medium`}>Database Hosts</h2>
<p css={tw`text-base text-neutral-400`}>Database hosts that servers can have databases created on.</p>
</div>
<Button type={'button'} size={'large'} css={tw`h-10 ml-auto px-4 py-0`}>
New Database
</Button>
<NavLink to={`${match.url}/new`} css={tw`ml-auto`}>
<Button type={'button'} size={'large'} css={tw`h-10 px-4 py-0`}>
New Database Host
</Button>
</NavLink>
</div>
<FlashMessageRender byKey={'databases'} css={tw`mb-4`}/>

View file

@ -0,0 +1,16 @@
import React from 'react';
import tw from 'twin.macro';
import AdminContentBlock from '@/components/admin/AdminContentBlock';
export default () => {
return (
<AdminContentBlock>
<div css={tw`w-full flex flex-row items-center mb-8`}>
<div css={tw`flex flex-col`}>
<h2 css={tw`text-2xl text-neutral-50 font-header font-medium`}>Create Database Host</h2>
<p css={tw`text-base text-neutral-400`}>Add a new database host to the panel.</p>
</div>
</div>
</AdminContentBlock>
);
};

View file

@ -1,4 +1,3 @@
import CopyOnClick from '@/components/elements/CopyOnClick';
import React, { useContext, useEffect, useState } from 'react';
import getLocations, { Context as LocationsContext } from '@/api/admin/locations/getLocations';
import FlashMessageRender from '@/components/FlashMessageRender';
@ -9,7 +8,8 @@ import tw from 'twin.macro';
import AdminContentBlock from '@/components/admin/AdminContentBlock';
import AdminCheckbox from '@/components/admin/AdminCheckbox';
import AdminTable, { TableBody, TableHead, TableHeader, TableRow, Pagination, Loading, NoItems, ContentWrapper } from '@/components/admin/AdminTable';
import Button from '@/components/elements/Button';
import NewLocationButton from '@/components/admin/locations/NewLocationButton';
import CopyOnClick from '@/components/elements/CopyOnClick';
const RowCheckbox = ({ id }: { id: number}) => {
const isChecked = AdminContext.useStoreState(state => state.locations.selectedLocations.indexOf(id) >= 0);
@ -68,9 +68,7 @@ const LocationsContainer = () => {
<p css={tw`text-base text-neutral-400`}>All locations that nodes can be assigned to for easier categorization.</p>
</div>
<Button type={'button'} size={'large'} css={tw`h-10 ml-auto px-4 py-0`}>
New Location
</Button>
<NewLocationButton/>
</div>
<FlashMessageRender byKey={'locations'} css={tw`mb-4`}/>

View file

@ -0,0 +1,111 @@
import React, { useState } from 'react';
import Button from '@/components/elements/Button';
import Field from '@/components/elements/Field';
import Modal from '@/components/elements/Modal';
import FlashMessageRender from '@/components/FlashMessageRender';
import useFlash from '@/plugins/useFlash';
import { Form, Formik, FormikHelpers } from 'formik';
import tw from 'twin.macro';
import { object, string } from 'yup';
import createLocation from '@/api/admin/locations/createLocation';
import getLocations from '@/api/admin/locations/getLocations';
interface Values {
short: string,
long: string,
}
const schema = object().shape({
short: string()
.required('A location short name must be provided.')
.max(32, 'Location short name must not exceed 32 characters.'),
long: string()
.max(255, 'Location long name must not exceed 255 characters.'),
});
export default () => {
const [ visible, setVisible ] = useState(false);
const { clearFlashes, clearAndAddHttpError } = useFlash();
const { mutate } = getLocations();
const submit = ({ short, long }: Values, { setSubmitting }: FormikHelpers<Values>) => {
clearFlashes('location:create');
setSubmitting(true);
createLocation(short, long)
.then(location => {
mutate(data => ({ ...data, items: data.items.concat(location) }), false);
setVisible(false);
})
.catch(error => {
clearAndAddHttpError(error);
setSubmitting(false);
});
};
return (
<>
<Formik
onSubmit={submit}
initialValues={{ short: '', long: '' }}
validationSchema={schema}
>
{
({ isSubmitting, resetForm }) => (
<Modal
visible={visible}
dismissable={!isSubmitting}
showSpinnerOverlay={isSubmitting}
onDismissed={() => {
resetForm();
setVisible(false);
}}
>
<FlashMessageRender byKey={'location:create'} css={tw`mb-6`}/>
<h2 css={tw`text-neutral-100 text-2xl mb-6`}>New Location</h2>
<Form css={tw`m-0`}>
<Field
type={'string'}
id={'short'}
name={'short'}
label={'Short'}
description={'A short name used to identify this location.'}
/>
<div css={tw`mt-6`}>
<Field
type={'string'}
id={'long'}
name={'long'}
label={'Long'}
description={'A long name for this location.'}
/>
</div>
<div css={tw`flex flex-wrap justify-end mt-6`}>
<Button
type={'button'}
isSecondary
css={tw`w-full sm:w-auto sm:mr-2`}
onClick={() => setVisible(false)}
>
Cancel
</Button>
<Button css={tw`w-full mt-4 sm:w-auto sm:mt-0`} type={'submit'}>
Create Location
</Button>
</div>
</Form>
</Modal>
)
}
</Formik>
<Button type={'button'} size={'large'} css={tw`h-10 ml-auto px-4 py-0`} onClick={() => setVisible(true)}>
New Location
</Button>
</>
);
};

View file

@ -62,7 +62,9 @@ export default () => {
}}
>
<FlashMessageRender byKey={'nest:create'} css={tw`mb-6`}/>
<h2 css={tw`text-neutral-100 text-2xl mb-6`}>New Nest</h2>
<Form css={tw`m-0`}>
<Field
type={'string'}

View file

@ -0,0 +1,16 @@
import React from 'react';
import tw from 'twin.macro';
import AdminContentBlock from '@/components/admin/AdminContentBlock';
export default () => {
return (
<AdminContentBlock>
<div css={tw`w-full flex flex-row items-center mb-8`}>
<div css={tw`flex flex-col`}>
<h2 css={tw`text-2xl text-neutral-50 font-header font-medium`}>Create Node</h2>
<p css={tw`text-base text-neutral-400`}>Add a new node to the panel.</p>
</div>
</div>
</AdminContentBlock>
);
};

View file

@ -69,9 +69,11 @@ const NodesContainer = () => {
<p css={tw`text-base text-neutral-400`}>All nodes available on the system.</p>
</div>
<Button type={'button'} size={'large'} css={tw`h-10 ml-auto px-4 py-0`}>
New Node
</Button>
<NavLink to={`${match.url}/new`} css={tw`ml-auto`}>
<Button type={'button'} size={'large'} css={tw`h-10 px-4 py-0`}>
New Node
</Button>
</NavLink>
</div>
<FlashMessageRender byKey={'nodes'} css={tw`mb-4`}/>

View file

@ -0,0 +1,16 @@
import React from 'react';
import tw from 'twin.macro';
import AdminContentBlock from '@/components/admin/AdminContentBlock';
export default () => {
return (
<AdminContentBlock>
<div css={tw`w-full flex flex-row items-center mb-8`}>
<div css={tw`flex flex-col`}>
<h2 css={tw`text-2xl text-neutral-50 font-header font-medium`}>Create User</h2>
<p css={tw`text-base text-neutral-400`}>Add a new user to the panel.</p>
</div>
</div>
</AdminContentBlock>
);
};

View file

@ -68,9 +68,11 @@ const UsersContainer = () => {
<p css={tw`text-base text-neutral-400`}>All registered users on the system.</p>
</div>
<Button type={'button'} size={'large'} css={tw`h-10 ml-auto px-4 py-0`}>
New User
</Button>
<NavLink to={`${match.url}/new`} css={tw`ml-auto`}>
<Button type={'button'} size={'large'} css={tw`h-10 px-4 py-0`}>
New User
</Button>
</NavLink>
</div>
<FlashMessageRender byKey={'users'} css={tw`mb-4`}/>

View file

@ -10,11 +10,14 @@ import OverviewContainer from '@/components/admin/overview/OverviewContainer';
import SettingsContainer from '@/components/admin/settings/SettingsContainer';
import ApiKeysContainer from '@/components/admin/api/ApiKeysContainer';
import DatabasesContainer from '@/components/admin/databases/DatabasesContainer';
import NewDatabaseContainer from '@/components/admin/databases/NewDatabaseContainer';
import NodesContainer from '@/components/admin/nodes/NodesContainer';
import NewNodeContainer from '@/components/admin/nodes/NewNodeContainer';
import LocationsContainer from '@/components/admin/locations/LocationsContainer';
import ServersContainer from '@/components/admin/servers/ServersContainer';
import NewServerContainer from '@/components/admin/servers/NewServerContainer';
import UsersContainer from '@/components/admin/users/UsersContainer';
import NewUserContainer from '@/components/admin/users/NewUserContainer';
import RolesContainer from '@/components/admin/roles/RolesContainer';
import RoleEditContainer from '@/components/admin/roles/RoleEditContainer';
import NestsContainer from '@/components/admin/nests/NestsContainer';
@ -174,13 +177,18 @@ const AdminRouter = ({ location, match }: RouteComponentProps) => {
<Route path={`${match.path}/api`} component={ApiKeysContainer} exact/>
<Route path={`${match.path}/databases`} component={DatabasesContainer} exact/>
<Route path={`${match.path}/databases/new`} component={NewDatabaseContainer} exact/>
<Route path={`${match.path}/locations`} component={LocationsContainer} exact/>
<Route path={`${match.path}/nodes`} component={NodesContainer} exact/>
<Route path={`${match.path}/nodes/new`} component={NewNodeContainer} exact/>
<Route path={`${match.path}/servers`} component={ServersContainer} exact/>
<Route path={`${match.path}/servers/new`} component={NewServerContainer} exact/>
<Route path={`${match.path}/users`} component={UsersContainer} exact/>
<Route path={`${match.path}/users/new`} component={NewUserContainer} exact/>
<Route path={`${match.path}/roles`} component={RolesContainer} exact/>
<Route