ui(admin): start work on server startup settings

This commit is contained in:
Matthew Penner 2021-09-12 23:50:12 -06:00
parent 6362731d55
commit a615b7fa70
No known key found for this signature in database
GPG Key ID: 030E4AB751DC756F
7 changed files with 239 additions and 157 deletions

View File

@ -39,8 +39,7 @@ rules:
comma-dangle: comma-dangle:
- warn - warn
- always-multiline - always-multiline
spaced-comment: spaced-comment: 0
- warn
array-bracket-spacing: array-bracket-spacing:
- warn - warn
- always - always

View File

@ -0,0 +1,24 @@
import http from '@/api/http';
import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg';
interface Filters {
name?: string;
}
export default (nestId: number, filters?: Filters): Promise<Egg[]> => {
const params = {};
if (filters !== undefined) {
Object.keys(filters).forEach(key => {
// @ts-ignore
params['filter[' + key + ']'] = filters[key];
});
}
return new Promise((resolve, reject) => {
http.get(`/api/application/nests/${nestId}/eggs`, { params: { ...params } })
.then(response => resolve(
(response.data.data || []).map(rawDataToEgg)
))
.catch(reject);
});
};

View File

@ -0,0 +1,24 @@
import http from '@/api/http';
import { Nest, rawDataToNest } from '@/api/admin/nests/getNests';
interface Filters {
name?: string;
}
export default (filters?: Filters): Promise<Nest[]> => {
const params = {};
if (filters !== undefined) {
Object.keys(filters).forEach(key => {
// @ts-ignore
params['filter[' + key + ']'] = filters[key];
});
}
return new Promise((resolve, reject) => {
http.get('/api/application/nests', { params: { ...params } })
.then(response => resolve(
(response.data.data || []).map(rawDataToNest)
))
.catch(reject);
});
};

View File

@ -0,0 +1,32 @@
import Label from '@/components/elements/Label';
import Select from '@/components/elements/Select';
import React, { useEffect, useState } from 'react';
import { Egg } from '@/api/admin/eggs/getEgg';
import searchEggs from '@/api/admin/nests/searchEggs';
export default ({ nestId, eggId }: { nestId: number | null; eggId?: number }) => {
const [ eggs, setEggs ] = useState<Egg[]>([]);
useEffect(() => {
if (nestId === null) {
return;
}
searchEggs(nestId, {})
.then(eggs => setEggs(eggs))
.catch(error => console.error(error));
}, [ nestId ]);
return (
<>
<Label>Egg</Label>
<Select defaultValue={eggId || undefined} id={'eggId'} name={'eggId'}>
{eggs.map(v => (
<option key={v.id} value={v.id.toString()}>
{v.name}
</option>
))}
</Select>
</>
);
};

View File

@ -0,0 +1,30 @@
import Label from '@/components/elements/Label';
import Select from '@/components/elements/Select';
import React, { useEffect, useState } from 'react';
import { Nest } from '@/api/admin/nests/getNests';
import searchNests from '@/api/admin/nests/searchNests';
export default ({ nestId, setNestId }: { nestId: number | null; setNestId: (value: number | null) => void }) => {
const [ nests, setNests ] = useState<Nest[] | null>(null);
useEffect(() => {
console.log(nestId || undefined);
searchNests({})
.then(nests => setNests(nests))
.catch(error => console.error(error));
}, []);
return (
<>
<Label>Nest</Label>
<Select value={nestId || undefined} onChange={e => setNestId(Number(e.currentTarget.value))}>
{nests?.map(v => (
<option key={v.id} value={v.id.toString()}>
{v.name}
</option>
))}
</Select>
</>
);
};

View File

@ -135,7 +135,7 @@ export function ServerResourceContainer () {
<FormikSwitch <FormikSwitch
name={'oomKiller'} name={'oomKiller'}
label={'Out of Memory Killer'} label={'Out of Memory Killer'}
description={'Enabling OOM killer may cause server processes to exit unexpectedly. '} description={'Enabling OOM killer may cause server processes to exit unexpectedly.'}
/> />
</div> </div>
</div> </div>

View File

@ -1,153 +1,112 @@
import React from 'react'; import EggSelect from '@/components/admin/servers/EggSelect';
import Button from '@/components/elements/Button'; import NestSelect from '@/components/admin/servers/NestSelect';
import FormikSwitch from '@/components/elements/FormikSwitch'; import FormikSwitch from '@/components/elements/FormikSwitch';
import React, { useState } from 'react';
import Button from '@/components/elements/Button';
import Input from '@/components/elements/Input'; import Input from '@/components/elements/Input';
import AdminBox from '@/components/admin/AdminBox'; import AdminBox from '@/components/admin/AdminBox';
import tw from 'twin.macro'; import tw from 'twin.macro';
import { object } from 'yup'; import { object } from 'yup';
import updateServer from '@/api/admin/servers/updateServer';
import Field from '@/components/elements/Field'; import Field from '@/components/elements/Field';
import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik'; import { Form, Formik, useFormikContext } from 'formik';
import { Context } from '@/components/admin/servers/ServerRouter'; import { Context } from '@/components/admin/servers/ServerRouter';
import { ApplicationStore } from '@/state'; import { ApplicationStore } from '@/state';
import { Actions, useStoreActions } from 'easy-peasy'; import { Actions, useStoreActions } from 'easy-peasy';
import Label from '@/components/elements/Label'; import Label from '@/components/elements/Label';
// import { ServerEggVariable } from '@/api/server/types';
/* interface Props { // interface Values {
variable: ServerEggVariable; // startupCommand: string;
} */ // image: string;
//
// eggId: number;
// skipScripts: boolean;
// }
interface Values { function ServerStartupLineContainer () {
startupCommand: string;
nestId: number;
eggId: number;
}
/* const VariableBox = ({ variable }: Props) => {
const { isSubmitting } = useFormikContext(); const { isSubmitting } = useFormikContext();
const server = Context.useStoreState(state => state.server);
if (server === undefined) {
return (
<></>
);
}
return (
<AdminBox title={'Service Configuration'} css={tw`relative w-full`}>
<SpinnerOverlay visible={isSubmitting}/>
<Form css={tw`mb-0`}>
<div css={tw`md:w-full md:flex md:flex-col`}>
<Field
name={variable.envVariable}
defaultValue={variable.serverValue}
placeholder={variable.defaultValue}
description={variable.description}
/>
</div>
</Form>
</AdminBox>
);
}; */
const ServerServiceContainer = () => {
const { isSubmitting } = useFormikContext();
const server = Context.useStoreState(state => state.server);
if (server === undefined) {
return (
<></>
);
}
return (
<AdminBox title={'Service Configuration'} css={tw`relative w-full`}>
<SpinnerOverlay visible={isSubmitting}/>
<Form css={tw`mb-0`}>
<div css={tw`md:w-full md:flex md:flex-col`}>
<div css={tw`flex-1`}>
<div css={tw`p-3 mb-6 border-l-4 border-red-500`}>
<p css={tw`text-xs text-neutral-200`}>
This is a destructive operation in many cases. This server will be stopped immediately in order for this action to proceed.
</p>
</div>
<div css={tw`p-3 mb-6 border-l-4 border-red-500`}>
<p css={tw`text-xs text-neutral-200`}>
Changing any of the below values will result in the server processing a re-install command. The server will be stopped and will then proceed. If you would like the service scripts to not run, ensure the box is checked at the bottom.
</p>
</div>
</div>
<div css={tw`pb-4 mb-6 md:w-full md:flex md:flex-col md:mb-0`}>
Nest/Egg Selector HERE
</div>
<div css={tw`pb-4 mb-6 md:w-full md:flex md:flex-col md:mb-0`}>
<div css={tw`mt-6 bg-neutral-800 border border-neutral-900 shadow-inner p-4 rounded`}>
<FormikSwitch
name={'skip_install_script'}
label={'Skip Egg Install Script'}
description={'If the selected Egg has an install script attached to it, the script will run during install. If you would like to skip this step, check this box.'}
/>
</div>
</div>
</div>
</Form>
</AdminBox>
);
};
const ServerStartupContainer = () => {
const { isSubmitting } = useFormikContext();
const server = Context.useStoreState(state => state.server);
if (server === undefined) {
return (
<></>
);
}
return ( return (
<AdminBox title={'Startup Command'} css={tw`relative w-full`}> <AdminBox title={'Startup Command'} css={tw`relative w-full`}>
<SpinnerOverlay visible={isSubmitting}/> <SpinnerOverlay visible={isSubmitting}/>
<Form css={tw`mb-0`}> <Form css={tw`mb-0`}>
<div css={tw`mb-6 md:w-full md:flex md:flex-col`}> <div css={tw`mb-6`}>
<div css={tw`mb-6 md:w-full md:flex md:flex-col md:mr-4`}> <Field
<Field id={'startupCommand'}
id={'startupCommand'} name={'startupCommand'}
name={'startupCommand'} label={'Startup Command'}
label={'Startup Command'} type={'text'}
type={'string'} description={'Edit your server\'s startup command here. The following variables are available by default: {{SERVER_MEMORY}}, {{SERVER_IP}}, and {{SERVER_PORT}}.'}
description={'Edit your server\'s startup command here. The following variables are available by default: {{SERVER_MEMORY}}, {{SERVER_IP}}, and {{SERVER_PORT}}.'} />
/> </div>
</div>
<div css={tw`mb-6 md:w-full md:flex md:flex-col md:mb-0`}> <div>
<div> <Label>Default Startup Command</Label>
<Label>Default Startup Command</Label> <Input disabled/>
<Input </div>
disabled </Form>
value={server.relations.egg?.configStartup || ''} </AdminBox>
/> );
</div> }
function ServerServiceContainer ({ nestId: nestId2, eggId }: { nestId: number | null; eggId: number | null }) {
const { isSubmitting } = useFormikContext();
const [ nestId, setNestId ] = useState<number | null>(nestId2);
return (
<AdminBox title={'Service Configuration'} css={tw`relative w-full`}>
<SpinnerOverlay visible={isSubmitting}/>
<Form css={tw`mb-0`}>
<div css={tw`mb-6`}>
<NestSelect nestId={nestId} setNestId={setNestId}/>
</div>
<div css={tw`mb-6`}>
<EggSelect nestId={nestId} eggId={eggId || undefined}/>
</div>
<div css={tw`bg-neutral-800 border border-neutral-900 shadow-inner p-4 rounded`}>
<FormikSwitch
name={'skipScript'}
label={'Skip Egg Install Script'}
description={'SoonTM'}
/>
</div>
</Form>
</AdminBox>
);
}
function ServerImageContainer () {
const { isSubmitting } = useFormikContext();
return (
<AdminBox title={'Image Configuration'} css={tw`relative w-full`}>
<SpinnerOverlay visible={isSubmitting}/>
<Form css={tw`mb-0`}>
<div css={tw`md:w-full md:flex md:flex-col`}>
<div>
<Field
id={'image'}
name={'image'}
label={'Docker Image'}
type={'text'}
/>
</div> </div>
</div> </div>
</Form> </Form>
</AdminBox> </AdminBox>
); );
}; }
export default () => { export default function ServerStartupContainer () {
const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes); const { clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
const server = Context.useStoreState(state => state.server); const server = Context.useStoreState(state => state.server);
const setServer = Context.useStoreActions(actions => actions.setServer);
if (server === undefined) { if (server === undefined) {
return ( return (
@ -155,16 +114,8 @@ export default () => {
); );
} }
const submit = (values: Values, { setSubmitting }: FormikHelpers<Values>) => { const submit = () => {
clearFlashes('server'); clearFlashes('server');
// updateServer(server.id, values)
// .then(() => setServer({ ...server, ...values }))
// .catch(error => {
// console.error(error);
// clearAndAddHttpError({ key: 'server', error });
// })
// .then(() => setSubmitting(false));
}; };
return ( return (
@ -172,34 +123,56 @@ export default () => {
onSubmit={submit} onSubmit={submit}
initialValues={{ initialValues={{
startupCommand: server.container.startupCommand, startupCommand: server.container.startupCommand,
nestId: server.nestId, image: server.container.image,
eggId: server.eggId, eggId: 0,
skipScripts: false,
}} }}
validationSchema={object().shape({ validationSchema={object().shape({})}
})}
> >
{ {({ isSubmitting, isValid }) => (
({ isSubmitting, isValid }) => ( <div css={tw`flex flex-col`}>
<div css={tw`flex flex-col`}> <div css={tw`flex flex-row mb-8`}>
<div css={tw`flex flex-col w-full mb-4 mr-0 lg:mr-2`}> <ServerStartupLineContainer/>
<ServerStartupContainer/> </div>
<div css={tw`grid grid-cols-2 gap-x-8 mb-8`}>
<div css={tw`flex`}>
<ServerServiceContainer nestId={server?.nestId || null} eggId={server?.eggId || null}/>
</div> </div>
<div css={tw`flex flex-col w-1/2 mr-0 lg:mr-2`}> <div css={tw`flex`}>
<ServerServiceContainer/> <ServerImageContainer/>
</div>
<div css={tw`flex flex-col w-1/2 mr-0 lg:mr-2`}>
Server Startup variables go here
</div>
<div css={tw`py-2 pr-6 mt-4 rounded shadow-md bg-neutral-700`}>
<div css={tw`flex flex-row`}>
<Button type="submit" size="small" css={tw`ml-auto`} disabled={isSubmitting || !isValid}>
Save Changes
</Button>
</div>
</div> </div>
</div> </div>
)
} {/*<div css={tw`grid gap-8 md:grid-cols-2`}>*/}
{/* {variables.map((variable, i) => (*/}
{/* <TitledGreyBox*/}
{/* key={i}*/}
{/* title={<p css={tw`text-sm uppercase`}>{variable.name}</p>}*/}
{/* >*/}
{/* <InputSpinner visible={false}>*/}
{/* <Input*/}
{/* name={variable.envVariable}*/}
{/* defaultValue={variable.serverValue}*/}
{/* placeholder={variable.defaultValue}*/}
{/* />*/}
{/* </InputSpinner>*/}
{/* <p css={tw`mt-1 text-xs text-neutral-300`}>*/}
{/* {variable.description}*/}
{/* </p>*/}
{/* </TitledGreyBox>*/}
{/* ))}*/}
{/*</div>*/}
<div css={tw`py-2 pr-6 mt-4 rounded shadow-md bg-neutral-700`}>
<div css={tw`flex flex-row`}>
<Button type="submit" size="small" css={tw`ml-auto`} disabled={isSubmitting || !isValid}>
Save Changes
</Button>
</div>
</div>
</div>
)}
</Formik> </Formik>
); );
}; }