misc_pterodactyl-panel/resources/scripts/components/admin/servers/NewServerContainer.tsx

210 lines
9.5 KiB
TypeScript

import { Egg } from '@/api/admin/egg';
import AdminBox from '@/components/admin/AdminBox';
import NodeSelect from '@/components/admin/servers/NodeSelect';
import { ServerImageContainer, ServerServiceContainer, ServerVariableContainer } from '@/components/admin/servers/ServerStartupContainer';
import BaseSettingsBox from '@/components/admin/servers/settings/BaseSettingsBox';
import FeatureLimitsBox from '@/components/admin/servers/settings/FeatureLimitsBox';
import ServerResourceBox from '@/components/admin/servers/settings/ServerResourceBox';
import Button from '@/components/elements/Button';
import Field from '@/components/elements/Field';
import FormikSwitch from '@/components/elements/FormikSwitch';
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, { 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 createServer, { CreateServerRequest } from '@/api/admin/servers/createServer';
import { Allocation, Node, getAllocations } from '@/api/admin/node';
function InternalForm () {
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', '');
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 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'}
label={'Start after installation'}
description={'Should the server be automatically started after it has been installed?'}
/>
</div>
</BaseSettingsBox>
<FeatureLimitsBox/>
<ServerServiceContainer
egg={egg}
setEgg={setEgg}
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={'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/>
<ServerImageContainer/>
</div>
<AdminBox title={'Startup Command'} css={tw`relative w-full col-span-2`}>
<SpinnerOverlay visible={isSubmitting}/>
<Field
id={'startup'}
name={'startup'}
label={'Startup Command'}
type={'text'}
description={'Edit your server\'s startup command here. The following variables are available by default: {{SERVER_MEMORY}}, {{SERVER_IP}}, and {{SERVER_PORT}}.'}
placeholder={egg?.startup || ''}
/>
</AdminBox>
<div css={tw`col-span-2 grid grid-cols-1 md:grid-cols-2 gap-y-6 gap-x-8`}>
{/* This ensures that no variables are rendered unless the environment has a value for the variable. */}
{egg?.relationships.variables?.filter(v => Object.keys(environment).find(e => e === v.environmentVariable) !== undefined).map((v, i) => (
<ServerVariableContainer
key={i}
variable={v}
/>
))}
</div>
<div css={tw`bg-neutral-700 rounded shadow-md px-4 py-3 col-span-2`}>
<div css={tw`flex flex-row`}>
<Button
type="submit"
size="small"
css={tw`ml-auto`}
disabled={isSubmitting || !isValid}
>
Create Server
</Button>
</div>
</div>
</div>
</Form>
);
}
export default () => {
const history = useHistory();
const { clearFlashes, clearAndAddHttpError } = useFlash();
const submit = (r: CreateServerRequest, { setSubmitting }: FormikHelpers<CreateServerRequest>) => {
console.log(r);
clearFlashes('server:create');
createServer(r)
.then(s => history.push(`/admin/servers/${s.id}`))
.catch(error => clearAndAddHttpError({ key: 'server:create', error }))
.then(() => setSubmitting(false));
};
return (
<AdminContentBlock title={'New Server'}>
<div css={tw`w-full flex flex-row items-center mb-8`}>
<div css={tw`flex flex-col flex-shrink`} style={{ minWidth: '0' }}>
<h2 css={tw`text-2xl text-neutral-50 font-header font-medium`}>New Server</h2>
<p css={tw`text-base text-neutral-400 whitespace-nowrap overflow-ellipsis overflow-hidden`}>Add a new server to the panel.</p>
</div>
</div>
<FlashMessageRender byKey={'server:create'} css={tw`mb-4`}/>
<Formik
onSubmit={submit}
initialValues={{
externalId: '',
name: '',
description: '',
ownerId: 0,
nodeId: 0,
limits: {
memory: 1024,
swap: 0,
disk: 4096,
io: 500,
cpu: 0,
threads: '',
// This value is inverted to have the switch be on when the
// OOM Killer is enabled, rather than when disabled.
oomDisabled: false,
},
featureLimits: {
allocations: 1,
backups: 0,
databases: 0,
},
allocation: {
default: 0,
additional: [] as number[],
},
startup: '',
environment: [],
eggId: 0,
image: '',
skipScripts: false,
startOnCompletion: true,
} as CreateServerRequest}
validationSchema={object().shape({})}
>
<InternalForm/>
</Formik>
</AdminContentBlock>
);
};