ui(admin): server edit cleanup, fix startup form
This commit is contained in:
parent
95f3eb54db
commit
df895f4a9f
|
@ -16,18 +16,20 @@ class UpdateServerRequest extends ApplicationApiRequest
|
||||||
'name' => $rules['name'],
|
'name' => $rules['name'],
|
||||||
'description' => array_merge(['nullable'], $rules['description']),
|
'description' => array_merge(['nullable'], $rules['description']),
|
||||||
'owner_id' => $rules['owner_id'],
|
'owner_id' => $rules['owner_id'],
|
||||||
'oom_killer' => 'sometimes|boolean',
|
|
||||||
|
|
||||||
'memory' => $rules['memory'],
|
'limits' => 'sometimes|array',
|
||||||
'swap' => $rules['swap'],
|
'limits.memory' => $rules['memory'],
|
||||||
'disk' => $rules['disk'],
|
'limits.swap' => $rules['swap'],
|
||||||
'io' => $rules['io'],
|
'limits.disk' => $rules['disk'],
|
||||||
'threads' => $rules['threads'],
|
'limits.io' => $rules['io'],
|
||||||
'cpu' => $rules['cpu'],
|
'limits.threads' => $rules['threads'],
|
||||||
|
'limits.cpu' => $rules['cpu'],
|
||||||
|
'limits.oom_killer' => 'sometimes|boolean',
|
||||||
|
|
||||||
'databases' => $rules['database_limit'],
|
'feature_limits' => 'required|array',
|
||||||
'allocations' => $rules['allocation_limit'],
|
'feature_limits.allocations' => $rules['allocation_limit'],
|
||||||
'backups' => $rules['backup_limit'],
|
'feature_limits.backups' => $rules['backup_limit'],
|
||||||
|
'feature_limits.databases' => $rules['database_limit'],
|
||||||
|
|
||||||
'allocation_id' => 'bail|exists:allocations,id',
|
'allocation_id' => 'bail|exists:allocations,id',
|
||||||
'add_allocations' => 'bail|array',
|
'add_allocations' => 'bail|array',
|
||||||
|
@ -46,18 +48,18 @@ class UpdateServerRequest extends ApplicationApiRequest
|
||||||
'name' => array_get($data, 'name'),
|
'name' => array_get($data, 'name'),
|
||||||
'description' => array_get($data, 'description'),
|
'description' => array_get($data, 'description'),
|
||||||
'owner_id' => array_get($data, 'owner_id'),
|
'owner_id' => array_get($data, 'owner_id'),
|
||||||
'oom_disabled' => !array_get($data, 'oom_killer'),
|
|
||||||
|
|
||||||
'memory' => array_get($data, 'memory'),
|
'memory' => array_get($data, 'limits.memory'),
|
||||||
'swap' => array_get($data, 'swap'),
|
'swap' => array_get($data, 'limits.swap'),
|
||||||
'disk' => array_get($data, 'disk'),
|
'disk' => array_get($data, 'limits.disk'),
|
||||||
'io' => array_get($data, 'io'),
|
'io' => array_get($data, 'limits.io'),
|
||||||
'threads' => array_get($data, 'threads'),
|
'threads' => array_get($data, 'limits.threads'),
|
||||||
'cpu' => array_get($data, 'cpu'),
|
'cpu' => array_get($data, 'limits.cpu'),
|
||||||
|
'oom_disabled' => array_get($data, 'limits.oom_disabled'),
|
||||||
|
|
||||||
'database_limit' => array_get($data, 'databases'),
|
'allocation_limit' => array_get($data, 'feature_limits.allocations'),
|
||||||
'allocation_limit' => array_get($data, 'allocations'),
|
'backup_limit' => array_get($data, 'feature_limits.backups'),
|
||||||
'backup_limit' => array_get($data, 'backups'),
|
'database_limit' => array_get($data, 'feature_limits.databases'),
|
||||||
|
|
||||||
'allocation_id' => array_get($data, 'allocation_id'),
|
'allocation_id' => array_get($data, 'allocation_id'),
|
||||||
'add_allocations' => array_get($data, 'add_allocations'),
|
'add_allocations' => array_get($data, 'add_allocations'),
|
||||||
|
|
|
@ -9,24 +9,14 @@ class UpdateServerStartupRequest extends ApplicationApiRequest
|
||||||
{
|
{
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
$data = Server::getRulesForUpdate($this->route()->parameter('server')->id);
|
$rules = Server::getRulesForUpdate($this->route()->parameter('server')->id);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'startup' => $data['startup'],
|
'startup' => $rules['startup'],
|
||||||
'environment' => 'present|array',
|
'environment' => 'present|array',
|
||||||
'egg' => $data['egg_id'],
|
'egg_id' => $rules['egg_id'],
|
||||||
'image' => $data['image'],
|
'image' => $rules['image'],
|
||||||
'skip_scripts' => 'present|boolean',
|
'skip_scripts' => 'present|boolean',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validated(): array
|
|
||||||
{
|
|
||||||
$data = parent::validated();
|
|
||||||
|
|
||||||
return collect($data)->only(['startup', 'environment', 'skip_scripts'])->merge([
|
|
||||||
'egg_id' => array_get($data, 'egg'),
|
|
||||||
'docker_image' => array_get($data, 'image'),
|
|
||||||
])->toArray();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@ class ServerTransformer extends Transformer
|
||||||
'nest_id' => $model->nest_id,
|
'nest_id' => $model->nest_id,
|
||||||
'egg_id' => $model->egg_id,
|
'egg_id' => $model->egg_id,
|
||||||
'container' => [
|
'container' => [
|
||||||
'startup_command' => $model->startup,
|
'startup' => $model->startup,
|
||||||
'image' => $model->image,
|
'image' => $model->image,
|
||||||
'environment' => $this->environmentService->handle($model),
|
'environment' => $this->environmentService->handle($model),
|
||||||
],
|
],
|
||||||
|
|
|
@ -71,8 +71,7 @@ export interface Server {
|
||||||
eggId: number;
|
eggId: number;
|
||||||
|
|
||||||
container: {
|
container: {
|
||||||
startupCommand: string;
|
startup: string;
|
||||||
defaultStartup: string;
|
|
||||||
image: string;
|
image: string;
|
||||||
environment: Map<string, string>;
|
environment: Map<string, string>;
|
||||||
}
|
}
|
||||||
|
@ -121,8 +120,7 @@ export const rawDataToServer = ({ attributes }: FractalResponseData): Server =>
|
||||||
eggId: attributes.egg_id,
|
eggId: attributes.egg_id,
|
||||||
|
|
||||||
container: {
|
container: {
|
||||||
startupCommand: attributes.container.startup_command,
|
startup: attributes.container.startup,
|
||||||
defaultStartup: '',
|
|
||||||
image: attributes.container.image,
|
image: attributes.container.image,
|
||||||
environment: attributes.container.environment,
|
environment: attributes.container.environment,
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,17 +6,21 @@ export interface Values {
|
||||||
name: string;
|
name: string;
|
||||||
ownerId: number;
|
ownerId: number;
|
||||||
|
|
||||||
memory: number;
|
limits: {
|
||||||
swap: number;
|
memory: number;
|
||||||
disk: number;
|
swap: number;
|
||||||
io: number;
|
disk: number;
|
||||||
cpu: number;
|
io: number;
|
||||||
threads: string;
|
cpu: number;
|
||||||
oomDisabled: boolean;
|
threads: string;
|
||||||
|
oomDisabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
databases: number;
|
featureLimits: {
|
||||||
allocations: number;
|
allocations: number;
|
||||||
backups: number;
|
backups: number;
|
||||||
|
databases: number;
|
||||||
|
}
|
||||||
|
|
||||||
allocationId: number;
|
allocationId: number;
|
||||||
addAllocations: number[];
|
addAllocations: number[];
|
||||||
|
@ -24,16 +28,36 @@ export interface Values {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (id: number, server: Partial<Values>, include: string[] = []): Promise<Server> => {
|
export default (id: number, server: Partial<Values>, include: string[] = []): Promise<Server> => {
|
||||||
const data = {};
|
|
||||||
|
|
||||||
Object.keys(server).forEach((key) => {
|
|
||||||
const key2 = key.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
|
|
||||||
// @ts-ignore
|
|
||||||
data[key2] = server[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
http.patch(`/api/application/servers/${id}`, data, { params: { include: include.join(',') } })
|
http.patch(
|
||||||
|
`/api/application/servers/${id}`,
|
||||||
|
{
|
||||||
|
external_id: server.externalId,
|
||||||
|
name: server.name,
|
||||||
|
owner_id: server.ownerId,
|
||||||
|
|
||||||
|
limits: {
|
||||||
|
memory: server.limits?.memory,
|
||||||
|
swap: server.limits?.swap,
|
||||||
|
disk: server.limits?.disk,
|
||||||
|
io: server.limits?.io,
|
||||||
|
cpu: server.limits?.cpu,
|
||||||
|
threads: server.limits?.threads,
|
||||||
|
oom_disabled: server.limits?.oomDisabled,
|
||||||
|
},
|
||||||
|
|
||||||
|
feature_limits: {
|
||||||
|
allocations: server.featureLimits?.allocations,
|
||||||
|
backups: server.featureLimits?.backups,
|
||||||
|
databases: server.featureLimits?.databases,
|
||||||
|
},
|
||||||
|
|
||||||
|
allocation_id: server.allocationId,
|
||||||
|
add_allocations: server.addAllocations,
|
||||||
|
remove_allocations: server.removeAllocations,
|
||||||
|
},
|
||||||
|
{ params: { include: include.join(',') } }
|
||||||
|
)
|
||||||
.then(({ data }) => resolve(rawDataToServer(data)))
|
.then(({ data }) => resolve(rawDataToServer(data)))
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import http from '@/api/http';
|
||||||
|
import { Server, rawDataToServer } from '@/api/admin/servers/getServers';
|
||||||
|
|
||||||
|
export interface Values {
|
||||||
|
startup: string;
|
||||||
|
environment: Record<string, any>;
|
||||||
|
eggId: number;
|
||||||
|
image: string;
|
||||||
|
skipScripts: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (id: number, values: Partial<Values>, include: string[] = []): Promise<Server> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
http.patch(
|
||||||
|
`/api/application/servers/${id}/startup`,
|
||||||
|
{
|
||||||
|
startup: values.startup,
|
||||||
|
environment: values.environment,
|
||||||
|
egg_id: values.eggId,
|
||||||
|
image: values.image,
|
||||||
|
skip_scripts: values.skipScripts,
|
||||||
|
},
|
||||||
|
{ params: { include: include.join(',') } }
|
||||||
|
)
|
||||||
|
.then(({ data }) => resolve(rawDataToServer(data)))
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
};
|
|
@ -1,12 +1,39 @@
|
||||||
import Label from '@/components/elements/Label';
|
import Label from '@/components/elements/Label';
|
||||||
import Select from '@/components/elements/Select';
|
import Select from '@/components/elements/Select';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Egg } from '@/api/admin/eggs/getEgg';
|
import { Egg } from '@/api/admin/eggs/getEgg';
|
||||||
import searchEggs from '@/api/admin/nests/searchEggs';
|
import searchEggs from '@/api/admin/nests/searchEggs';
|
||||||
|
|
||||||
export default ({ nestId, egg, setEgg }: { nestId: number | null; egg: Egg | null, setEgg: (value: Egg | null) => void }) => {
|
export default ({ nestId, egg, setEgg }: { nestId: number | null; egg: Egg | null, setEgg: (value: Egg | null) => void }) => {
|
||||||
|
const { setFieldValue } = useFormikContext();
|
||||||
|
|
||||||
const [ eggs, setEggs ] = useState<Egg[]>([]);
|
const [ eggs, setEggs ] = useState<Egg[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* So you may be asking yourself, "what cluster-fuck of code is this?"
|
||||||
|
*
|
||||||
|
* Well, this code makes sure that when the egg changes, that the environment
|
||||||
|
* object has empty string values instead of undefined so React doesn't think
|
||||||
|
* the variable fields are uncontrolled.
|
||||||
|
*/
|
||||||
|
const setEgg2 = (newEgg: Egg | null) => {
|
||||||
|
if (newEgg === null) {
|
||||||
|
setEgg(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset all variables to be empty, don't inherit the previous values.
|
||||||
|
const newVariables = newEgg?.relations.variables;
|
||||||
|
newVariables?.forEach(v => setFieldValue('environment.' + v.envVariable, ''));
|
||||||
|
const variables = egg?.relations.variables?.filter(v => newVariables?.find(v2 => v2.envVariable === v.envVariable) === undefined);
|
||||||
|
|
||||||
|
setEgg(newEgg);
|
||||||
|
|
||||||
|
// Clear any variables that don't exist on the new egg.
|
||||||
|
variables?.forEach(v => setFieldValue('environment.' + v.envVariable, undefined));
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (nestId === null) {
|
if (nestId === null) {
|
||||||
return;
|
return;
|
||||||
|
@ -16,10 +43,10 @@ export default ({ nestId, egg, setEgg }: { nestId: number | null; egg: Egg | nul
|
||||||
.then(eggs => {
|
.then(eggs => {
|
||||||
setEggs(eggs);
|
setEggs(eggs);
|
||||||
if (eggs.length < 1) {
|
if (eggs.length < 1) {
|
||||||
setEgg(null);
|
setEgg2(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setEgg(eggs[0]);
|
setEgg2(eggs[0]);
|
||||||
})
|
})
|
||||||
.catch(error => console.error(error));
|
.catch(error => console.error(error));
|
||||||
}, [ nestId ]);
|
}, [ nestId ]);
|
||||||
|
@ -31,7 +58,7 @@ export default ({ nestId, egg, setEgg }: { nestId: number | null; egg: Egg | nul
|
||||||
defaultValue={egg?.id || undefined}
|
defaultValue={egg?.id || undefined}
|
||||||
id={'eggId'}
|
id={'eggId'}
|
||||||
name={'eggId'}
|
name={'eggId'}
|
||||||
onChange={e => setEgg(eggs.find(egg => egg.id.toString() === e.currentTarget.value) || null)}
|
onChange={e => setEgg2(eggs.find(egg => egg.id.toString() === e.currentTarget.value) || null)}
|
||||||
>
|
>
|
||||||
{eggs.map(v => (
|
{eggs.map(v => (
|
||||||
<option key={v.id} value={v.id.toString()}>
|
<option key={v.id} value={v.id.toString()}>
|
||||||
|
|
|
@ -14,6 +14,8 @@ import { ApplicationStore } from '@/state';
|
||||||
import { SubNavigation, SubNavigationLink } from '@/components/admin/SubNavigation';
|
import { SubNavigation, SubNavigationLink } from '@/components/admin/SubNavigation';
|
||||||
import ServerSettingsContainer from '@/components/admin/servers/ServerSettingsContainer';
|
import ServerSettingsContainer from '@/components/admin/servers/ServerSettingsContainer';
|
||||||
|
|
||||||
|
export const ServerIncludes = [ 'allocations', 'user', 'variables' ];
|
||||||
|
|
||||||
interface ctx {
|
interface ctx {
|
||||||
server: Server | undefined;
|
server: Server | undefined;
|
||||||
setServer: Action<ctx, Server | undefined>;
|
setServer: Action<ctx, Server | undefined>;
|
||||||
|
@ -40,7 +42,7 @@ const ServerRouter = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
clearFlashes('server');
|
clearFlashes('server');
|
||||||
|
|
||||||
getServer(Number(match.params?.id), [ 'allocations', 'user', 'variables' ])
|
getServer(Number(match.params?.id), ServerIncludes)
|
||||||
.then(server => setServer(server))
|
.then(server => setServer(server))
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
|
@ -14,140 +14,13 @@ import updateServer, { Values } 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, FormikHelpers, useFormikContext } from 'formik';
|
||||||
import { Context } from '@/components/admin/servers/ServerRouter';
|
import { Context, ServerIncludes } 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 OwnerSelect from '@/components/admin/servers/OwnerSelect';
|
import OwnerSelect from '@/components/admin/servers/OwnerSelect';
|
||||||
import Button from '@/components/elements/Button';
|
import Button from '@/components/elements/Button';
|
||||||
import FormikSwitch from '@/components/elements/FormikSwitch';
|
import FormikSwitch from '@/components/elements/FormikSwitch';
|
||||||
|
|
||||||
export function ServerFeatureContainer () {
|
|
||||||
const { isSubmitting } = useFormikContext();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AdminBox icon={faConciergeBell} title={'Feature Limits'} css={tw`relative w-full`}>
|
|
||||||
<SpinnerOverlay visible={isSubmitting}/>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<div css={tw`mb-6 md:w-full md:flex md:flex-col md:ml-4 md:mb-0`}>
|
|
||||||
<Field
|
|
||||||
id={'backups'}
|
|
||||||
name={'backups'}
|
|
||||||
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}/>
|
|
||||||
|
|
||||||
<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.'}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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.'}
|
|
||||||
/>
|
|
||||||
</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={'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>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<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`}>
|
|
||||||
<FormikSwitch
|
|
||||||
name={'oomKiller'}
|
|
||||||
label={'Out of Memory Killer'}
|
|
||||||
description={'Enabling the Out of Memory Killer may cause server processes to exit unexpectedly.'}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AdminBox>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ServerSettingsContainer ({ server }: { server?: Server }) {
|
export function ServerSettingsContainer ({ server }: { server?: Server }) {
|
||||||
const { isSubmitting } = useFormikContext();
|
const { isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
|
@ -184,6 +57,42 @@ export function ServerSettingsContainer ({ server }: { server?: Server }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ServerFeatureContainer () {
|
||||||
|
const { isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AdminBox icon={faConciergeBell} title={'Feature Limits'} css={tw`relative w-full`}>
|
||||||
|
<SpinnerOverlay visible={isSubmitting}/>
|
||||||
|
|
||||||
|
<div css={tw`grid grid-cols-1 md:grid-cols-3 gap-x-8 gap-y-6`}>
|
||||||
|
<Field
|
||||||
|
id={'featureLimits.allocations'}
|
||||||
|
name={'featureLimits.allocations'}
|
||||||
|
label={'Allocation Limit'}
|
||||||
|
type={'number'}
|
||||||
|
description={'The total number of allocations a user is allowed to create for this server.'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Field
|
||||||
|
id={'featureLimits.backups'}
|
||||||
|
name={'featureLimits.backups'}
|
||||||
|
label={'Backup Limit'}
|
||||||
|
type={'number'}
|
||||||
|
description={'The total number of backups that can be created for this server.'}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Field
|
||||||
|
id={'featureLimits.databases'}
|
||||||
|
name={'featureLimits.databases'}
|
||||||
|
label={'Database Limit'}
|
||||||
|
type={'number'}
|
||||||
|
description={'The total number of databases a user is allowed to create for this server.'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</AdminBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function ServerAllocationsContainer ({ server }: { server: Server }) {
|
export function ServerAllocationsContainer ({ server }: { server: Server }) {
|
||||||
const { isSubmitting } = useFormikContext();
|
const { isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
|
@ -234,7 +143,90 @@ export function ServerAllocationsContainer ({ server }: { server: Server }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type Values2 = Omit<Values, 'oomDisabled'> & { oomKiller: boolean };
|
export function ServerResourceContainer () {
|
||||||
|
const { isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AdminBox icon={faBalanceScale} title={'Resources'} css={tw`relative w-full`}>
|
||||||
|
<SpinnerOverlay visible={isSubmitting}/>
|
||||||
|
|
||||||
|
<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={'limits.cpu'}
|
||||||
|
name={'limits.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.'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div css={tw`mb-6 md:w-full md:flex md:flex-col md:ml-4 md:mb-0`}>
|
||||||
|
<Field
|
||||||
|
id={'limits.threads'}
|
||||||
|
name={'limits.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.'}
|
||||||
|
/>
|
||||||
|
</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={'limits.memory'}
|
||||||
|
name={'limits.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>
|
||||||
|
|
||||||
|
<div css={tw`mb-6 md:w-full md:flex md:flex-col md:ml-4 md:mb-0`}>
|
||||||
|
<Field
|
||||||
|
id={'limits.swap'}
|
||||||
|
name={'limits.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={'limits.disk'}
|
||||||
|
name={'limits.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>
|
||||||
|
|
||||||
|
<div css={tw`mb-6 md:w-full md:flex md:flex-col md:ml-4 md:mb-0`}>
|
||||||
|
<Field
|
||||||
|
id={'limits.io'}
|
||||||
|
name={'limits.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`}>
|
||||||
|
<FormikSwitch
|
||||||
|
name={'limits.oomDisabled'}
|
||||||
|
label={'Out of Memory Killer'}
|
||||||
|
description={'Enabling the Out of Memory Killer may cause server processes to exit unexpectedly.'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AdminBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function ServerSettingsContainer2 ({ server }: { server: Server }) {
|
export default function ServerSettingsContainer2 ({ server }: { server: Server }) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
@ -243,10 +235,14 @@ export default function ServerSettingsContainer2 ({ server }: { server: Server }
|
||||||
|
|
||||||
const setServer = Context.useStoreActions(actions => actions.setServer);
|
const setServer = Context.useStoreActions(actions => actions.setServer);
|
||||||
|
|
||||||
const submit = (values: Values2, { setSubmitting, setFieldValue }: FormikHelpers<Values2>) => {
|
const submit = (values: Values, { setSubmitting, setFieldValue }: FormikHelpers<Values>) => {
|
||||||
clearFlashes('server');
|
clearFlashes('server');
|
||||||
|
|
||||||
updateServer(server.id, { ...values, oomDisabled: !values.oomKiller }, [ 'allocations', 'user' ])
|
// This value is inverted to have the switch be on when the
|
||||||
|
// OOM Killer is enabled, rather than when disabled.
|
||||||
|
values.limits.oomDisabled = !values.limits.oomDisabled;
|
||||||
|
|
||||||
|
updateServer(server.id, values, ServerIncludes)
|
||||||
.then(s => {
|
.then(s => {
|
||||||
setServer({ ...server, ...s });
|
setServer({ ...server, ...s });
|
||||||
|
|
||||||
|
@ -269,19 +265,23 @@ export default function ServerSettingsContainer2 ({ server }: { server: Server }
|
||||||
name: server.name,
|
name: server.name,
|
||||||
ownerId: server.ownerId,
|
ownerId: server.ownerId,
|
||||||
|
|
||||||
memory: server.limits.memory,
|
limits: {
|
||||||
swap: server.limits.swap,
|
memory: server.limits.memory,
|
||||||
disk: server.limits.disk,
|
swap: server.limits.swap,
|
||||||
io: server.limits.io,
|
disk: server.limits.disk,
|
||||||
cpu: server.limits.cpu,
|
io: server.limits.io,
|
||||||
threads: server.limits.threads || '',
|
cpu: server.limits.cpu,
|
||||||
// Yes, this is named differently on purpose. Naming it like this makes the toggle switch
|
threads: server.limits.threads || '',
|
||||||
// be in an ON state when the oom killer is enabled, instead of when its disabled.
|
// This value is inverted to have the switch be on when the
|
||||||
oomKiller: !server.limits.oomDisabled,
|
// OOM Killer is enabled, rather than when disabled.
|
||||||
|
oomDisabled: !server.limits.oomDisabled,
|
||||||
|
},
|
||||||
|
|
||||||
databases: server.featureLimits.databases,
|
featureLimits: {
|
||||||
allocations: server.featureLimits.allocations,
|
allocations: server.featureLimits.allocations,
|
||||||
backups: server.featureLimits.backups,
|
backups: server.featureLimits.backups,
|
||||||
|
databases: server.featureLimits.databases,
|
||||||
|
},
|
||||||
|
|
||||||
allocationId: server.allocationId,
|
allocationId: server.allocationId,
|
||||||
addAllocations: [] as number[],
|
addAllocations: [] as number[],
|
||||||
|
|
|
@ -1,30 +1,22 @@
|
||||||
import getEgg, { Egg, EggVariable } from '@/api/admin/eggs/getEgg';
|
import getEgg, { Egg, EggVariable } from '@/api/admin/eggs/getEgg';
|
||||||
import { Server } from '@/api/admin/servers/getServers';
|
import { Server } from '@/api/admin/servers/getServers';
|
||||||
|
import updateServerStartup, { Values } from '@/api/admin/servers/updateServerStartup';
|
||||||
import EggSelect from '@/components/admin/servers/EggSelect';
|
import EggSelect from '@/components/admin/servers/EggSelect';
|
||||||
import NestSelect from '@/components/admin/servers/NestSelect';
|
import NestSelect from '@/components/admin/servers/NestSelect';
|
||||||
|
import { Context, ServerIncludes } from '@/components/admin/servers/ServerRouter';
|
||||||
import FormikSwitch from '@/components/elements/FormikSwitch';
|
import FormikSwitch from '@/components/elements/FormikSwitch';
|
||||||
import InputSpinner from '@/components/elements/InputSpinner';
|
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import Button from '@/components/elements/Button';
|
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 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, useFormikContext } from 'formik';
|
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
|
||||||
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 TitledGreyBox from '@/components/elements/TitledGreyBox';
|
import { object } from 'yup';
|
||||||
|
|
||||||
// interface Values {
|
|
||||||
// startupCommand: string;
|
|
||||||
// image: string;
|
|
||||||
//
|
|
||||||
// eggId: number;
|
|
||||||
// skipScripts: boolean;
|
|
||||||
// }
|
|
||||||
|
|
||||||
function ServerStartupLineContainer ({ egg }: { egg: Egg }) {
|
function ServerStartupLineContainer ({ egg }: { egg: Egg }) {
|
||||||
const { isSubmitting } = useFormikContext();
|
const { isSubmitting } = useFormikContext();
|
||||||
|
@ -33,22 +25,20 @@ function ServerStartupLineContainer ({ egg }: { egg: Egg }) {
|
||||||
<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`}>
|
<div css={tw`mb-6`}>
|
||||||
<div css={tw`mb-6`}>
|
<Field
|
||||||
<Field
|
id={'startup'}
|
||||||
id={'startupCommand'}
|
name={'startup'}
|
||||||
name={'startupCommand'}
|
label={'Startup Command'}
|
||||||
label={'Startup Command'}
|
type={'text'}
|
||||||
type={'text'}
|
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>
|
<div>
|
||||||
<Label>Default Startup Command</Label>
|
<Label>Default Startup Command</Label>
|
||||||
<Input value={egg.startup} readOnly/>
|
<Input value={egg.startup} readOnly/>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
|
||||||
</AdminBox>
|
</AdminBox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -62,23 +52,21 @@ function ServerServiceContainer ({ server, egg, setEgg }: { server: Server, egg:
|
||||||
<AdminBox title={'Service Configuration'} css={tw`relative w-full`}>
|
<AdminBox title={'Service Configuration'} css={tw`relative w-full`}>
|
||||||
<SpinnerOverlay visible={isSubmitting}/>
|
<SpinnerOverlay visible={isSubmitting}/>
|
||||||
|
|
||||||
<Form css={tw`mb-0`}>
|
<div css={tw`mb-6`}>
|
||||||
<div css={tw`mb-6`}>
|
<NestSelect nestId={nestId} setNestId={setNestId}/>
|
||||||
<NestSelect nestId={nestId} setNestId={setNestId}/>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div css={tw`mb-6`}>
|
<div css={tw`mb-6`}>
|
||||||
<EggSelect nestId={nestId} egg={egg} setEgg={setEgg}/>
|
<EggSelect nestId={nestId} egg={egg} setEgg={setEgg}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div css={tw`bg-neutral-800 border border-neutral-900 shadow-inner p-4 rounded`}>
|
<div css={tw`bg-neutral-800 border border-neutral-900 shadow-inner p-4 rounded`}>
|
||||||
<FormikSwitch
|
<FormikSwitch
|
||||||
name={'skipScript'}
|
name={'skipScript'}
|
||||||
label={'Skip Egg Install Script'}
|
label={'Skip Egg Install Script'}
|
||||||
description={'SoonTM'}
|
description={'SoonTM'}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
|
||||||
</AdminBox>
|
</AdminBox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -90,52 +78,119 @@ function ServerImageContainer () {
|
||||||
<AdminBox title={'Image Configuration'} css={tw`relative w-full`}>
|
<AdminBox title={'Image Configuration'} css={tw`relative w-full`}>
|
||||||
<SpinnerOverlay visible={isSubmitting}/>
|
<SpinnerOverlay visible={isSubmitting}/>
|
||||||
|
|
||||||
<Form css={tw`mb-0`}>
|
<div css={tw`md:w-full md:flex md:flex-col`}>
|
||||||
<div css={tw`md:w-full md:flex md:flex-col`}>
|
<div>
|
||||||
<div>
|
<Field
|
||||||
<Field
|
id={'image'}
|
||||||
id={'image'}
|
name={'image'}
|
||||||
name={'image'}
|
label={'Docker Image'}
|
||||||
label={'Docker Image'}
|
type={'text'}
|
||||||
type={'text'}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</div>
|
||||||
</AdminBox>
|
</AdminBox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ServerVariableContainer ({ variable, defaultValue }: { variable: EggVariable, defaultValue: string }) {
|
function ServerVariableContainer ({ variable, defaultValue }: { variable: EggVariable, defaultValue: string }) {
|
||||||
const [ value, setValue ] = useState<string>('');
|
const key = 'environment.' + variable.envVariable;
|
||||||
|
|
||||||
|
const { isSubmitting, setFieldValue } = useFormikContext();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValue(defaultValue);
|
setFieldValue(key, defaultValue);
|
||||||
}, [ defaultValue ]);
|
|
||||||
|
// return () => {
|
||||||
|
// setFieldValue(key, undefined);
|
||||||
|
// };
|
||||||
|
}, [ variable, defaultValue ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdminBox title={<p css={tw`text-sm uppercase`}>{variable.name}</p>}>
|
<AdminBox css={tw`relative w-full`} title={<p css={tw`text-sm uppercase`}>{variable.name}</p>}>
|
||||||
<InputSpinner visible={false}>
|
<SpinnerOverlay visible={isSubmitting}/>
|
||||||
<Input
|
|
||||||
name={variable.envVariable}
|
<Field
|
||||||
placeholder={variable.defaultValue}
|
id={key}
|
||||||
type={'text'}
|
name={key}
|
||||||
value={value}
|
type={'text'}
|
||||||
onChange={e => setValue(e.target.value)}
|
placeholder={variable.defaultValue}
|
||||||
/>
|
description={variable.description}
|
||||||
</InputSpinner>
|
/>
|
||||||
<p css={tw`mt-1 text-xs text-neutral-300`}>
|
|
||||||
{variable.description}
|
|
||||||
</p>
|
|
||||||
</AdminBox>
|
</AdminBox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ServerStartupContainer ({ server }: { server: Server }) {
|
function ServerStartupForm ({ server }: { server: Server }) {
|
||||||
const { clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
const { isSubmitting, isValid, setFieldValue } = useFormikContext();
|
||||||
|
|
||||||
const [ egg, setEgg ] = useState<Egg | null>(null);
|
const [ egg, setEgg ] = useState<Egg | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getEgg(server.eggId, [ 'variables' ])
|
||||||
|
.then(egg => {
|
||||||
|
if (egg.relations.variables === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
egg.relations.variables?.forEach(v => setFieldValue('environment.' + v.envVariable, ''));
|
||||||
|
setEgg(egg);
|
||||||
|
})
|
||||||
|
.catch(error => console.error(error));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (egg === null) {
|
||||||
|
return (<></>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form>
|
||||||
|
<div css={tw`flex flex-col`}>
|
||||||
|
<div css={tw`flex flex-row mb-6`}>
|
||||||
|
<ServerStartupLineContainer egg={egg}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div css={tw`grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 mb-6`}>
|
||||||
|
<div css={tw`flex`}>
|
||||||
|
<ServerServiceContainer
|
||||||
|
server={server}
|
||||||
|
egg={egg}
|
||||||
|
setEgg={setEgg}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div css={tw`flex`}>
|
||||||
|
<ServerImageContainer/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div css={tw`grid grid-cols-1 md:grid-cols-2 gap-y-6 gap-x-8`}>
|
||||||
|
{egg.relations.variables?.map((v, i) => (
|
||||||
|
<ServerVariableContainer
|
||||||
|
key={i}
|
||||||
|
variable={v}
|
||||||
|
defaultValue={server.relations?.variables.find(v2 => v.eggId === v2.eggId && v.envVariable === v2.envVariable)?.serverValue || ''}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div css={tw`bg-neutral-700 rounded shadow-md py-2 pr-6 mt-6`}>
|
||||||
|
<div css={tw`flex flex-row`}>
|
||||||
|
<Button type="submit" size="small" css={tw`ml-auto`} disabled={isSubmitting || !isValid}>
|
||||||
|
Save Changes
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ServerStartupContainer ({ server }: { server: Server }) {
|
||||||
|
const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
||||||
|
|
||||||
|
const [ egg, setEgg ] = useState<Egg | null>(null);
|
||||||
|
|
||||||
|
const setServer = Context.useStoreActions(actions => actions.setServer);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getEgg(server.eggId, [ 'variables' ])
|
getEgg(server.eggId, [ 'variables' ])
|
||||||
.then(egg => setEgg(egg))
|
.then(egg => setEgg(egg))
|
||||||
|
@ -146,61 +201,35 @@ export default function ServerStartupContainer ({ server }: { server: Server })
|
||||||
return (<></>);
|
return (<></>);
|
||||||
}
|
}
|
||||||
|
|
||||||
const submit = () => {
|
const submit = (values: Values, { setSubmitting }: FormikHelpers<Values>) => {
|
||||||
clearFlashes('server');
|
clearFlashes('server');
|
||||||
|
|
||||||
|
updateServerStartup(server.id, values, ServerIncludes)
|
||||||
|
.then(s => {
|
||||||
|
setServer({ ...server, ...s });
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
clearAndAddHttpError({ key: 'server', error });
|
||||||
|
})
|
||||||
|
.then(() => setSubmitting(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Formik
|
<Formik
|
||||||
onSubmit={submit}
|
onSubmit={submit}
|
||||||
initialValues={{
|
initialValues={{
|
||||||
startupCommand: server.container.startupCommand,
|
startup: server.container.startup,
|
||||||
|
// Don't ask.
|
||||||
|
environment: Object.fromEntries(egg.relations.variables?.map(v => [ v.envVariable, '' ]) || []),
|
||||||
image: server.container.image,
|
image: server.container.image,
|
||||||
eggId: 0,
|
eggId: server.eggId,
|
||||||
skipScripts: false,
|
skipScripts: false,
|
||||||
}}
|
}}
|
||||||
validationSchema={object().shape({})}
|
validationSchema={object().shape({
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
{({ isSubmitting, isValid }) => (
|
<ServerStartupForm server={server}/>
|
||||||
<div css={tw`flex flex-col`}>
|
|
||||||
<div css={tw`flex flex-row mb-6`}>
|
|
||||||
<ServerStartupLineContainer egg={egg}/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div css={tw`grid grid-cols-1 md:grid-cols-2 md:gap-x-8 gap-y-6 md:gap-y-0 mb-6`}>
|
|
||||||
<div css={tw`flex`}>
|
|
||||||
<ServerServiceContainer
|
|
||||||
server={server}
|
|
||||||
egg={egg}
|
|
||||||
setEgg={setEgg}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div css={tw`flex`}>
|
|
||||||
<ServerImageContainer/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{egg !== null &&
|
|
||||||
<div css={tw`grid gap-y-6 gap-x-8 grid-cols-1 md:grid-cols-2`}>
|
|
||||||
{egg.relations.variables?.map((v, i) => (
|
|
||||||
<ServerVariableContainer
|
|
||||||
key={i}
|
|
||||||
variable={v}
|
|
||||||
defaultValue={server.relations?.variables.find(v2 => v.eggId === v2.eggId && v.envVariable === v2.envVariable)?.serverValue || ''}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div css={tw`bg-neutral-700 rounded shadow-md py-2 pr-6 mt-6`}>
|
|
||||||
<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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ const FormikSwitch = ({ name, label, ...props }: SwitchProps) => {
|
||||||
form.setFieldTouched(name);
|
form.setFieldTouched(name);
|
||||||
form.setFieldValue(field.name, !field.value);
|
form.setFieldValue(field.name, !field.value);
|
||||||
}}
|
}}
|
||||||
defaultChecked={field.value}
|
defaultChecked={ field.value}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -165,7 +165,7 @@ Route::group(['prefix' => '/servers'], function () {
|
||||||
Route::get('/external/{external_id}', [\Pterodactyl\Http\Controllers\Api\Application\Servers\ExternalServerController::class, 'index']);
|
Route::get('/external/{external_id}', [\Pterodactyl\Http\Controllers\Api\Application\Servers\ExternalServerController::class, 'index']);
|
||||||
|
|
||||||
Route::patch('/{server}', [\Pterodactyl\Http\Controllers\Api\Application\Servers\ServerController::class, 'update']);
|
Route::patch('/{server}', [\Pterodactyl\Http\Controllers\Api\Application\Servers\ServerController::class, 'update']);
|
||||||
Route::patch('/{server}/build', [\Pterodactyl\Http\Controllers\Api\Application\Servers\ServerDetailsController::class, 'build']);
|
// Route::patch('/{server}/build', [\Pterodactyl\Http\Controllers\Api\Application\Servers\ServerDetailsController::class, 'build']);
|
||||||
Route::patch('/{server}/startup', [\Pterodactyl\Http\Controllers\Api\Application\Servers\StartupController::class, 'index']);
|
Route::patch('/{server}/startup', [\Pterodactyl\Http\Controllers\Api\Application\Servers\StartupController::class, 'index']);
|
||||||
|
|
||||||
Route::post('/', [\Pterodactyl\Http\Controllers\Api\Application\Servers\ServerController::class, 'store']);
|
Route::post('/', [\Pterodactyl\Http\Controllers\Api\Application\Servers\ServerController::class, 'store']);
|
||||||
|
|
Loading…
Reference in New Issue