ui(admin): add egg install editing
This commit is contained in:
parent
e8ddadc608
commit
8d0dd42475
8 changed files with 193 additions and 111 deletions
|
@ -7,7 +7,6 @@ use Pterodactyl\Models\Nest;
|
|||
use Illuminate\Http\Response;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Spatie\QueryBuilder\QueryBuilder;
|
||||
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
|
||||
use Pterodactyl\Transformers\Api\Application\EggTransformer;
|
||||
use Pterodactyl\Http\Requests\Api\Application\Eggs\GetEggRequest;
|
||||
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
|
||||
|
@ -19,22 +18,8 @@ use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
|
|||
|
||||
class EggController extends ApplicationApiController
|
||||
{
|
||||
private EggRepositoryInterface $repository;
|
||||
|
||||
/**
|
||||
* EggController constructor.
|
||||
*/
|
||||
public function __construct(EggRepositoryInterface $repository)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of all eggs on a given nest.
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Container\BindingResolutionException
|
||||
*/
|
||||
public function index(GetEggsRequest $request, Nest $nest): array
|
||||
{
|
||||
|
@ -58,8 +43,6 @@ class EggController extends ApplicationApiController
|
|||
|
||||
/**
|
||||
* Returns a single egg.
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Container\BindingResolutionException
|
||||
*/
|
||||
public function view(GetEggRequest $request, Egg $egg): array
|
||||
{
|
||||
|
@ -70,8 +53,6 @@ class EggController extends ApplicationApiController
|
|||
|
||||
/**
|
||||
* Creates a new egg.
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Container\BindingResolutionException
|
||||
*/
|
||||
public function store(StoreEggRequest $request): JsonResponse
|
||||
{
|
||||
|
@ -84,8 +65,6 @@ class EggController extends ApplicationApiController
|
|||
|
||||
/**
|
||||
* Updates an egg.
|
||||
*
|
||||
* @throws \Illuminate\Contracts\Container\BindingResolutionException
|
||||
*/
|
||||
public function update(UpdateEggRequest $request, Egg $egg): array
|
||||
{
|
||||
|
|
|
@ -2,12 +2,27 @@
|
|||
|
||||
namespace Pterodactyl\Http\Requests\Api\Application\Eggs;
|
||||
|
||||
use Pterodactyl\Models\Egg;
|
||||
|
||||
class UpdateEggRequest extends StoreEggRequest
|
||||
{
|
||||
public function rules(array $rules = null): array
|
||||
{
|
||||
return $rules ?? Egg::getRulesForUpdate($this->route()->parameter('egg')->id);
|
||||
return [
|
||||
'nest_id' => 'sometimes|numeric|exists:nests,id',
|
||||
'name' => 'sometimes|string|max:191',
|
||||
'description' => 'sometimes|string|nullable',
|
||||
'features' => 'sometimes|array|nullable',
|
||||
'docker_images' => 'sometimes|required|array|min:1',
|
||||
'docker_images.*' => 'sometimes|string',
|
||||
'file_denylist' => 'sometimes|array|nullable',
|
||||
'file_denylist.*' => 'sometimes|string',
|
||||
'config_files' => 'sometimes|nullable|json',
|
||||
'config_startup' => 'sometimes|nullable|json',
|
||||
'config_stop' => 'sometimes|nullable|string|max:191',
|
||||
'config_from' => 'sometimes|nullable|numeric|exists:eggs,id',
|
||||
'startup' => 'sometimes|nullable|string',
|
||||
'script_container' => 'sometimes|string',
|
||||
'script_entry' => 'sometimes|string',
|
||||
'script_install' => 'sometimes|string',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,29 @@
|
|||
import http from '@/api/http';
|
||||
import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg';
|
||||
|
||||
export default (nestId: number, name: string): Promise<Egg> => {
|
||||
export default (egg: Partial<Egg>): Promise<Egg> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post('/api/application/eggs', {
|
||||
nestId, name,
|
||||
})
|
||||
.then(({ data }) => resolve(rawDataToEgg(data.attributes)))
|
||||
http.post(
|
||||
'/api/application/eggs/',
|
||||
{
|
||||
nest_id: egg.nestId,
|
||||
name: egg.name,
|
||||
description: egg.description,
|
||||
features: egg.features,
|
||||
docker_images: egg.dockerImages,
|
||||
config_files: egg.configFiles,
|
||||
config_startup: egg.configStartup,
|
||||
config_stop: egg.configStop,
|
||||
config_from: egg.configFrom,
|
||||
startup: egg.startup,
|
||||
script_container: egg.scriptContainer,
|
||||
copy_script_from: egg.copyScriptFrom,
|
||||
script_entry: egg.scriptEntry,
|
||||
script_is_privileged: egg.scriptIsPrivileged,
|
||||
script_install: egg.scriptInstall,
|
||||
},
|
||||
)
|
||||
.then(({ data }) => resolve(rawDataToEgg(data)))
|
||||
.catch(reject);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -43,7 +43,6 @@ export interface Egg {
|
|||
dockerImages: string[];
|
||||
configFiles: string | null;
|
||||
configStartup: string | null;
|
||||
configLogs: string | null;
|
||||
configStop: string | null;
|
||||
configFrom: number | null;
|
||||
startup: string;
|
||||
|
@ -71,11 +70,10 @@ export const rawDataToEgg = ({ attributes }: FractalResponseData): Egg => ({
|
|||
description: attributes.description,
|
||||
features: attributes.features,
|
||||
dockerImages: attributes.docker_images,
|
||||
configFiles: attributes.config_files,
|
||||
configStartup: attributes.config_startup,
|
||||
configLogs: attributes.config_logs,
|
||||
configStop: attributes.config_stop,
|
||||
configFrom: attributes.config_from,
|
||||
configFiles: attributes.config?.files,
|
||||
configStartup: attributes.config?.startup,
|
||||
configStop: attributes.config?.stop,
|
||||
configFrom: attributes.config?.extends,
|
||||
startup: attributes.startup,
|
||||
copyScriptFrom: attributes.copy_script_from,
|
||||
scriptContainer: attributes.script?.container,
|
||||
|
|
29
resources/scripts/api/admin/eggs/updateEgg.ts
Normal file
29
resources/scripts/api/admin/eggs/updateEgg.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import http from '@/api/http';
|
||||
import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg';
|
||||
|
||||
export default (id: number, egg: Partial<Egg>): Promise<Egg> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.patch(
|
||||
`/api/application/eggs/${id}`,
|
||||
{
|
||||
nest_id: egg.nestId,
|
||||
name: egg.name,
|
||||
description: egg.description,
|
||||
features: egg.features,
|
||||
docker_images: egg.dockerImages,
|
||||
config_files: egg.configFiles,
|
||||
config_startup: egg.configStartup,
|
||||
config_stop: egg.configStop,
|
||||
config_from: egg.configFrom,
|
||||
startup: egg.startup,
|
||||
script_container: egg.scriptContainer,
|
||||
copy_script_from: egg.copyScriptFrom,
|
||||
script_entry: egg.scriptEntry,
|
||||
script_is_privileged: egg.scriptIsPrivileged,
|
||||
script_install: egg.scriptInstall,
|
||||
},
|
||||
)
|
||||
.then(({ data }) => resolve(rawDataToEgg(data)))
|
||||
.catch(reject);
|
||||
});
|
||||
};
|
|
@ -99,50 +99,48 @@ const EditInformationContainer = () => {
|
|||
description: string().max(255, ''),
|
||||
})}
|
||||
>
|
||||
{
|
||||
({ isSubmitting, isValid }) => (
|
||||
<React.Fragment>
|
||||
<AdminBox title={'Edit Nest'} css={tw`flex-1 self-start w-full relative mb-8 lg:mb-0 mr-0 lg:mr-4`}>
|
||||
<SpinnerOverlay visible={isSubmitting}/>
|
||||
{({ isSubmitting, isValid }) => (
|
||||
<React.Fragment>
|
||||
<AdminBox title={'Edit Nest'} css={tw`flex-1 self-start w-full relative mb-8 lg:mb-0 mr-0 lg:mr-4`}>
|
||||
<SpinnerOverlay visible={isSubmitting}/>
|
||||
|
||||
<Form css={tw`mb-0`}>
|
||||
<div>
|
||||
<Field
|
||||
id={'name'}
|
||||
name={'name'}
|
||||
label={'Name'}
|
||||
type={'text'}
|
||||
<Form css={tw`mb-0`}>
|
||||
<div>
|
||||
<Field
|
||||
id={'name'}
|
||||
name={'name'}
|
||||
label={'Name'}
|
||||
type={'text'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div css={tw`mt-6`}>
|
||||
<Field
|
||||
id={'description'}
|
||||
name={'description'}
|
||||
label={'Description'}
|
||||
type={'text'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div css={tw`w-full flex flex-row items-center mt-6`}>
|
||||
<div css={tw`flex`}>
|
||||
<NestDeleteButton
|
||||
nestId={nest.id}
|
||||
onDeleted={() => history.push('/admin/nests')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div css={tw`mt-6`}>
|
||||
<Field
|
||||
id={'description'}
|
||||
name={'description'}
|
||||
label={'Description'}
|
||||
type={'text'}
|
||||
/>
|
||||
<div css={tw`flex ml-auto`}>
|
||||
<Button type={'submit'} disabled={isSubmitting || !isValid}>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div css={tw`w-full flex flex-row items-center mt-6`}>
|
||||
<div css={tw`flex`}>
|
||||
<NestDeleteButton
|
||||
nestId={nest.id}
|
||||
onDeleted={() => history.push('/admin/nests')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div css={tw`flex ml-auto`}>
|
||||
<Button type={'submit'} disabled={isSubmitting || !isValid}>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</AdminBox>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</Form>
|
||||
</AdminBox>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,52 +1,98 @@
|
|||
import { Egg } from '@/api/admin/eggs/getEgg';
|
||||
import updateEgg from '@/api/admin/eggs/updateEgg';
|
||||
import Field from '@/components/elements/Field';
|
||||
import useFlash from '@/plugins/useFlash';
|
||||
import { shell } from '@codemirror/legacy-modes/mode/shell';
|
||||
import { faScroll } from '@fortawesome/free-solid-svg-icons';
|
||||
import { Form, Formik, FormikHelpers } from 'formik';
|
||||
import React from 'react';
|
||||
import tw from 'twin.macro';
|
||||
import AdminBox from '@/components/admin/AdminBox';
|
||||
import { Context } from '@/components/admin/nests/eggs/EggRouter';
|
||||
import Button from '@/components/elements/Button';
|
||||
import Editor from '@/components/elements/Editor';
|
||||
import Input from '@/components/elements/Input';
|
||||
import Label from '@/components/elements/Label';
|
||||
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||
|
||||
export default () => {
|
||||
const egg = Context.useStoreState(state => state.egg);
|
||||
interface Values {
|
||||
scriptContainer: string;
|
||||
scriptEntry: string;
|
||||
scriptInstall: string;
|
||||
}
|
||||
|
||||
if (egg === undefined) {
|
||||
return (
|
||||
<></>
|
||||
);
|
||||
}
|
||||
export default function EggInstallContainer ({ egg }: { egg: Egg }) {
|
||||
const { clearFlashes, clearAndAddHttpError } = useFlash();
|
||||
|
||||
let fetchFileContent: null | (() => Promise<string>) = null;
|
||||
|
||||
const submit = async (values: Values, { setSubmitting }: FormikHelpers<Values>) => {
|
||||
if (fetchFileContent === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
values.scriptInstall = await fetchFileContent();
|
||||
|
||||
clearFlashes('egg');
|
||||
|
||||
updateEgg(egg.id, values)
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
clearAndAddHttpError({ key: 'egg', error });
|
||||
})
|
||||
.then(() => setSubmitting(false));
|
||||
};
|
||||
|
||||
return (
|
||||
<AdminBox title={'Install Script'} padding={false}>
|
||||
<div css={tw`relative pb-4`}>
|
||||
<SpinnerOverlay visible={false}/>
|
||||
<Formik
|
||||
onSubmit={submit}
|
||||
initialValues={{
|
||||
scriptContainer: egg.scriptContainer,
|
||||
scriptEntry: egg.scriptEntry,
|
||||
scriptInstall: '',
|
||||
}}
|
||||
>
|
||||
{({ isSubmitting, isValid }) => (
|
||||
<AdminBox icon={faScroll} title={'Install Script'} padding={false}>
|
||||
<div css={tw`relative pb-4`}>
|
||||
<SpinnerOverlay visible={isSubmitting}/>
|
||||
|
||||
<Editor overrides={tw`h-96 mb-4`} initialContent={egg.scriptInstall || ''} mode={shell}/>
|
||||
<Form>
|
||||
<Editor
|
||||
overrides={tw`h-96 mb-4`}
|
||||
initialContent={egg.scriptInstall || ''}
|
||||
mode={shell}
|
||||
fetchContent={value => {
|
||||
fetchFileContent = value;
|
||||
}}
|
||||
/>
|
||||
|
||||
<div css={tw`mx-6 mb-4`}>
|
||||
<div css={tw`grid grid-cols-3 gap-x-8 gap-y-6`}>
|
||||
<div>
|
||||
<Label>Install Container</Label>
|
||||
<Input type="text" defaultValue={egg.scriptContainer}/>
|
||||
<p className={'input-help'}>The Docker image to use for running this installation script.</p>
|
||||
</div>
|
||||
<div css={tw`mx-6 mb-4`}>
|
||||
<div css={tw`grid grid-cols-3 gap-x-8 gap-y-6`}>
|
||||
<Field
|
||||
id={'scriptContainer'}
|
||||
name={'scriptContainer'}
|
||||
label={'Install Container'}
|
||||
type={'text'}
|
||||
description={'The Docker image to use for running this installation script.'}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<Label>Install Entrypoint</Label>
|
||||
<Input type="text" defaultValue={egg.scriptEntry}/>
|
||||
<p className={'input-help'}>The command that should be used to run this script inside of the installation container.</p>
|
||||
</div>
|
||||
<Field
|
||||
id={'scriptEntry'}
|
||||
name={'scriptEntry'}
|
||||
label={'Install Entrypoint'}
|
||||
type={'text'}
|
||||
description={'The command that should be used to run this script inside of the installation container.'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div css={tw`flex flex-row border-t border-neutral-600`}>
|
||||
<Button type={'submit'} size={'small'} css={tw`ml-auto mr-6 mt-4`} disabled={isSubmitting || !isValid}>
|
||||
Save Changes
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div css={tw`flex flex-row border-t border-neutral-600`}>
|
||||
<Button type={'button'} size={'small'} css={tw`ml-auto mr-6 mt-4`}>
|
||||
Save Changes
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</AdminBox>
|
||||
</AdminBox>
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ const EggRouter = () => {
|
|||
</Route>
|
||||
|
||||
<Route path={`${match.path}/install`} exact>
|
||||
<EggInstallContainer/>
|
||||
<EggInstallContainer egg={egg}/>
|
||||
</Route>
|
||||
</Switch>
|
||||
</AdminContentBlock>
|
||||
|
|
Loading…
Reference in a new issue