ui(admin): basic server creation

This commit is contained in:
Matthew Penner 2021-10-29 00:04:28 -06:00
parent cc2ed97b0f
commit 70cf5c17aa
No known key found for this signature in database
GPG key ID: BAB67850901908A8
8 changed files with 81 additions and 29 deletions

View file

@ -79,7 +79,7 @@ class ServerController extends ApplicationApiController
*/
public function store(StoreServerRequest $request): JsonResponse
{
$server = $this->creationService->handle($request->validated(), $request->getDeploymentObject());
$server = $this->creationService->handle($request->validated());
return $this->fractal->item($server)
->transformWith(ServerTransformer::class)

View file

@ -74,7 +74,7 @@ class StoreServerRequest extends ApplicationApiRequest
'startup' => array_get($data, 'startup'),
'environment' => array_get($data, 'environment'),
'egg_id' => array_get($data, 'egg'),
'egg_id' => array_get($data, 'egg_id'),
'image' => array_get($data, 'image'),
'skip_scripts' => array_get($data, 'skip_scripts'),
'start_on_completion' => array_get($data, 'start_on_completion', false),

View file

@ -74,3 +74,11 @@ export const searchNodes = async (params: QueryBuilderParams<'name'>): Promise<N
return data.data.map(AdminTransformers.toNode);
};
export const getAllocations = async (id: string | number, params?: QueryBuilderParams<'ip' | 'server_id'>): Promise<Allocation[]> => {
const { data } = await http.get(`/api/application/nodes/${id}/allocations`, {
params: withQueryBuilderParams(params),
});
return data.data.map(AdminTransformers.toAllocation);
};

View file

@ -53,9 +53,10 @@ export default (r: CreateServerRequest, include: string[] = []): Promise<Server>
memory: r.limits.memory,
swap: r.limits.swap,
threads: r.limits.threads,
oom_killer: r.limits.oomDisabled,
},
featureLimits: {
feature_limits: {
allocations: r.featureLimits.allocations,
backups: r.featureLimits.backups,
databases: r.featureLimits.databases,

View file

@ -13,7 +13,12 @@ export default ({ selectedNestId, onNestSelect }: Props) => {
useEffect(() => {
searchNests({})
.then(setNests)
.then(nests => {
setNests(nests);
if (selectedNestId === 0 && nests.length > 0) {
onNestSelect(nests[0].id);
}
})
.catch(error => console.error(error));
}, []);

View file

@ -12,25 +12,50 @@ import Label from '@/components/elements/Label';
import Select from '@/components/elements/Select';
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
import FlashMessageRender from '@/components/FlashMessageRender';
import useFlash from '@/plugins/useFlash';
import { faNetworkWired } from '@fortawesome/free-solid-svg-icons';
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
import tw from 'twin.macro';
import AdminContentBlock from '@/components/admin/AdminContentBlock';
import { object } from 'yup';
import { CreateServerRequest } from '@/api/admin/servers/createServer';
import createServer, { CreateServerRequest } from '@/api/admin/servers/createServer';
import { Allocation, Node, getAllocations } from '@/api/admin/node';
function InternalForm () {
const { isSubmitting, isValid, values: { environment } } = useFormikContext<CreateServerRequest>();
const { isSubmitting, isValid, setFieldValue, values: { environment } } = useFormikContext<CreateServerRequest>();
const [ egg, setEgg ] = useState<Egg | null>(null);
const [ node, setNode ] = useState<Node | null>(null);
const [ allocations, setAllocations ] = useState<Allocation[] | null>(null);
useEffect(() => {
if (egg === null) {
return;
}
setFieldValue('eggId', egg.id);
setFieldValue('startup', egg.startup);
setFieldValue('image', egg.dockerImages.length > 0 ? egg.dockerImages[0] : '');
}, [ egg ]);
useEffect(() => {
if (node === null) {
return;
}
// server_id: 0 filters out assigned allocations
getAllocations(node.id, { filters: { server_id: '0' } })
.then(setAllocations);
}, [ node ]);
return (
<Form>
<div css={tw`grid grid-cols-2 gap-y-6 gap-x-8 mb-16`}>
<div css={tw`grid grid-cols-1 gap-y-6 col-span-2 md:col-span-1`}>
<BaseSettingsBox>
<NodeSelect/>
<NodeSelect node={node} setNode={setNode}/>
<div css={tw`xl:col-span-2 bg-neutral-800 border border-neutral-900 shadow-inner p-4 rounded`}>
<FormikSwitch
name={'startOnCompletion'}
@ -43,25 +68,32 @@ function InternalForm () {
<ServerServiceContainer
egg={egg}
setEgg={setEgg}
/* TODO: Get lowest nest_id rather than always defaulting to 1 */
nestId={1}
nestId={0}
/>
</div>
<div css={tw`grid grid-cols-1 gap-y-6 col-span-2 md:col-span-1`}>
<AdminBox icon={faNetworkWired} title={'Networking'} isLoading={isSubmitting}>
<div css={tw`grid grid-cols-1 gap-4 lg:gap-6`}>
<div>
<Label htmlFor={'allocationId'}>Primary Allocation</Label>
<Select id={'allocationId'} name={'allocationId'} disabled>
<option value="">Select a node...</option>
</Select>
</div>
<div>
<Label htmlFor={'additionalAllocations'}>Additional Allocations</Label>
<Select id={'additionalAllocations'} name={'additionalAllocations'} disabled>
<option value="">Select a node...</option>
<Label htmlFor={'allocation.default'}>Primary Allocation</Label>
<Select
id={'allocation.default'}
name={'allocation.default'}
disabled={node === null}
onChange={e => setFieldValue('allocation.default', Number(e.currentTarget.value))}
>
{node === null ? <option value="">Select a node...</option> : <option value="">Select an allocation...</option>}
{allocations?.map(a => <option key={a.id} value={a.id.toString()}>{a.getDisplayText()}</option>)}
</Select>
</div>
{/*<div>*/}
{/* /!* TODO: Multi-select *!/*/}
{/* <Label htmlFor={'allocation.additional'}>Additional Allocations</Label>*/}
{/* <Select id={'allocation.additional'} name={'allocation.additional'} disabled={node === null}>*/}
{/* {node === null ? <option value="">Select a node...</option> : <option value="">Select additional allocations...</option>}*/}
{/* {allocations?.map(a => <option key={a.id} value={a.id.toString()}>{a.getDisplayText()}</option>)}*/}
{/* </Select>*/}
{/*</div>*/}
</div>
</AdminBox>
<ServerResourceBox/>
@ -109,9 +141,18 @@ function InternalForm () {
}
export default () => {
const history = useHistory();
const { clearFlashes, clearAndAddHttpError } = useFlash();
const submit = (r: CreateServerRequest, { setSubmitting }: FormikHelpers<CreateServerRequest>) => {
console.log(r);
setSubmitting(false);
clearFlashes('server:create');
createServer(r)
.then(s => history.push(`/admin/servers/${s.id}`))
.catch(error => clearAndAddHttpError({ key: 'server:create', error }))
.then(() => setSubmitting(false));
};
return (

View file

@ -3,21 +3,18 @@ import { useFormikContext } from 'formik';
import SearchableSelect, { Option } from '@/components/elements/SearchableSelect';
import { Node, searchNodes } from '@/api/admin/node';
export default ({ selected }: { selected?: Node }) => {
const context = useFormikContext();
export default ({ node, setNode }: { node: Node | null, setNode: (_: Node | null) => void }) => {
const { setFieldValue } = useFormikContext();
const [ node, setNode ] = useState<Node | null>(selected || null);
const [ nodes, setNodes ] = useState<Node[] | null>(null);
const onSearch = async (query: string) => {
setNodes(
await searchNodes({ filters: { name: query } }),
);
setNodes(await searchNodes({ filters: { name: query } }));
};
const onSelect = (node: Node | null) => {
setNode(node);
context.setFieldValue('ownerId', node?.id || null);
setFieldValue('nodeId', node?.id || null);
};
const getSelectedText = (node: Node | null): string => node?.name || '';

View file

@ -4,7 +4,7 @@ import SearchableSelect, { Option } from '@/components/elements/SearchableSelect
import { User, searchUserAccounts } from '@/api/admin/user';
export default ({ selected }: { selected?: User }) => {
const context = useFormikContext();
const { setFieldValue } = useFormikContext();
const [ user, setUser ] = useState<User | null>(selected || null);
const [ users, setUsers ] = useState<User[] | null>(null);
@ -17,7 +17,7 @@ export default ({ selected }: { selected?: User }) => {
const onSelect = (user: User | null) => {
setUser(user);
context.setFieldValue('ownerId', user?.id || null);
setFieldValue('ownerId', user?.id || null);
};
const getSelectedText = (user: User | null): string => user?.email || '';