forked from Alex/Pterodactyl-Panel
Cleanup more of the server UI logic
This commit is contained in:
parent
e3aca937b5
commit
f6998018b4
@ -1,8 +1,9 @@
|
||||
import { Model, UUID } from '@/api/admin/index';
|
||||
|
||||
export interface Egg extends Model {
|
||||
id: string;
|
||||
id: number;
|
||||
uuid: UUID;
|
||||
startup: string;
|
||||
relationships: {
|
||||
variables?: EggVariable[];
|
||||
};
|
||||
|
@ -25,6 +25,7 @@ export interface Allocation extends Model {
|
||||
node?: Node;
|
||||
server?: Server | null;
|
||||
};
|
||||
getDisplayText(): string;
|
||||
}
|
||||
|
||||
export interface Node extends Model {
|
||||
|
@ -168,5 +168,10 @@ export class AdminTransformers {
|
||||
node: transform(attributes.relationships?.node as FractalResponseData, this.toNode),
|
||||
server: transform(attributes.relationships?.server as FractalResponseData, this.toServer),
|
||||
},
|
||||
getDisplayText (): string {
|
||||
const raw = `${this.ip}:${this.port}`;
|
||||
|
||||
return !this.alias ? raw : `${this.alias} (${raw})`;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -3,37 +3,31 @@ import { NavLink } from 'react-router-dom';
|
||||
import tw, { styled } from 'twin.macro';
|
||||
|
||||
export const SubNavigation = styled.div`
|
||||
${tw`flex flex-row items-center flex-shrink-0 h-12 mb-4 border-b border-neutral-700`};
|
||||
${tw`flex flex-row items-center flex-shrink-0 h-12 mb-4 border-b border-neutral-700`};
|
||||
|
||||
& > div {
|
||||
${tw`flex flex-col justify-center flex-shrink-0 h-full`};
|
||||
& > a {
|
||||
${tw`flex flex-row items-center h-full px-4 border-b text-neutral-300 text-base whitespace-nowrap border-transparent`};
|
||||
|
||||
& > a {
|
||||
${tw`flex flex-row items-center h-full px-4 border-t text-neutral-300`};
|
||||
border-top-color: transparent !important;
|
||||
|
||||
& > svg {
|
||||
${tw`w-6 h-6 mr-2`};
|
||||
}
|
||||
|
||||
& > span {
|
||||
${tw`text-base whitespace-nowrap`};
|
||||
}
|
||||
|
||||
&:active, &.active {
|
||||
${tw`border-b text-primary-300 border-primary-300`};
|
||||
}
|
||||
}
|
||||
& > svg {
|
||||
${tw`w-6 h-6 mr-2`};
|
||||
}
|
||||
|
||||
&:active, &.active {
|
||||
${tw`text-primary-300 border-primary-300`};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const SubNavigationLink = ({ to, name, children }: { to: string, name: string, children: React.ReactNode }) => {
|
||||
interface Props {
|
||||
to: string;
|
||||
name: string;
|
||||
icon: React.ComponentType;
|
||||
}
|
||||
|
||||
export const SubNavigationLink = ({ to, name, icon: IconComponent }: Props) => {
|
||||
return (
|
||||
<div>
|
||||
<NavLink to={to} exact>
|
||||
{children}
|
||||
<span>{name}</span>
|
||||
</NavLink>
|
||||
</div>
|
||||
<NavLink to={to} exact>
|
||||
<IconComponent css={tw`w-6 h-6 mr-2`}/>{name}
|
||||
</NavLink>
|
||||
);
|
||||
};
|
||||
|
@ -5,27 +5,29 @@ import tw from 'twin.macro';
|
||||
import Button from '@/components/elements/Button';
|
||||
import ConfirmationModal from '@/components/elements/ConfirmationModal';
|
||||
import deleteServer from '@/api/admin/servers/deleteServer';
|
||||
import { TrashIcon } from '@heroicons/react/outline';
|
||||
import { useServerFromRoute } from '@/api/admin/server';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
interface Props {
|
||||
serverId: number;
|
||||
onDeleted: () => void;
|
||||
}
|
||||
|
||||
export default ({ serverId, onDeleted }: Props) => {
|
||||
export default () => {
|
||||
const history = useHistory();
|
||||
const [ visible, setVisible ] = useState(false);
|
||||
const [ loading, setLoading ] = useState(false);
|
||||
const { data: server } = useServerFromRoute();
|
||||
|
||||
const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
||||
const {
|
||||
clearFlashes,
|
||||
clearAndAddHttpError,
|
||||
} = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
||||
|
||||
const onDelete = () => {
|
||||
if (!server) return;
|
||||
|
||||
setLoading(true);
|
||||
clearFlashes('server');
|
||||
|
||||
deleteServer(serverId)
|
||||
.then(() => {
|
||||
setLoading(false);
|
||||
onDeleted();
|
||||
})
|
||||
deleteServer(server.id)
|
||||
.then(() => history.push('/admin/servers'))
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
clearAndAddHttpError({ key: 'server', error });
|
||||
@ -35,6 +37,8 @@ export default ({ serverId, onDeleted }: Props) => {
|
||||
});
|
||||
};
|
||||
|
||||
if (!server) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConfirmationModal
|
||||
@ -47,11 +51,14 @@ export default ({ serverId, onDeleted }: Props) => {
|
||||
>
|
||||
Are you sure you want to delete this server?
|
||||
</ConfirmationModal>
|
||||
|
||||
<Button type={'button'} size={'xsmall'} color={'red'} onClick={() => setVisible(true)}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" css={tw`h-5 w-5`}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
<Button
|
||||
type={'button'}
|
||||
size={'small'}
|
||||
color={'red'}
|
||||
onClick={() => setVisible(true)}
|
||||
css={tw`flex items-center justify-center`}
|
||||
>
|
||||
<TrashIcon css={tw`w-5 h-5 mr-2`}/> Delete Server
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
|
@ -1,17 +1,13 @@
|
||||
import React from 'react';
|
||||
import AdminBox from '@/components/admin/AdminBox';
|
||||
import tw from 'twin.macro';
|
||||
import { Context } from '@/components/admin/servers/ServerRouter';
|
||||
import Button from '@/components/elements/Button';
|
||||
import { useServerFromRoute } from '@/api/admin/server';
|
||||
|
||||
const ServerManageContainer = () => {
|
||||
const server = Context.useStoreState(state => state.server);
|
||||
export default () => {
|
||||
const { data: server } = useServerFromRoute();
|
||||
|
||||
if (server === undefined) {
|
||||
return (
|
||||
<></>
|
||||
);
|
||||
}
|
||||
if (!server) return null;
|
||||
|
||||
return (
|
||||
<div css={tw`grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-x-2 gap-y-2`}>
|
||||
@ -52,17 +48,3 @@ const ServerManageContainer = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default () => {
|
||||
const server = Context.useStoreState(state => state.server);
|
||||
|
||||
if (server === undefined) {
|
||||
return (
|
||||
<></>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ServerManageContainer/>
|
||||
);
|
||||
};
|
||||
|
@ -4,8 +4,6 @@ import React, { useEffect } from 'react';
|
||||
import { useLocation } from 'react-router';
|
||||
import tw from 'twin.macro';
|
||||
import { Route, Switch, useRouteMatch } from 'react-router-dom';
|
||||
import { action, Action, createContextStore } from 'easy-peasy';
|
||||
import { Server } from '@/api/admin/servers/getServers';
|
||||
import AdminContentBlock from '@/components/admin/AdminContentBlock';
|
||||
import Spinner from '@/components/elements/Spinner';
|
||||
import FlashMessageRender from '@/components/FlashMessageRender';
|
||||
@ -13,23 +11,9 @@ import { SubNavigation, SubNavigationLink } from '@/components/admin/SubNavigati
|
||||
import ServerSettingsContainer from '@/components/admin/servers/ServerSettingsContainer';
|
||||
import useFlash from '@/plugins/useFlash';
|
||||
import { useServerFromRoute } from '@/api/admin/server';
|
||||
import { AdjustmentsIcon, CogIcon, DatabaseIcon, FolderIcon, ShieldExclamationIcon } from '@heroicons/react/outline';
|
||||
|
||||
export const ServerIncludes = [ 'allocations', 'user', 'variables' ];
|
||||
|
||||
interface ctx {
|
||||
server: Server | undefined;
|
||||
setServer: Action<ctx, Server | undefined>;
|
||||
}
|
||||
|
||||
export const Context = createContextStore<ctx>({
|
||||
server: undefined,
|
||||
|
||||
setServer: action((state, payload) => {
|
||||
state.server = payload;
|
||||
}),
|
||||
});
|
||||
|
||||
const ServerRouter = () => {
|
||||
export default () => {
|
||||
const location = useLocation();
|
||||
const match = useRouteMatch<{ id?: string }>();
|
||||
|
||||
@ -41,11 +25,8 @@ const ServerRouter = () => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!error) {
|
||||
clearFlashes('server');
|
||||
} else {
|
||||
clearAndAddHttpError({ error, key: 'server' });
|
||||
}
|
||||
if (!error) clearFlashes('server');
|
||||
if (error) clearAndAddHttpError({ error, key: 'server' });
|
||||
}, [ error ]);
|
||||
|
||||
if (!server || (error && isValidating)) {
|
||||
@ -67,60 +48,18 @@ const ServerRouter = () => {
|
||||
</div>
|
||||
<FlashMessageRender byKey={'server'} css={tw`mb-4`}/>
|
||||
<SubNavigation>
|
||||
<SubNavigationLink to={`${match.url}`} name={'Settings'}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
clipRule="evenodd"
|
||||
fillRule="evenodd"
|
||||
d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z"
|
||||
/>
|
||||
</svg>
|
||||
</SubNavigationLink>
|
||||
<SubNavigationLink to={`${match.url}/startup`} name={'Startup'}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M5 4a1 1 0 00-2 0v7.268a2 2 0 000 3.464V16a1 1 0 102 0v-1.268a2 2 0 000-3.464V4zM11 4a1 1 0 10-2 0v1.268a2 2 0 000 3.464V16a1 1 0 102 0V8.732a2 2 0 000-3.464V4zM16 3a1 1 0 011 1v7.268a2 2 0 010 3.464V16a1 1 0 11-2 0v-1.268a2 2 0 010-3.464V4a1 1 0 011-1z"/>
|
||||
</svg>
|
||||
</SubNavigationLink>
|
||||
<SubNavigationLink to={`${match.url}/databases`} name={'Databases'}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
clipRule="evenodd"
|
||||
fillRule="evenodd"
|
||||
d="M3 12v3c0 1.657 3.134 3 7 3s7-1.343 7-3v-3c0 1.657-3.134 3-7 3s-7-1.343-7-3z"
|
||||
/>
|
||||
<path
|
||||
clipRule="evenodd"
|
||||
fillRule="evenodd"
|
||||
d="M3 7v3c0 1.657 3.134 3 7 3s7-1.343 7-3V7c0 1.657-3.134 3-7 3S3 8.657 3 7z"
|
||||
/>
|
||||
<path
|
||||
clipRule="evenodd"
|
||||
fillRule="evenodd"
|
||||
d="M17 5c0 1.657-3.134 3-7 3S3 6.657 3 5s3.134-3 7-3 7 1.343 7 3z"
|
||||
/>
|
||||
</svg>
|
||||
</SubNavigationLink>
|
||||
<SubNavigationLink to={`${match.url}/mounts`} name={'Mounts'}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"/>
|
||||
</svg>
|
||||
</SubNavigationLink>
|
||||
<SubNavigationLink to={`${match.url}/manage`} name={'Manage'}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10 1.944A11.954 11.954 0 012.166 5C2.056 5.649 2 6.319 2 7c0 5.225 3.34 9.67 8 11.317C14.66 16.67 18 12.225 18 7c0-.682-.057-1.35-.166-2.001A11.954 11.954 0 0110 1.944zM11 14a1 1 0 11-2 0 1 1 0 012 0zm0-7a1 1 0 10-2 0v3a1 1 0 102 0V7z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</SubNavigationLink>
|
||||
<SubNavigationLink to={`${match.url}`} name={'Settings'} icon={CogIcon}/>
|
||||
<SubNavigationLink to={`${match.url}/startup`} name={'Startup'} icon={AdjustmentsIcon}/>
|
||||
<SubNavigationLink to={`${match.url}/databases`} name={'Databases'} icon={DatabaseIcon}/>
|
||||
<SubNavigationLink to={`${match.url}/mounts`} name={'Mounts'} icon={FolderIcon}/>
|
||||
<SubNavigationLink to={`${match.url}/manage`} name={'Manage'} icon={ShieldExclamationIcon}/>
|
||||
</SubNavigation>
|
||||
<Switch location={location}>
|
||||
<Route path={`${match.path}`} exact>
|
||||
<ServerSettingsContainer server={server}/>
|
||||
<ServerSettingsContainer/>
|
||||
</Route>
|
||||
<Route path={`${match.path}/startup`} exact>
|
||||
<ServerStartupContainer server={server}/>
|
||||
<ServerStartupContainer/>
|
||||
</Route>
|
||||
<Route path={`${match.path}/manage`} exact>
|
||||
<ServerManageContainer/>
|
||||
@ -129,11 +68,3 @@ const ServerRouter = () => {
|
||||
</AdminContentBlock>
|
||||
);
|
||||
};
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<Context.Provider>
|
||||
<ServerRouter/>
|
||||
</Context.Provider>
|
||||
);
|
||||
};
|
||||
|
@ -1,118 +1,22 @@
|
||||
import { Server } from '@/api/admin/servers/getServers';
|
||||
import { useServerFromRoute } from '@/api/admin/server';
|
||||
import ServerDeleteButton from '@/components/admin/servers/ServerDeleteButton';
|
||||
import { faBalanceScale } 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, ServerIncludes } from '@/components/admin/servers/ServerRouter';
|
||||
import { ApplicationStore } from '@/state';
|
||||
import { Actions, useStoreActions } from 'easy-peasy';
|
||||
import { Form, Formik, FormikHelpers } from 'formik';
|
||||
import { useStoreActions } from 'easy-peasy';
|
||||
import Button from '@/components/elements/Button';
|
||||
import FormikSwitch from '@/components/elements/FormikSwitch';
|
||||
import BaseSettingsBox from '@/components/admin/servers/settings/BaseSettingsBox';
|
||||
import FeatureLimitsBox from '@/components/admin/servers/settings/FeatureLimitsBox';
|
||||
import NetworkingBox from '@/components/admin/servers/settings/NetworkingBox';
|
||||
import ServerResourceBox from '@/components/admin/servers/settings/ServerResourceBox';
|
||||
|
||||
export function ServerResourceContainer () {
|
||||
const { isSubmitting } = useFormikContext();
|
||||
export default () => {
|
||||
const { data: server, mutate } = useServerFromRoute();
|
||||
const { clearFlashes, clearAndAddHttpError } = useStoreActions(actions => actions.flashes);
|
||||
|
||||
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-2 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 }) {
|
||||
const history = useHistory();
|
||||
|
||||
const {
|
||||
clearFlashes,
|
||||
clearAndAddHttpError,
|
||||
} = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
||||
|
||||
const setServer = Context.useStoreActions(actions => actions.setServer);
|
||||
if (!server) return null;
|
||||
|
||||
const submit = (values: Values, { setSubmitting, setFieldValue }: FormikHelpers<Values>) => {
|
||||
clearFlashes('server');
|
||||
@ -121,9 +25,9 @@ export default function ServerSettingsContainer2 ({ server }: { server: Server }
|
||||
// OOM Killer is enabled, rather than when disabled.
|
||||
values.limits.oomDisabled = !values.limits.oomDisabled;
|
||||
|
||||
updateServer(server.id, values, ServerIncludes)
|
||||
updateServer(server.id, values)
|
||||
.then(s => {
|
||||
setServer({ ...server, ...s });
|
||||
// setServer({ ...server, ...s });
|
||||
|
||||
// TODO: Figure out how to properly clear react-selects for allocations.
|
||||
setFieldValue('addAllocations', []);
|
||||
@ -142,8 +46,7 @@ export default function ServerSettingsContainer2 ({ server }: { server: Server }
|
||||
initialValues={{
|
||||
externalId: server.externalId || '',
|
||||
name: server.name,
|
||||
ownerId: server.ownerId,
|
||||
|
||||
ownerId: server.userId,
|
||||
limits: {
|
||||
memory: server.limits.memory,
|
||||
swap: server.limits.swap,
|
||||
@ -155,13 +58,11 @@ export default function ServerSettingsContainer2 ({ server }: { server: Server }
|
||||
// OOM Killer is enabled, rather than when disabled.
|
||||
oomDisabled: !server.limits.oomDisabled,
|
||||
},
|
||||
|
||||
featureLimits: {
|
||||
allocations: server.featureLimits.allocations,
|
||||
backups: server.featureLimits.backups,
|
||||
databases: server.featureLimits.databases,
|
||||
},
|
||||
|
||||
allocationId: server.allocationId,
|
||||
addAllocations: [] as number[],
|
||||
removeAllocations: [] as number[],
|
||||
@ -177,14 +78,10 @@ export default function ServerSettingsContainer2 ({ server }: { server: Server }
|
||||
<NetworkingBox/>
|
||||
</div>
|
||||
<div css={tw`flex flex-col`}>
|
||||
<ServerResourceContainer/>
|
||||
|
||||
<div css={tw`bg-neutral-700 rounded shadow-md py-2 px-6 mt-6`}>
|
||||
<ServerResourceBox/>
|
||||
<div css={tw`bg-neutral-700 rounded shadow-md px-4 xl:px-5 py-5 mt-6`}>
|
||||
<div css={tw`flex flex-row`}>
|
||||
<ServerDeleteButton
|
||||
serverId={server?.id}
|
||||
onDeleted={() => history.push('/admin/servers')}
|
||||
/>
|
||||
<ServerDeleteButton/>
|
||||
<Button
|
||||
type="submit"
|
||||
size="small"
|
||||
@ -201,4 +98,4 @@ export default function ServerSettingsContainer2 ({ server }: { server: Server }
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -1,9 +1,7 @@
|
||||
import { getEgg, Egg, EggVariable } from '@/api/admin/eggs/getEgg';
|
||||
import { Server } from '@/api/admin/servers/getServers';
|
||||
import { Egg, EggVariable } from '@/api/admin/egg';
|
||||
import updateServerStartup, { Values } from '@/api/admin/servers/updateServerStartup';
|
||||
import EggSelect from '@/components/admin/servers/EggSelect';
|
||||
import NestSelect from '@/components/admin/servers/NestSelect';
|
||||
import { Context, ServerIncludes } from '@/components/admin/servers/ServerRouter';
|
||||
import FormikSwitch from '@/components/elements/FormikSwitch';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import Button from '@/components/elements/Button';
|
||||
@ -17,6 +15,7 @@ import { ApplicationStore } from '@/state';
|
||||
import { Actions, useStoreActions } from 'easy-peasy';
|
||||
import Label from '@/components/elements/Label';
|
||||
import { object } from 'yup';
|
||||
import { Server, useServerFromRoute } from '@/api/admin/server';
|
||||
|
||||
function ServerStartupLineContainer ({ egg, server }: { egg: Egg | null; server: Server }) {
|
||||
const { isSubmitting, setFieldValue } = useFormikContext();
|
||||
@ -179,26 +178,28 @@ function ServerStartupForm ({ egg, setEgg, server }: { egg: Egg | null, setEgg:
|
||||
);
|
||||
}
|
||||
|
||||
export default function ServerStartupContainer ({ server }: { server: Server }) {
|
||||
export default () => {
|
||||
const { data: server, mutate } = useServerFromRoute();
|
||||
const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
||||
|
||||
const [ egg, setEgg ] = useState<Egg | null>(null);
|
||||
|
||||
const setServer = Context.useStoreActions(actions => actions.setServer);
|
||||
|
||||
useEffect(() => {
|
||||
if (!server) return;
|
||||
|
||||
getEgg(server.eggId)
|
||||
.then(egg => setEgg(egg))
|
||||
.catch(error => console.error(error));
|
||||
}, []);
|
||||
}, [ server?.eggId ]);
|
||||
|
||||
if (!server) return null;
|
||||
|
||||
const submit = (values: Values, { setSubmitting }: FormikHelpers<Values>) => {
|
||||
clearFlashes('server');
|
||||
|
||||
updateServerStartup(server.id, values, ServerIncludes)
|
||||
.then(s => {
|
||||
setServer({ ...server, ...s });
|
||||
})
|
||||
updateServerStartup(server.id, values)
|
||||
// .then(s => {
|
||||
// mutate(data => { ...data, ...s });
|
||||
// })
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
clearAndAddHttpError({ key: 'server', error });
|
||||
@ -227,4 +228,4 @@ export default function ServerStartupContainer ({ server }: { server: Server })
|
||||
/>
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -0,0 +1,70 @@
|
||||
import { useFormikContext } from 'formik';
|
||||
import AdminBox from '@/components/admin/AdminBox';
|
||||
import { faBalanceScale } from '@fortawesome/free-solid-svg-icons';
|
||||
import tw from 'twin.macro';
|
||||
import Field from '@/components/elements/Field';
|
||||
import FormikSwitch from '@/components/elements/FormikSwitch';
|
||||
import React from 'react';
|
||||
import { useServerFromRoute } from '@/api/admin/server';
|
||||
|
||||
export default () => {
|
||||
const { isSubmitting } = useFormikContext();
|
||||
const { data: server } = useServerFromRoute();
|
||||
|
||||
if (!server) return null;
|
||||
|
||||
return (
|
||||
<AdminBox icon={faBalanceScale} title={'Resources'} isLoading={isSubmitting}>
|
||||
<div css={tw`grid grid-cols-1 xl:grid-cols-2 gap-4 lg:gap-6`}>
|
||||
<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.'}
|
||||
/>
|
||||
<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.'}
|
||||
/>
|
||||
<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.'}
|
||||
/>
|
||||
<Field
|
||||
id={'limits.swap'}
|
||||
name={'limits.swap'}
|
||||
label={'Swap Limit'}
|
||||
type={'number'}
|
||||
/>
|
||||
<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.'}
|
||||
/>
|
||||
<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 css={tw`xl:col-span-2 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>
|
||||
);
|
||||
};
|
@ -9,13 +9,9 @@ interface Props {
|
||||
isSecondary?: boolean;
|
||||
}
|
||||
|
||||
const ButtonStyle = styled.button<Omit<Props, 'isLoading'>>`
|
||||
const ButtonStyle = styled.button<Props>`
|
||||
${tw`relative inline-block rounded p-2 tracking-wide text-sm transition-all duration-150 border`};
|
||||
|
||||
& > span {
|
||||
${tw`select-none`};
|
||||
}
|
||||
|
||||
${props => ((!props.isSecondary && !props.color) || props.color === 'primary') && css<Props>`
|
||||
${props => !props.isSecondary && tw`bg-primary-500 border-primary-600 border text-primary-50`};
|
||||
|
||||
@ -77,21 +73,23 @@ const ButtonStyle = styled.button<Omit<Props, 'isLoading'>>`
|
||||
}
|
||||
`};
|
||||
|
||||
&:disabled { opacity: 0.55; cursor: default }
|
||||
${props => props.isLoading && tw`text-transparent`};
|
||||
|
||||
&:disabled {
|
||||
${tw`opacity-75 cursor-not-allowed`};
|
||||
}
|
||||
`;
|
||||
|
||||
type ComponentProps = Omit<JSX.IntrinsicElements['button'], 'ref' | keyof Props> & Props;
|
||||
|
||||
const Button: React.FC<ComponentProps> = ({ children, isLoading, ...props }) => (
|
||||
<ButtonStyle {...props}>
|
||||
const Button: React.FC<ComponentProps> = ({ children, isLoading, disabled, ...props }) => (
|
||||
<ButtonStyle {...props} isLoading={isLoading} disabled={isLoading || disabled}>
|
||||
{isLoading &&
|
||||
<div css={tw`flex absolute justify-center items-center w-full h-full left-0 top-0`}>
|
||||
<Spinner size={'small'}/>
|
||||
</div>
|
||||
}
|
||||
<span css={isLoading ? tw`text-transparent` : undefined}>
|
||||
{children}
|
||||
</span>
|
||||
{children}
|
||||
</ButtonStyle>
|
||||
);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user