This commit is contained in:
Matthew Penner 2020-10-04 14:25:58 -06:00
parent df6f5c3a09
commit e7aeeace26
18 changed files with 614 additions and 65 deletions

View file

@ -0,0 +1,15 @@
import http from '@/api/http';
export interface Role {
id: number,
name: string,
description: string|null,
}
export default (): Promise<Role[]> => {
return new Promise((resolve, reject) => {
http.get('/admin/roles')
.then(({ data }) => resolve(data || []))
.catch(reject);
});
};

View file

@ -1,12 +1,22 @@
import React, { useState } from 'react';
import NewApiKeyButton from '@/components/admin/api/NewApiKeyButton';
import React, { useEffect, useState } from 'react';
import tw from 'twin.macro';
import AdminContentBlock from '@/components/admin/AdminContentBlock';
import Button from '@/components/elements/Button';
import Spinner from '@/components/elements/Spinner';
interface Key {
id: number,
}
export default () => {
const [ loading ] = useState<boolean>(false);
const [ keys ] = useState<any[]>([]);
const [ loading, setLoading ] = useState<boolean>(true);
const [ keys ] = useState<Key[]>([]);
useEffect(() => {
setTimeout(() => {
setLoading(false);
}, 500);
});
return (
<AdminContentBlock>
@ -16,16 +26,14 @@ export default () => {
<p css={tw`text-base text-neutral-400`}>Control access credentials for managing this Panel via the API.</p>
</div>
<Button type={'button'} size={'large'} css={tw`h-10 ml-auto px-4 py-0`}>
New API Key
</Button>
<NewApiKeyButton />
</div>
<div css={tw`w-full flex flex-col`}>
<div css={tw`w-full flex flex-col bg-neutral-700 rounded-lg shadow-md`}>
{ loading ?
<div css={tw`w-full flex flex-col items-center justify-center`} style={{ height: '24rem' }}>
<Spinner/>
<Spinner size={'base'}/>
</div>
:
keys.length < 1 ?

View file

@ -0,0 +1,173 @@
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 React, { useState } from 'react';
import tw from 'twin.macro';
import { object } from 'yup';
interface Values {
description: string,
}
const schema = object().shape({
});
export default () => {
const [ visible, setVisible ] = useState(false);
const { clearFlashes } = useFlash();
const submit = (values: Values, { setSubmitting }: FormikHelpers<Values>) => {
clearFlashes('api:create');
console.log(values);
setSubmitting(true);
setTimeout(() => {
setVisible(false);
}, 500);
};
return (
<>
<Formik
onSubmit={submit}
initialValues={{ description: '' }}
validationSchema={schema}
>
{
({ isSubmitting, resetForm }) => (
<Modal
visible={visible}
dismissable={!isSubmitting}
showSpinnerOverlay={isSubmitting}
onDismissed={() => {
resetForm();
setVisible(false);
}}
>
<FlashMessageRender byKey={'api:create'} css={tw`mb-6`}/>
<h2 css={tw`text-2xl mb-6`}>New API Key</h2>
<Form css={tw`m-0`}>
<Field
type={'string'}
id={'description'}
name={'description'}
label={'Description'}
description={'A descriptive note for this API Key.'}
/>
<div css={tw`w-full flex flex-col mt-6`}>
<div css={tw`h-10 w-full flex flex-row items-center bg-neutral-900 rounded-t-md px-4`}>
<p css={tw`text-sm text-neutral-300 uppercase`}>Permissions</p>
<div css={tw`flex flex-row space-x-4 ml-auto`}>
<span css={tw`text-xs text-neutral-300 cursor-pointer`}>None</span>
<span css={tw`text-xs text-neutral-300 cursor-pointer`}>Read</span>
<span css={tw`text-xs text-neutral-300 cursor-pointer`}>Write</span>
</div>
</div>
<div css={tw`w-full flex flex-col bg-neutral-700 rounded-b-md py-1 px-4`}>
<div css={tw`w-full flex flex-row items-center py-1`}>
<p css={tw`text-sm text-neutral-200`}>Allocations</p>
<div css={tw`flex space-x-6 ml-auto`}>
<div css={tw`flex pr-1`}>
<input type={'radio'} name={'allocations'} css={tw`h-5 w-5`} value={'0'}/>
</div>
<div css={tw`flex`}>
<input type={'radio'} name={'allocations'} css={tw`h-5 w-5`} value={'1'}/>
</div>
<div css={tw`flex pr-1`}>
<input type={'radio'} name={'allocations'} css={tw`h-5 w-5`} value={'2'}/>
</div>
</div>
</div>
<div css={tw`w-full flex flex-row items-center py-1`}>
<p css={tw`text-sm text-neutral-200`}>Databases</p>
<div css={tw`flex space-x-6 ml-auto`}>
<div css={tw`flex pr-1`}>
<input type={'radio'} name={'databases'} css={tw`h-5 w-5`} value={'0'}/>
</div>
<div css={tw`flex`}>
<input type={'radio'} name={'databases'} css={tw`h-5 w-5`} value={'1'}/>
</div>
<div css={tw`flex pr-1`}>
<input type={'radio'} name={'databases'} css={tw`h-5 w-5`} value={'2'}/>
</div>
</div>
</div>
<div css={tw`w-full flex flex-row items-center py-1`}>
<p css={tw`text-sm text-neutral-200`}>Eggs</p>
<div css={tw`flex space-x-6 ml-auto`}>
<div css={tw`flex pr-1`}>
<input type={'radio'} name={'eggs'} css={tw`h-5 w-5`} value={'0'}/>
</div>
<div css={tw`flex`}>
<input type={'radio'} name={'eggs'} css={tw`h-5 w-5`} value={'1'}/>
</div>
<div css={tw`flex pr-1`}>
<input type={'radio'} name={'eggs'} css={tw`h-5 w-5`} value={'2'}/>
</div>
</div>
</div>
<div css={tw`w-full flex flex-row items-center py-1`}>
<p css={tw`text-sm text-neutral-200`}>Locations</p>
<div css={tw`flex space-x-6 ml-auto`}>
<div css={tw`flex pr-1`}>
<input type={'radio'} name={'locations'} css={tw`h-5 w-5`} value={'0'}/>
</div>
<div css={tw`flex`}>
<input type={'radio'} name={'locations'} css={tw`h-5 w-5`} value={'1'}/>
</div>
<div css={tw`flex pr-1`}>
<input type={'radio'} name={'locations'} css={tw`h-5 w-5`} value={'2'}/>
</div>
</div>
</div>
</div>
</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 API Key
</Button>
</div>
</Form>
</Modal>
)
}
</Formik>
<Button type={'button'} size={'large'} css={tw`h-10 ml-auto px-4 py-0`} onClick={() => setVisible(true)}>
New API Key
</Button>
</>
);
};

View file

@ -1,3 +1,4 @@
import Button from '@/components/elements/Button';
import React from 'react';
import tw from 'twin.macro';
import AdminContentBlock from '@/components/admin/AdminContentBlock';
@ -5,9 +6,15 @@ import AdminContentBlock from '@/components/admin/AdminContentBlock';
export default () => {
return (
<AdminContentBlock>
<div>
<h2 css={tw`text-2xl text-neutral-50 font-header font-medium`}>Mounts</h2>
<p css={tw`text-base text-neutral-400`}>Configure and manage additional mount points for servers.</p>
<div css={tw`w-full flex flex-row items-center`}>
<div css={tw`flex flex-col`}>
<h2 css={tw`text-2xl text-neutral-50 font-header font-medium`}>Mounts</h2>
<p css={tw`text-base text-neutral-400`}>Configure and manage additional mount points for servers.</p>
</div>
<Button type={'button'} size={'large'} css={tw`h-10 ml-auto px-4 py-0`}>
New Mount
</Button>
</div>
</AdminContentBlock>
);

View file

@ -1,3 +1,4 @@
import Button from '@/components/elements/Button';
import React from 'react';
import tw from 'twin.macro';
import AdminContentBlock from '@/components/admin/AdminContentBlock';
@ -5,9 +6,15 @@ import AdminContentBlock from '@/components/admin/AdminContentBlock';
export default () => {
return (
<AdminContentBlock>
<div>
<h2 css={tw`text-2xl text-neutral-50 font-header font-medium`}>Nests</h2>
<p css={tw`text-base text-neutral-400`}>All nests currently available on this system.</p>
<div css={tw`w-full flex flex-row items-center`}>
<div css={tw`flex flex-col`}>
<h2 css={tw`text-2xl text-neutral-50 font-header font-medium`}>Nests</h2>
<p css={tw`text-base text-neutral-400`}>All nests currently available on this system.</p>
</div>
<Button type={'button'} size={'large'} css={tw`h-10 ml-auto px-4 py-0`}>
New Nest
</Button>
</div>
</AdminContentBlock>
);

View file

@ -1,3 +1,4 @@
import Button from '@/components/elements/Button';
import React from 'react';
import tw from 'twin.macro';
import AdminContentBlock from '@/components/admin/AdminContentBlock';
@ -5,9 +6,15 @@ import AdminContentBlock from '@/components/admin/AdminContentBlock';
export default () => {
return (
<AdminContentBlock>
<div>
<h2 css={tw`text-2xl text-neutral-50 font-header font-medium`}>Nodes</h2>
<p css={tw`text-base text-neutral-400`}>All nodes available on the system.</p>
<div css={tw`w-full flex flex-row items-center`}>
<div css={tw`flex flex-col`}>
<h2 css={tw`text-2xl text-neutral-50 font-header font-medium`}>Nodes</h2>
<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>
</div>
</AdminContentBlock>
);

View file

@ -0,0 +1,99 @@
import { httpErrorToHuman } from '@/api/http';
import FlashMessageRender from '@/components/FlashMessageRender';
import useFlash from '@/plugins/useFlash';
import React, { useEffect, useState } from 'react';
import Button from '@/components/elements/Button';
import tw from 'twin.macro';
import AdminContentBlock from '@/components/admin/AdminContentBlock';
import Spinner from '@/components/elements/Spinner';
import getRoles, { Role } from '@/api/admin/roles/getRoles';
export default () => {
const { clearFlashes, addError } = useFlash();
const [ loading, setLoading ] = useState<boolean>(true);
const [ roles, setRoles ] = useState<Role[]>([]);
useEffect(() => {
clearFlashes('roles');
getRoles()
.then(roles => setRoles(roles))
.catch(error => {
addError({ message: httpErrorToHuman(error), key: 'roles' });
console.error(error);
})
.then(() => setLoading(false));
}, []);
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`}>Roles</h2>
<p css={tw`text-base text-neutral-400`}>Soon&trade;</p>
</div>
<Button type={'button'} size={'large'} css={tw`h-10 ml-auto px-4 py-0`}>
New Role
</Button>
</div>
<FlashMessageRender byKey={'roles'} css={tw`mb-4`}/>
<div css={tw`w-full flex flex-col`}>
<div css={tw`w-full flex flex-col bg-neutral-700 rounded-lg shadow-md`}>
{ loading ?
<div css={tw`w-full flex flex-col items-center justify-center`} style={{ height: '24rem' }}>
<Spinner size={'base'}/>
</div>
:
roles.length < 1 ?
<div css={tw`w-full flex flex-col items-center justify-center pb-6 py-2 sm:py-8 md:py-10 px-8`}>
<div css={tw`h-64 flex`}>
<img src={'/assets/svgs/not_found.svg'} alt={'No Items'} css={tw`h-full select-none`}/>
</div>
<p css={tw`text-lg text-neutral-300 text-center font-normal sm:mt-8`}>No items could be found, it&apos;s almost like they are hiding.</p>
</div>
:
<div css={tw`overflow-x-auto`}>
<table css={tw`w-full table-auto`}>
<thead>
<tr>
<th css={tw`py-4 px-4 text-left pl-8`}>
<span css={tw`font-medium text-base text-neutral-300 text-left whitespace-no-wrap mr-2`}>ID</span>
</th>
<th css={tw`py-4 px-4 text-left`}>
<span css={tw`font-medium text-base text-neutral-300 text-left whitespace-no-wrap mr-2`}>Name</span>
</th>
<th css={tw`py-4 px-4 text-left pr-8`}>
<span css={tw`font-medium text-base text-neutral-300 text-left whitespace-no-wrap mr-2`}>Description</span>
</th>
</tr>
</thead>
<tbody css={tw`bg-neutral-600`}>
{
roles.map(role => (
<tr key={role.id} css={tw`h-12 cursor-pointer`}>
<td css={tw`py-3 px-4 text-neutral-200 text-left whitespace-no-wrap pl-8`}>{role.id}</td>
<td css={tw`py-3 px-4 text-neutral-200 text-left whitespace-no-wrap`}>{role.name}</td>
<td css={tw`py-3 px-4 text-neutral-200 text-left whitespace-no-wrap pr-8`}>{role.description}</td>
</tr>
))
}
</tbody>
</table>
<div css={tw`h-12 w-full flex flex-row items-center justify-between px-6`}>
</div>
</div>
}
</div>
</div>
</AdminContentBlock>
);
};

View file

@ -1,3 +1,4 @@
import Button from '@/components/elements/Button';
import React from 'react';
import tw from 'twin.macro';
import AdminContentBlock from '@/components/admin/AdminContentBlock';
@ -5,9 +6,15 @@ import AdminContentBlock from '@/components/admin/AdminContentBlock';
export default () => {
return (
<AdminContentBlock>
<div>
<h2 css={tw`text-2xl text-neutral-50 font-header font-medium`}>Servers</h2>
<p css={tw`text-base text-neutral-400`}>All servers available on the system.</p>
<div css={tw`w-full flex flex-row items-center`}>
<div css={tw`flex flex-col`}>
<h2 css={tw`text-2xl text-neutral-50 font-header font-medium`}>Servers</h2>
<p css={tw`text-base text-neutral-400`}>All servers available on the system.</p>
</div>
<Button type={'button'} size={'large'} css={tw`h-10 ml-auto px-4 py-0`}>
New Server
</Button>
</div>
</AdminContentBlock>
);

View file

@ -1,3 +1,4 @@
import Button from '@/components/elements/Button';
import React from 'react';
import tw from 'twin.macro';
import AdminContentBlock from '@/components/admin/AdminContentBlock';
@ -5,9 +6,15 @@ import AdminContentBlock from '@/components/admin/AdminContentBlock';
export default () => {
return (
<AdminContentBlock>
<div>
<h2 css={tw`text-2xl text-neutral-50 font-header font-medium`}>Users</h2>
<p css={tw`text-base text-neutral-400`}>All registered users on the system.</p>
<div css={tw`w-full flex flex-row items-center`}>
<div css={tw`flex flex-col`}>
<h2 css={tw`text-2xl text-neutral-50 font-header font-medium`}>Users</h2>
<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>
</div>
</AdminContentBlock>
);

View file

@ -1,3 +1,4 @@
import RolesContainer from '@/components/admin/roles/RolesContainer';
import React, { useState } from 'react';
import { NavLink, Route, RouteComponentProps, Switch } from 'react-router-dom';
import NotFound from '@/components/screens/NotFound';
@ -126,6 +127,10 @@ export default ({ location, match }: RouteComponentProps) => {
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /></svg>
<span>Users</span>
</NavLink>
<NavLink to={`${match.url}/roles`}>
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" /></svg>
<span>Roles</span>
</NavLink>
<span>Service Management</span>
@ -144,7 +149,7 @@ export default ({ location, match }: RouteComponentProps) => {
</NavLink>
<div className={'user'}>
<img src={'https://cdn.krygon.app/avatars/52564280420073473/7db9f06013ec39f7fa5c1e79241c43afa1f152d82cbb193ecaab7753b9a3e61e?size=64'} alt="Profile Picture" css={tw`h-10 w-10 rounded-full select-none`} />
<img src={'https://www.gravatar.com/avatar/78a6a270ec41715a8ae96c02b8961f9e?s=64'} alt="Profile Picture" css={tw`h-10 w-10 rounded-full select-none`} />
<div css={tw`flex flex-col ml-4`}>
<span css={tw`font-header font-medium text-sm text-neutral-50 whitespace-no-wrap leading-tight select-none`}>Matthew Penner</span>
@ -170,6 +175,7 @@ export default ({ location, match }: RouteComponentProps) => {
<Route path={`${match.path}/nodes`} component={NodesContainer}/>
<Route path={`${match.path}/servers`} component={ServersContainer}/>
<Route path={`${match.path}/users`} component={UsersContainer}/>
<Route path={`${match.path}/roles`} component={RolesContainer}/>
<Route path={`${match.path}/nests`} component={NestsContainer}/>
<Route path={`${match.path}/mounts`} component={MountsContainer}/>