2021-09-15 21:37:17 +00:00
import getAllocations from '@/api/admin/nodes/getAllocations' ;
2021-09-13 04:15:45 +00:00
import { Server } from '@/api/admin/servers/getServers' ;
import ServerDeleteButton from '@/components/admin/servers/ServerDeleteButton' ;
2021-09-15 21:37:17 +00:00
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' ;
2021-05-20 22:00:46 +00:00
import React from 'react' ;
import AdminBox from '@/components/admin/AdminBox' ;
2021-09-13 04:15:45 +00:00
import { useHistory } from 'react-router-dom' ;
2021-05-20 22:00:46 +00:00
import tw from 'twin.macro' ;
import { object } from 'yup' ;
2021-09-13 04:15:45 +00:00
import updateServer , { Values } from '@/api/admin/servers/updateServer' ;
2021-05-20 22:00:46 +00:00
import Field from '@/components/elements/Field' ;
import SpinnerOverlay from '@/components/elements/SpinnerOverlay' ;
import { Form , Formik , FormikHelpers , useFormikContext } from 'formik' ;
2021-09-16 20:59:22 +00:00
import { Context , ServerIncludes } from '@/components/admin/servers/ServerRouter' ;
2021-05-20 22:00:46 +00:00
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' ;
2021-09-16 20:59:22 +00:00
export function ServerSettingsContainer ( { server } : { server? : Server } ) {
2021-05-20 22:00:46 +00:00
const { isSubmitting } = useFormikContext ( ) ;
return (
2021-09-16 20:59:22 +00:00
< AdminBox icon = { faCogs } title = { 'Settings' } css = { tw ` relative w-full ` } >
2021-05-20 22:00:46 +00:00
< 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
2021-09-16 20:59:22 +00:00
id = { 'name' }
name = { 'name' }
label = { 'Server Name' }
type = { 'text' }
2021-08-05 04:16:52 +00:00
/ >
2021-05-20 22:00:46 +00:00
< / div >
2021-09-16 20:59:22 +00:00
< div css = { tw ` mb-6 md:w-full md:flex md:flex-col md:ml-4 md:mb-0 ` } >
2021-08-05 04:16:52 +00:00
< Field
2021-09-16 20:59:22 +00:00
id = { 'externalId' }
name = { 'externalId' }
label = { 'External Identifier' }
type = { 'text' }
2021-08-05 04:16:52 +00:00
/ >
2021-05-20 22:00:46 +00:00
< / div >
2021-09-16 20:59:22 +00:00
< / div >
2021-05-20 22:00:46 +00:00
2021-09-16 20:59:22 +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 } / >
2021-08-05 04:16:52 +00:00
< / div >
< / div >
2021-05-20 22:00:46 +00:00
< / AdminBox >
) ;
2021-09-13 04:15:45 +00:00
}
2021-05-20 22:00:46 +00:00
2021-09-16 20:59:22 +00:00
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 } ) {
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 = > {
2021-09-16 22:54:02 +00:00
return { value : a.id.toString ( ) , label : a.getDisplayText ( ) } ;
2021-09-16 20:59:22 +00:00
} ) ) ;
} ;
return (
< AdminBox icon = { faNetworkWired } title = { 'Networking' } css = { tw ` relative w-full ` } >
< SpinnerOverlay visible = { isSubmitting } / >
< div css = { tw ` mb-6 ` } >
< Label > Primary Allocation < / Label >
< Select
id = { 'allocationId' }
name = { 'allocationId' }
>
{ server . relations ? . allocations ? . map ( a = > (
2021-09-16 22:54:02 +00:00
< option key = { a . id } value = { a . id } > { a . getDisplayText ( ) } < / option >
2021-09-16 20:59:22 +00:00
) ) }
< / 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 = > {
2021-09-16 22:54:02 +00:00
return { value : a.id.toString ( ) , label : a.getDisplayText ( ) } ;
2021-09-16 20:59:22 +00:00
} ) || [ ] }
isMulti
isSearchable
css = { tw ` mb-2 ` }
/ >
< / AdminBox >
) ;
}
2021-09-13 04:15:45 +00:00
export function ServerResourceContainer ( ) {
2021-05-20 22:00:46 +00:00
const { isSubmitting } = useFormikContext ( ) ;
return (
2021-09-15 21:37:17 +00:00
< AdminBox icon = { faBalanceScale } title = { 'Resources' } css = { tw ` relative w-full ` } >
2021-05-20 22:00:46 +00:00
< 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
2021-09-16 20:59:22 +00:00
id = { 'limits.cpu' }
name = { 'limits.cpu' }
2021-08-05 04:16:52 +00:00
label = { 'CPU Limit' }
2021-09-15 21:37:17 +00:00
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
/ >
2021-05-20 22:00:46 +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
2021-09-16 20:59:22 +00:00
id = { 'limits.threads' }
name = { 'limits.threads' }
2021-08-05 04:16:52 +00:00
label = { 'CPU Pinning' }
2021-09-15 21:37:17 +00:00
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
/ >
2021-05-20 22:00:46 +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
2021-09-16 20:59:22 +00:00
id = { 'limits.memory' }
name = { 'limits.memory' }
2021-08-05 04:16:52 +00:00
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.' }
/ >
2021-05-20 22:00:46 +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
2021-09-16 20:59:22 +00:00
id = { 'limits.swap' }
name = { 'limits.swap' }
2021-08-05 04:16:52 +00:00
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
2021-09-16 20:59:22 +00:00
id = { 'limits.disk' }
name = { 'limits.disk' }
2021-08-05 04:16:52 +00:00
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.' }
/ >
2021-05-20 22:00:46 +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
2021-09-16 20:59:22 +00:00
id = { 'limits.io' }
name = { 'limits.io' }
2021-08-05 04:16:52 +00:00
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 >
2021-09-18 18:26:36 +00:00
< div css = { tw ` mb-2 md:w-full md:flex md:flex-row ` } >
2021-09-15 21:37:17 +00:00
< div css = { tw ` bg-neutral-800 border border-neutral-900 shadow-inner p-4 rounded ` } >
2021-08-05 04:16:52 +00:00
< FormikSwitch
2021-09-16 20:59:22 +00:00
name = { 'limits.oomDisabled' }
2021-08-05 04:16:52 +00:00
label = { 'Out of Memory Killer' }
2021-09-15 21:37:17 +00:00
description = { 'Enabling the Out of Memory Killer may cause server processes to exit unexpectedly.' }
2021-08-05 04:16:52 +00:00
/ >
< / div >
< / div >
2021-05-20 22:00:46 +00:00
< / AdminBox >
) ;
2021-09-13 04:15:45 +00:00
}
2021-05-20 22:00:46 +00:00
2021-09-16 03:19:39 +00:00
export default function ServerSettingsContainer2 ( { server } : { server : Server } ) {
2021-09-13 04:15:45 +00:00
const history = useHistory ( ) ;
2021-05-20 22:00:46 +00:00
const { clearFlashes , clearAndAddHttpError } = useStoreActions ( ( actions : Actions < ApplicationStore > ) = > actions . flashes ) ;
const setServer = Context . useStoreActions ( actions = > actions . setServer ) ;
2021-09-16 20:59:22 +00:00
const submit = ( values : Values , { setSubmitting , setFieldValue } : FormikHelpers < Values > ) = > {
2021-05-20 22:00:46 +00:00
clearFlashes ( 'server' ) ;
2021-09-16 20:59:22 +00:00
// 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 )
2021-09-15 21:37:17 +00:00
. then ( s = > {
setServer ( { . . . server , . . . s } ) ;
// TODO: Figure out how to properly clear react-selects for allocations.
setFieldValue ( 'addAllocations' , [ ] ) ;
setFieldValue ( 'removeAllocations' , [ ] ) ;
} )
2021-05-20 22:00:46 +00:00
. catch ( error = > {
console . error ( error ) ;
clearAndAddHttpError ( { key : 'server' , error } ) ;
} )
. then ( ( ) = > setSubmitting ( false ) ) ;
} ;
return (
< Formik
onSubmit = { submit }
initialValues = { {
externalId : server.externalId || '' ,
name : server.name ,
2021-09-13 04:15:45 +00:00
ownerId : server.ownerId ,
2021-05-20 22:00:46 +00:00
2021-09-16 20:59:22 +00:00
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 || '' ,
// This value is inverted to have the switch be on when the
// 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 ,
} ,
2021-09-15 21:37:17 +00:00
allocationId : server.allocationId ,
addAllocations : [ ] as number [ ] ,
removeAllocations : [ ] as number [ ] ,
2021-05-20 22:00:46 +00:00
} }
validationSchema = { object ( ) . shape ( {
} ) }
>
2021-09-15 17:09:54 +00:00
{ ( { isSubmitting , isValid } ) = > (
< Form >
2021-09-18 18:26:36 +00:00
< div css = { tw ` grid grid-cols-1 md:grid-cols-2 gap-y-6 gap-x-8 mb-16 ` } >
2021-09-15 17:09:54 +00:00
< div css = { tw ` flex flex-col ` } >
< div css = { tw ` flex mb-6 ` } >
< ServerSettingsContainer server = { server } / >
2021-05-20 22:00:46 +00:00
< / div >
2021-09-15 17:09:54 +00:00
< div css = { tw ` flex mb-6 ` } >
< ServerFeatureContainer / >
< / div >
2021-09-16 03:19:39 +00:00
< div css = { tw ` flex ` } >
2021-09-15 21:37:17 +00:00
< ServerAllocationsContainer server = { server } / >
2021-09-15 17:09:54 +00:00
< / div >
2021-09-16 03:19:39 +00:00
< / div >
< div css = { tw ` flex flex-col ` } >
< div css = { tw ` flex mb-6 ` } >
< ServerResourceContainer / >
< / div >
2021-09-15 17:09:54 +00:00
< 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 >
2021-05-20 22:00:46 +00:00
< / div >
< / div >
< / div >
2021-09-15 17:09:54 +00:00
< / div >
< / Form >
) }
2021-05-20 22:00:46 +00:00
< / Formik >
) ;
2021-09-13 04:15:45 +00:00
}