ui(admin): rough layout on new server page

This commit is contained in:
Matthew Penner 2021-10-23 15:19:41 -06:00
parent bee7c4515c
commit cf1cc97340
No known key found for this signature in database
GPG key ID: BAB67850901908A8
6 changed files with 141 additions and 19 deletions

View file

@ -1,8 +1,30 @@
import React from 'react'; import { Egg } from '@/api/admin/egg';
import AdminBox from '@/components/admin/AdminBox';
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 Label from '@/components/elements/Label';
import Select from '@/components/elements/Select';
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
import FlashMessageRender from '@/components/FlashMessageRender';
import { faNetworkWired } from '@fortawesome/free-solid-svg-icons';
import { Form, Formik } from 'formik';
import React, { useState } from 'react';
import tw from 'twin.macro'; import tw from 'twin.macro';
import AdminContentBlock from '@/components/admin/AdminContentBlock'; import AdminContentBlock from '@/components/admin/AdminContentBlock';
import { object } from 'yup';
import { Values } from '@/api/admin/servers/updateServer';
export default () => { export default () => {
const [ egg, setEgg ] = useState<Egg | null>(null);
const submit = (_: Values) => {
//
};
return ( return (
<AdminContentBlock title={'New Server'}> <AdminContentBlock title={'New Server'}>
<div css={tw`w-full flex flex-row items-center mb-8`}> <div css={tw`w-full flex flex-row items-center mb-8`}>
@ -11,6 +33,108 @@ export default () => {
<p css={tw`text-base text-neutral-400 whitespace-nowrap overflow-ellipsis overflow-hidden`}>Add a new server to the panel.</p> <p css={tw`text-base text-neutral-400 whitespace-nowrap overflow-ellipsis overflow-hidden`}>Add a new server to the panel.</p>
</div> </div>
</div> </div>
<FlashMessageRender byKey={'server:create'} css={tw`mb-4`}/>
<Formik
onSubmit={submit}
initialValues={{
externalId: '',
name: '',
ownerId: 0,
limits: {
memory: 0,
swap: 0,
disk: 0,
io: 0,
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,
},
allocationId: 0,
addAllocations: [] as number[],
removeAllocations: [] as number[],
}}
validationSchema={object().shape({})}
>
{({ isSubmitting, isValid }) => (
<Form>
<div css={tw`grid grid-cols-1 md:grid-cols-2 gap-y-6 gap-x-8 mb-16`}>
<div css={tw`grid grid-cols-1 gap-y-6`}>
<BaseSettingsBox/>
<FeatureLimitsBox/>
{/* TODO: in networking box only show primary allocation and additional allocations */}
{/* TODO: add node select */}
<ServerServiceContainer
egg={egg}
setEgg={setEgg}
/* TODO: Get lowest nest_id rather than always defaulting to 1 */
nestId={1}
/>
</div>
<div css={tw`grid grid-cols-1 gap-y-6`}>
<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'}/>
</div>
<div>
<Label htmlFor={'additionalAllocations'}>Additional Allocations</Label>
<Select id={'additionalAllocations'} name={'additionalAllocations'}/>
</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`}>
{egg?.relationships.variables?.map((v, i) => (
<ServerVariableContainer
key={i}
variable={v}
defaultValue={v.defaultValue}
/>
))}
</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>
)}
</Formik>
</AdminContentBlock> </AdminContentBlock>
); );
}; };

View file

@ -3,10 +3,10 @@ import { useFormikContext } from 'formik';
import SearchableSelect, { Option } from '@/components/elements/SearchableSelect'; import SearchableSelect, { Option } from '@/components/elements/SearchableSelect';
import { User, searchUserAccounts } from '@/api/admin/user'; import { User, searchUserAccounts } from '@/api/admin/user';
export default ({ selected }: { selected: User }) => { export default ({ selected }: { selected?: User }) => {
const context = useFormikContext(); const context = useFormikContext();
const [ user, setUser ] = useState<User | null>(selected); const [ user, setUser ] = useState<User | null>(selected || null);
const [ users, setUsers ] = useState<User[] | null>(null); const [ users, setUsers ] = useState<User[] | null>(null);
const onSearch = async (query: string) => { const onSearch = async (query: string) => {

View file

@ -57,10 +57,10 @@ function ServerStartupLineContainer ({ egg, server }: { egg: Egg | null; server:
); );
} }
function ServerServiceContainer ({ egg, setEgg, server }: { egg: Egg | null, setEgg: (value: Egg | null) => void, server: Server }) { export function ServerServiceContainer ({ egg, setEgg, nestId: _nestId }: { egg: Egg | null, setEgg: (value: Egg | null) => void, nestId: number }) {
const { isSubmitting } = useFormikContext(); const { isSubmitting } = useFormikContext();
const [ nestId, setNestId ] = useState(server.nestId); const [ nestId, setNestId ] = useState(_nestId);
return ( return (
<AdminBox title={'Service Configuration'} isLoading={isSubmitting} css={tw`w-full`}> <AdminBox title={'Service Configuration'} isLoading={isSubmitting} css={tw`w-full`}>
@ -77,7 +77,7 @@ function ServerServiceContainer ({ egg, setEgg, server }: { egg: Egg | null, set
); );
} }
function ServerImageContainer () { export function ServerImageContainer () {
const { isSubmitting } = useFormikContext(); const { isSubmitting } = useFormikContext();
return ( return (
@ -98,7 +98,7 @@ function ServerImageContainer () {
); );
} }
function ServerVariableContainer ({ variable, defaultValue }: { variable: EggVariable, defaultValue: string }) { export function ServerVariableContainer ({ variable, defaultValue }: { variable: EggVariable, defaultValue: string }) {
const key = 'environment.' + variable.environmentVariable; const key = 'environment.' + variable.environmentVariable;
const { isSubmitting, setFieldValue } = useFormikContext(); const { isSubmitting, setFieldValue } = useFormikContext();
@ -140,7 +140,7 @@ function ServerStartupForm ({ egg, setEgg, server }: { egg: Egg | null, setEgg:
<ServerServiceContainer <ServerServiceContainer
egg={egg} egg={egg}
setEgg={setEgg} setEgg={setEgg}
server={server} nestId={server.nestId}
/> />
</div> </div>

View file

@ -11,14 +11,12 @@ export default () => {
const { data: server } = useServerFromRoute(); const { data: server } = useServerFromRoute();
const { isSubmitting } = useFormikContext(); const { isSubmitting } = useFormikContext();
if (!server) return null;
return ( return (
<AdminBox icon={faCogs} title={'Settings'} isLoading={isSubmitting}> <AdminBox icon={faCogs} title={'Settings'} isLoading={isSubmitting}>
<div css={tw`grid grid-cols-1 xl:grid-cols-2 gap-4 lg:gap-6`}> <div css={tw`grid grid-cols-1 xl:grid-cols-2 gap-4 lg:gap-6`}>
<Field id={'name'} name={'name'} label={'Server Name'} type={'text'}/> <Field id={'name'} name={'name'} label={'Server Name'} type={'text'}/>
<Field id={'externalId'} name={'externalId'} label={'External Identifier'} type={'text'}/> <Field id={'externalId'} name={'externalId'} label={'External Identifier'} type={'text'}/>
<OwnerSelect selected={server.relationships.user}/> <OwnerSelect selected={server?.relationships.user}/>
</div> </div>
</AdminBox> </AdminBox>
); );

View file

@ -13,9 +13,13 @@ export default () => {
const { isSubmitting } = useFormikContext(); const { isSubmitting } = useFormikContext();
const { data: server } = useServerFromRoute(); const { data: server } = useServerFromRoute();
if (!server) return null;
const loadOptions = async (inputValue: string, callback: (options: Option[]) => void) => { const loadOptions = async (inputValue: string, callback: (options: Option[]) => void) => {
if (!server) {
// eslint-disable-next-line node/no-callback-literal
callback([] as Option[]);
return;
}
const allocations = await getAllocations(server.nodeId, { ip: inputValue, server_id: '0' }); const allocations = await getAllocations(server.nodeId, { ip: inputValue, server_id: '0' });
callback(allocations.map(a => { callback(allocations.map(a => {
@ -29,7 +33,7 @@ export default () => {
<div> <div>
<Label htmlFor={'allocationId'}>Primary Allocation</Label> <Label htmlFor={'allocationId'}>Primary Allocation</Label>
<Select id={'allocationId'} name={'allocationId'}> <Select id={'allocationId'} name={'allocationId'}>
{server.relationships.allocations?.map(a => ( {server?.relationships.allocations?.map(a => (
<option key={a.id} value={a.id}>{a.getDisplayText()}</option> <option key={a.id} value={a.id}>{a.getDisplayText()}</option>
))} ))}
</Select> </Select>
@ -45,7 +49,7 @@ export default () => {
id={'removeAllocations'} id={'removeAllocations'}
name={'removeAllocations'} name={'removeAllocations'}
label={'Remove Allocations'} label={'Remove Allocations'}
options={server.relationships.allocations?.map(a => { options={server?.relationships.allocations?.map(a => {
return { value: a.id.toString(), label: a.getDisplayText() }; return { value: a.id.toString(), label: a.getDisplayText() };
}) || []} }) || []}
isMulti isMulti

View file

@ -5,13 +5,9 @@ import tw from 'twin.macro';
import Field from '@/components/elements/Field'; import Field from '@/components/elements/Field';
import FormikSwitch from '@/components/elements/FormikSwitch'; import FormikSwitch from '@/components/elements/FormikSwitch';
import React from 'react'; import React from 'react';
import { useServerFromRoute } from '@/api/admin/server';
export default () => { export default () => {
const { isSubmitting } = useFormikContext(); const { isSubmitting } = useFormikContext();
const { data: server } = useServerFromRoute();
if (!server) return null;
return ( return (
<AdminBox icon={faBalanceScale} title={'Resources'} isLoading={isSubmitting}> <AdminBox icon={faBalanceScale} title={'Resources'} isLoading={isSubmitting}>