ui(admin): basic server creation
This commit is contained in:
parent
cc2ed97b0f
commit
70cf5c17aa
8 changed files with 81 additions and 29 deletions
|
@ -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)
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
}, []);
|
||||
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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 || '';
|
||||
|
|
|
@ -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 || '';
|
||||
|
|
Loading…
Reference in a new issue