admin(ui): add blank 'create' views
This commit is contained in:
parent
e7021dfc39
commit
d81aef68b5
12 changed files with 201 additions and 16 deletions
12
resources/scripts/api/admin/locations/createLocation.ts
Normal file
12
resources/scripts/api/admin/locations/createLocation.ts
Normal 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);
|
||||
});
|
||||
};
|
|
@ -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) => {
|
||||
|
|
|
@ -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`}/>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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`}/>
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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'}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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`}/>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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`}/>
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue