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

340 lines
14 KiB
TypeScript
Raw Normal View History

import getAllocations from '@/api/admin/nodes/getAllocations';
import { Server } from '@/api/admin/servers/getServers';
import ServerDeleteButton from '@/components/admin/servers/ServerDeleteButton';
import Label from '@/components/elements/Label';
import Select from '@/components/elements/Select';
import SelectField, { AsyncSelectField, Option } from '@/components/elements/SelectField';
2021-09-15 17:09:54 +00:00
import { faBalanceScale, faCogs, faConciergeBell, faNetworkWired } from '@fortawesome/free-solid-svg-icons';
import React from 'react';
import AdminBox from '@/components/admin/AdminBox';
import { useHistory } from 'react-router-dom';
import tw from 'twin.macro';
import { object } from 'yup';
import updateServer, { Values } from '@/api/admin/servers/updateServer';
import Field from '@/components/elements/Field';
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
import { Context } from '@/components/admin/servers/ServerRouter';
import { ApplicationStore } from '@/state';
import { Actions, useStoreActions } from 'easy-peasy';
import OwnerSelect from '@/components/admin/servers/OwnerSelect';
import Button from '@/components/elements/Button';
import FormikSwitch from '@/components/elements/FormikSwitch';
export function ServerFeatureContainer () {
const { isSubmitting } = useFormikContext();
return (
2021-09-15 17:09:54 +00:00
<AdminBox icon={faConciergeBell} title={'Feature Limits'} css={tw`relative w-full`}>
<SpinnerOverlay visible={isSubmitting}/>
2021-08-05 04:16:52 +00:00
<div css={tw`mb-6 md:w-full md:flex md:flex-row`}>
<div css={tw`mb-6 md:w-full md:flex md:flex-col md:mr-4 md:mb-0`}>
<Field
id={'databases'}
name={'databases'}
label={'Database Limit'}
type={'number'}
description={'The total number of databases a user is allowed to create for this server.'}
/>
</div>
2021-08-05 04:16:52 +00:00
<div css={tw`mb-6 md:w-full md:flex md:flex-col md:mx-4 md:mb-0`}>
<Field
id={'allocations'}
name={'allocations'}
label={'Allocation Limit'}
type={'number'}
description={'The total number of allocations a user is allowed to create for this server.'}
/>
</div>
2021-08-05 04:16:52 +00:00
<div css={tw`mb-6 md:w-full md:flex md:flex-col md:ml-4 md:mb-0`}>
<Field
id={'backups'}
name={'backups'}
2021-08-05 04:16:52 +00:00
label={'Backup Limit'}
type={'number'}
description={'The total number of backups that can be created for this server.'}
/>
</div>
</div>
</AdminBox>
);
}
export function ServerResourceContainer () {
const { isSubmitting } = useFormikContext();
return (
<AdminBox icon={faBalanceScale} title={'Resources'} css={tw`relative w-full`}>
<SpinnerOverlay visible={isSubmitting}/>
2021-08-05 04:16:52 +00:00
<div css={tw`mb-6 md:w-full md:flex md:flex-row`}>
<div css={tw`mb-6 md:w-full md:flex md:flex-col md:mr-4 md:mb-0`}>
<Field
id={'cpu'}
name={'cpu'}
label={'CPU Limit'}
type={'text'}
description={'Each thread on the system is considered to be 100%. Setting this value to 0 will allow the server to use CPU time without restriction.'}
2021-08-05 04:16:52 +00:00
/>
</div>
2021-08-05 04:16:52 +00:00
<div css={tw`mb-6 md:w-full md:flex md:flex-col md:ml-4 md:mb-0`}>
<Field
id={'threads'}
name={'threads'}
label={'CPU Pinning'}
type={'text'}
description={'Advanced: Enter the specific CPU cores that this server can run on, or leave blank to allow all cores. This can be a single number, and or a comma seperated list, and or a dashed range. Example: 0, 0-1,3, or 0,1,3,4. It is recommended to leave this value blank and let the CPU handle balancing the load.'}
2021-08-05 04:16:52 +00:00
/>
</div>
2021-08-05 04:16:52 +00:00
</div>
<div css={tw`mb-6 md:w-full md:flex md:flex-row`}>
<div css={tw`mb-6 md:w-full md:flex md:flex-col md:mr-4 md:mb-0`}>
<Field
id={'memory'}
name={'memory'}
label={'Memory Limit'}
type={'number'}
description={'The maximum amount of memory allowed for this container. Setting this to 0 will allow unlimited memory in a container.'}
/>
</div>
2021-08-05 04:16:52 +00:00
<div css={tw`mb-6 md:w-full md:flex md:flex-col md:ml-4 md:mb-0`}>
<Field
id={'swap'}
name={'swap'}
label={'Swap Limit'}
type={'number'}
/>
</div>
</div>
<div css={tw`mb-6 md:w-full md:flex md:flex-row`}>
<div css={tw`mb-6 md:w-full md:flex md:flex-col md:mr-4 md:mb-0`}>
<Field
id={'disk'}
name={'disk'}
label={'Disk Limit'}
type={'number'}
description={'This server will not be allowed to boot if it is using more than this amount of space. If a server goes over this limit while running it will be safely stopped and locked until enough space is available. Set to 0 to allow unlimited disk usage.'}
/>
</div>
2021-08-05 04:16:52 +00:00
<div css={tw`mb-6 md:w-full md:flex md:flex-col md:ml-4 md:mb-0`}>
<Field
id={'io'}
name={'io'}
label={'Block IO Proportion'}
type={'number'}
description={'Advanced: The IO performance of this server relative to other running containers on the system. Value should be between 10 and 1000.'}
/>
</div>
</div>
<div css={tw`mb-6 md:w-full md:flex md:flex-row`}>
<div css={tw`bg-neutral-800 border border-neutral-900 shadow-inner p-4 rounded`}>
2021-08-05 04:16:52 +00:00
<FormikSwitch
name={'oomKiller'}
2021-08-05 04:16:52 +00:00
label={'Out of Memory Killer'}
description={'Enabling the Out of Memory Killer may cause server processes to exit unexpectedly.'}
2021-08-05 04:16:52 +00:00
/>
</div>
</div>
</AdminBox>
);
}
export function ServerSettingsContainer ({ server }: { server?: Server }) {
const { isSubmitting } = useFormikContext();
return (
2021-09-15 17:09:54 +00:00
<AdminBox icon={faCogs} title={'Settings'} css={tw`relative w-full`}>
<SpinnerOverlay visible={isSubmitting}/>
2021-08-05 04:16:52 +00:00
<div css={tw`mb-6 md:w-full md:flex md:flex-row`}>
<div css={tw`mb-6 md:w-full md:flex md:flex-col md:mr-4 md:mb-0`}>
<Field
id={'name'}
name={'name'}
label={'Server Name'}
type={'text'}
2021-08-05 04:16:52 +00:00
/>
</div>
<div css={tw`mb-6 md:w-full md:flex md:flex-col md:ml-4 md:mb-0`}>
<Field
id={'externalId'}
name={'externalId'}
label={'External Identifier'}
type={'text'}
2021-08-05 04:16:52 +00:00
/>
</div>
2021-08-05 04:16:52 +00:00
</div>
2021-08-05 04:16:52 +00:00
<div css={tw`mb-6 md:w-full md:flex md:flex-row`}>
<div css={tw`mb-6 w-full md:w-1/2 md:flex md:flex-col md:pr-4 md:mb-0`}>
<OwnerSelect selected={server?.relations.user || null}/>
</div>
2021-08-05 04:16:52 +00:00
</div>
</AdminBox>
);
}
export function ServerAllocationsContainer ({ server }: { server: Server }) {
2021-09-15 17:09:54 +00:00
const { isSubmitting } = useFormikContext();
const loadOptions = async (inputValue: string, callback: (options: Option[]) => void) => {
const allocations = await getAllocations(server.nodeId, { ip: inputValue, server_id: '0' });
callback(allocations.map(a => {
return { value: a.id.toString(), label: a.ip + ':' + a.port };
}));
};
2021-09-15 17:09:54 +00:00
return (
<AdminBox icon={faNetworkWired} title={'Networking'} css={tw`relative w-full`}>
2021-09-15 17:09:54 +00:00
<SpinnerOverlay visible={isSubmitting}/>
<div css={tw`mb-6`}>
<Label>Primary Allocation</Label>
<Select
id={'allocationId'}
name={'allocationId'}
>
{server.relations?.allocations?.map(a => (
<option key={a.id} value={a.id}>{a.ip}:{a.port}</option>
))}
</Select>
</div>
<AsyncSelectField
id={'addAllocations'}
name={'addAllocations'}
label={'Add Allocations'}
loadOptions={loadOptions}
isMulti
css={tw`mb-6`}
/>
<SelectField
id={'removeAllocations'}
name={'removeAllocations'}
label={'Remove Allocations'}
options={server.relations?.allocations?.map(a => {
return { value: a.id.toString(), label: a.ip + ':' + a.port };
}) || []}
isMulti
isSearchable
css={tw`mb-2`}
/>
2021-09-15 17:09:54 +00:00
</AdminBox>
);
}
type Values2 = Omit<Values, 'oomDisabled'> & { oomKiller: boolean };
export default function ServerSettingsContainer2 () {
const history = useHistory();
const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
const server = Context.useStoreState(state => state.server);
const setServer = Context.useStoreActions(actions => actions.setServer);
if (server === undefined) {
return (
<></>
);
}
const submit = (values: Values2, { setSubmitting, setFieldValue }: FormikHelpers<Values2>) => {
clearFlashes('server');
updateServer(server.id, { ...values, oomDisabled: !values.oomKiller }, [ 'allocations', 'user' ])
.then(s => {
setServer({ ...server, ...s });
// TODO: Figure out how to properly clear react-selects for allocations.
setFieldValue('addAllocations', []);
setFieldValue('removeAllocations', []);
})
.catch(error => {
console.error(error);
clearAndAddHttpError({ key: 'server', error });
})
.then(() => setSubmitting(false));
};
return (
<Formik
onSubmit={submit}
initialValues={{
externalId: server.externalId || '',
name: server.name,
ownerId: server.ownerId,
memory: server.limits.memory,
swap: server.limits.swap,
disk: server.limits.disk,
io: server.limits.io,
cpu: server.limits.cpu,
threads: server.limits.threads || '',
2021-09-15 17:09:54 +00:00
// Yes, this is named differently on purpose. Naming it like this makes the toggle switch
// be in an ON state when the oom killer is enabled, instead of when its disabled.
oomKiller: !server.limits.oomDisabled,
databases: server.featureLimits.databases,
allocations: server.featureLimits.allocations,
backups: server.featureLimits.backups,
allocationId: server.allocationId,
addAllocations: [] as number[],
removeAllocations: [] as number[],
}}
validationSchema={object().shape({
})}
>
2021-09-15 17:09:54 +00:00
{({ isSubmitting, isValid }) => (
<Form>
<div css={tw`grid grid-cols-2 gap-x-8`}>
<div css={tw`flex flex-col`}>
<div css={tw`flex mb-6`}>
<ServerSettingsContainer server={server}/>
</div>
2021-09-15 17:09:54 +00:00
<div css={tw`flex mb-6`}>
<ServerFeatureContainer/>
</div>
<div css={tw`flex mb-6`}>
<ServerAllocationsContainer server={server}/>
2021-09-15 17:09:54 +00:00
</div>
<div css={tw`bg-neutral-700 rounded shadow-md py-2 px-6`}>
<div css={tw`flex flex-row`}>
<ServerDeleteButton
serverId={server?.id}
onDeleted={() => history.push('/admin/servers')}
/>
<Button type="submit" size="small" css={tw`ml-auto`} disabled={isSubmitting || !isValid}>
Save Changes
</Button>
</div>
</div>
</div>
2021-09-15 17:09:54 +00:00
<div css={tw`flex flex-col`}>
<div css={tw`flex mb-8`}>
<ServerResourceContainer/>
</div>
</div>
</div>
</Form>
)}
</Formik>
);
}