2020-12-13 19:07:29 +00:00
|
|
|
import React, { useCallback, useEffect, useState } from 'react';
|
2020-08-22 22:43:28 +00:00
|
|
|
import TitledGreyBox from '@/components/elements/TitledGreyBox';
|
|
|
|
import tw from 'twin.macro';
|
2020-08-23 01:13:59 +00:00
|
|
|
import VariableBox from '@/components/server/startup/VariableBox';
|
2020-08-23 02:05:43 +00:00
|
|
|
import ServerContentBlock from '@/components/elements/ServerContentBlock';
|
2020-08-26 04:25:31 +00:00
|
|
|
import getServerStartup from '@/api/swr/getServerStartup';
|
|
|
|
import Spinner from '@/components/elements/Spinner';
|
2021-01-31 02:01:32 +00:00
|
|
|
import { ServerError } from '@/components/elements/ScreenBlock';
|
2020-08-26 04:25:31 +00:00
|
|
|
import { httpErrorToHuman } from '@/api/http';
|
|
|
|
import { ServerContext } from '@/state/server';
|
|
|
|
import { useDeepCompareEffect } from '@/plugins/useDeepCompareEffect';
|
2020-12-13 19:07:29 +00:00
|
|
|
import Select from '@/components/elements/Select';
|
|
|
|
import isEqual from 'react-fast-compare';
|
|
|
|
import Input from '@/components/elements/Input';
|
2022-02-27 16:47:22 +00:00
|
|
|
import { setSelectedDockerImage } from '@/api/server';
|
2020-12-13 19:07:29 +00:00
|
|
|
import InputSpinner from '@/components/elements/InputSpinner';
|
|
|
|
import useFlash from '@/plugins/useFlash';
|
2020-08-22 22:43:28 +00:00
|
|
|
|
|
|
|
const StartupContainer = () => {
|
2020-12-13 19:07:29 +00:00
|
|
|
const [ loading, setLoading ] = useState(false);
|
|
|
|
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
|
|
|
|
2020-08-26 04:25:31 +00:00
|
|
|
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
|
2020-12-13 19:07:29 +00:00
|
|
|
const variables = ServerContext.useStoreState(({ server }) => ({
|
|
|
|
variables: server.data!.variables,
|
|
|
|
invocation: server.data!.invocation,
|
|
|
|
dockerImage: server.data!.dockerImage,
|
|
|
|
}), isEqual);
|
2020-08-26 04:25:31 +00:00
|
|
|
|
2020-12-13 19:07:29 +00:00
|
|
|
const { data, error, isValidating, mutate } = getServerStartup(uuid, {
|
|
|
|
...variables,
|
|
|
|
dockerImages: [ variables.dockerImage ],
|
|
|
|
});
|
2020-08-26 04:25:31 +00:00
|
|
|
|
|
|
|
const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState);
|
2020-12-13 19:07:29 +00:00
|
|
|
const isCustomImage = data && !data.dockerImages.map(v => v.toLowerCase()).includes(variables.dockerImage.toLowerCase());
|
2020-08-26 04:25:31 +00:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
// Since we're passing in initial data this will not trigger on mount automatically. We
|
|
|
|
// want to always fetch fresh information from the API however when we're loading the startup
|
|
|
|
// information.
|
|
|
|
mutate();
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
useDeepCompareEffect(() => {
|
|
|
|
if (!data) return;
|
|
|
|
|
|
|
|
setServerFromState(s => ({
|
|
|
|
...s,
|
|
|
|
invocation: data.invocation,
|
|
|
|
variables: data.variables,
|
|
|
|
}));
|
|
|
|
}, [ data ]);
|
2020-08-22 22:43:28 +00:00
|
|
|
|
2020-12-13 19:07:29 +00:00
|
|
|
const updateSelectedDockerImage = useCallback((v: React.ChangeEvent<HTMLSelectElement>) => {
|
|
|
|
setLoading(true);
|
|
|
|
clearFlashes('startup:image');
|
|
|
|
|
|
|
|
const image = v.currentTarget.value;
|
|
|
|
setSelectedDockerImage(uuid, image)
|
|
|
|
.then(() => setServerFromState(s => ({ ...s, dockerImage: image })))
|
|
|
|
.catch(error => {
|
|
|
|
console.error(error);
|
|
|
|
clearAndAddHttpError({ key: 'startup:image', error });
|
|
|
|
})
|
|
|
|
.then(() => setLoading(false));
|
|
|
|
}, [ uuid ]);
|
|
|
|
|
2020-08-22 22:43:28 +00:00
|
|
|
return (
|
2020-08-26 04:25:31 +00:00
|
|
|
!data ?
|
|
|
|
(!error || (error && isValidating)) ?
|
|
|
|
<Spinner centered size={Spinner.Size.LARGE}/>
|
|
|
|
:
|
|
|
|
<ServerError
|
|
|
|
title={'Oops!'}
|
|
|
|
message={httpErrorToHuman(error)}
|
|
|
|
onRetry={() => mutate()}
|
|
|
|
/>
|
|
|
|
:
|
2020-12-13 19:07:29 +00:00
|
|
|
<ServerContentBlock title={'Startup Settings'} showFlashKey={'startup:image'}>
|
2021-03-21 17:33:09 +00:00
|
|
|
<div css={tw`md:flex`}>
|
2020-12-13 19:07:29 +00:00
|
|
|
<TitledGreyBox title={'Startup Command'} css={tw`flex-1`}>
|
|
|
|
<div css={tw`px-1 py-2`}>
|
|
|
|
<p css={tw`font-mono bg-neutral-900 rounded py-2 px-4`}>
|
|
|
|
{data.invocation}
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
</TitledGreyBox>
|
2021-03-21 17:33:09 +00:00
|
|
|
<TitledGreyBox title={'Docker Image'} css={tw`flex-1 lg:flex-none lg:w-1/3 mt-8 md:mt-0 md:ml-10`}>
|
2020-12-13 19:07:29 +00:00
|
|
|
{data.dockerImages.length > 1 && !isCustomImage ?
|
|
|
|
<>
|
|
|
|
<InputSpinner visible={loading}>
|
|
|
|
<Select
|
|
|
|
disabled={data.dockerImages.length < 2}
|
|
|
|
onChange={updateSelectedDockerImage}
|
|
|
|
defaultValue={variables.dockerImage}
|
|
|
|
>
|
|
|
|
{data.dockerImages.map(image => (
|
|
|
|
<option key={image} value={image}>{image}</option>
|
|
|
|
))}
|
|
|
|
</Select>
|
|
|
|
</InputSpinner>
|
|
|
|
<p css={tw`text-xs text-neutral-300 mt-2`}>
|
|
|
|
This is an advanced feature allowing you to select a Docker image to use when
|
|
|
|
running this server instance.
|
|
|
|
</p>
|
|
|
|
</>
|
|
|
|
:
|
|
|
|
<>
|
|
|
|
<Input disabled readOnly value={variables.dockerImage}/>
|
|
|
|
{isCustomImage &&
|
|
|
|
<p css={tw`text-xs text-neutral-300 mt-2`}>
|
|
|
|
This {'server\'s'} Docker image has been manually set by an administrator and cannot
|
|
|
|
be changed through this UI.
|
|
|
|
</p>
|
|
|
|
}
|
|
|
|
</>
|
|
|
|
}
|
|
|
|
</TitledGreyBox>
|
|
|
|
</div>
|
|
|
|
<h3 css={tw`mt-8 mb-2 text-2xl`}>Variables</h3>
|
|
|
|
<div css={tw`grid gap-8 md:grid-cols-2`}>
|
2020-08-26 04:25:31 +00:00
|
|
|
{data.variables.map(variable => <VariableBox key={variable.envVariable} variable={variable}/>)}
|
2020-08-22 22:43:28 +00:00
|
|
|
</div>
|
2020-08-26 04:25:31 +00:00
|
|
|
</ServerContentBlock>
|
2020-08-22 22:43:28 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default StartupContainer;
|