diff --git a/app/Http/Controllers/Api/Application/Eggs/EggController.php b/app/Http/Controllers/Api/Application/Eggs/EggController.php index 113ab26a2..e02e6ca14 100644 --- a/app/Http/Controllers/Api/Application/Eggs/EggController.php +++ b/app/Http/Controllers/Api/Application/Eggs/EggController.php @@ -2,10 +2,12 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Eggs; +use Ramsey\Uuid\Uuid; use Pterodactyl\Models\Egg; use Pterodactyl\Models\Nest; use Illuminate\Http\Response; use Illuminate\Http\JsonResponse; +use Illuminate\Support\Facades\Log; use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Transformers\Api\Application\EggTransformer; use Pterodactyl\Http\Requests\Api\Application\Eggs\GetEggRequest; @@ -56,7 +58,16 @@ class EggController extends ApplicationApiController */ public function store(StoreEggRequest $request): JsonResponse { - $egg = Egg::query()->create($request->validated()); + $validated = $request->validated(); + Log::info(json_encode($validated, JSON_PRETTY_PRINT)); + $merged = array_merge($validated, [ + 'uuid' => Uuid::uuid4()->toString(), + // TODO: allow this to be set in the request, and default to config value if null or not present. + 'author' => config('pterodactyl.service.author'), + ]); + Log::info(json_encode($merged, JSON_PRETTY_PRINT)); + + $egg = Egg::query()->create($merged); return $this->fractal->item($egg) ->transformWith(EggTransformer::class) diff --git a/app/Http/Requests/Api/Application/Eggs/StoreEggRequest.php b/app/Http/Requests/Api/Application/Eggs/StoreEggRequest.php index 6759615c9..9c1146909 100644 --- a/app/Http/Requests/Api/Application/Eggs/StoreEggRequest.php +++ b/app/Http/Requests/Api/Application/Eggs/StoreEggRequest.php @@ -2,13 +2,29 @@ namespace Pterodactyl\Http\Requests\Api\Application\Eggs; -use Pterodactyl\Models\Egg; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; class StoreEggRequest extends ApplicationApiRequest { public function rules(array $rules = null): array { - return $rules ?? Egg::getRules(); + return [ + 'nest_id' => 'required|bail|numeric|exists:nests,id', + 'name' => 'required|string|max:191', + 'description' => 'sometimes|string|nullable', + 'features' => 'sometimes|array', + 'docker_images' => 'required|array|min:1', + 'docker_images.*' => 'required|string', + 'file_denylist' => 'sometimes|array|nullable', + 'file_denylist.*' => 'sometimes|string', + 'config_files' => 'required|nullable|json', + 'config_startup' => 'required|nullable|json', + 'config_stop' => 'required|nullable|string|max:191', +// 'config_from' => 'sometimes|nullable|numeric|exists:eggs,id', + 'startup' => 'required|string', + 'script_container' => 'sometimes|string', + 'script_entry' => 'sometimes|string', + 'script_install' => 'sometimes|string', + ]; } } diff --git a/app/Http/Requests/Api/Application/Eggs/UpdateEggRequest.php b/app/Http/Requests/Api/Application/Eggs/UpdateEggRequest.php index 7eb76fa24..090f5b51c 100644 --- a/app/Http/Requests/Api/Application/Eggs/UpdateEggRequest.php +++ b/app/Http/Requests/Api/Application/Eggs/UpdateEggRequest.php @@ -10,16 +10,16 @@ class UpdateEggRequest extends StoreEggRequest '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', + 'features' => 'sometimes|array', + 'docker_images' => 'sometimes|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', +// 'config_from' => 'sometimes|nullable|numeric|exists:eggs,id', + 'startup' => 'sometimes|string', 'script_container' => 'sometimes|string', 'script_entry' => 'sometimes|string', 'script_install' => 'sometimes|string', diff --git a/app/Models/Egg.php b/app/Models/Egg.php index 992a54f91..a45f07f4b 100644 --- a/app/Models/Egg.php +++ b/app/Models/Egg.php @@ -16,7 +16,6 @@ namespace Pterodactyl\Models; * @property array|null $file_denylist * @property string|null $config_files * @property string|null $config_startup - * @property string|null $config_logs * @property string|null $config_stop * @property int|null $config_from * @property string|null $startup @@ -32,7 +31,6 @@ namespace Pterodactyl\Models; * @property string $copy_script_container * @property string|null $inherit_config_files * @property string|null $inherit_config_startup - * @property string|null $inherit_config_logs * @property string|null $inherit_config_stop * @property string $inherit_file_denylist * @property array|null $inherit_features @@ -75,14 +73,16 @@ class Egg extends Model * @var array */ protected $fillable = [ + 'nest_id', + 'uuid', 'name', 'description', 'features', + 'author', 'docker_images', 'file_denylist', 'config_files', 'config_startup', - 'config_logs', 'config_stop', 'config_from', 'startup', @@ -123,7 +123,6 @@ class Egg extends Model 'config_from' => 'sometimes|bail|nullable|numeric|exists:eggs,id', 'config_stop' => 'required_without:config_from|nullable|string|max:191', 'config_startup' => 'required_without:config_from|nullable|json', - 'config_logs' => 'required_without:config_from|nullable|json', 'config_files' => 'required_without:config_from|nullable|json', 'update_url' => 'sometimes|nullable|string', ]; @@ -136,7 +135,6 @@ class Egg extends Model 'file_denylist' => null, 'config_stop' => null, 'config_startup' => null, - 'config_logs' => null, 'config_files' => null, 'update_url' => null, ]; @@ -214,20 +212,6 @@ class Egg extends Model return $this->configFrom->config_startup; } - /** - * Return the log reading configuration for an egg. - * - * @return string - */ - public function getInheritConfigLogsAttribute() - { - if (!is_null($this->config_logs) || is_null($this->config_from)) { - return $this->config_logs; - } - - return $this->configFrom->config_logs; - } - /** * Return the stop command configuration for an egg. * diff --git a/app/Services/Eggs/Sharing/EggExporterService.php b/app/Services/Eggs/Sharing/EggExporterService.php index f64656150..02682b4a8 100644 --- a/app/Services/Eggs/Sharing/EggExporterService.php +++ b/app/Services/Eggs/Sharing/EggExporterService.php @@ -50,7 +50,6 @@ class EggExporterService 'config' => [ 'files' => $egg->inherit_config_files, 'startup' => $egg->inherit_config_startup, - 'logs' => $egg->inherit_config_logs, 'stop' => $egg->inherit_config_stop, ], 'scripts' => [ diff --git a/app/Services/Eggs/Sharing/EggImporterService.php b/app/Services/Eggs/Sharing/EggImporterService.php index b6561f63d..4e71867c4 100644 --- a/app/Services/Eggs/Sharing/EggImporterService.php +++ b/app/Services/Eggs/Sharing/EggImporterService.php @@ -151,7 +151,6 @@ class EggImporterService 'update_url' => Arr::get($parsed, 'meta.update_url'), 'config_files' => Arr::get($parsed, 'config.files'), 'config_startup' => Arr::get($parsed, 'config.startup'), - 'config_logs' => Arr::get($parsed, 'config.logs'), 'config_stop' => Arr::get($parsed, 'config.stop'), 'startup' => Arr::get($parsed, 'startup'), 'script_install' => Arr::get($parsed, 'scripts.installation.script'), diff --git a/app/Services/Eggs/Sharing/EggUpdateImporterService.php b/app/Services/Eggs/Sharing/EggUpdateImporterService.php index 205314314..4b860a0b3 100644 --- a/app/Services/Eggs/Sharing/EggUpdateImporterService.php +++ b/app/Services/Eggs/Sharing/EggUpdateImporterService.php @@ -74,7 +74,6 @@ class EggUpdateImporterService 'docker_images' => object_get($parsed, 'images') ?? [object_get($parsed, 'image')], 'config_files' => object_get($parsed, 'config.files'), 'config_startup' => object_get($parsed, 'config.startup'), - 'config_logs' => object_get($parsed, 'config.logs'), 'config_stop' => object_get($parsed, 'config.stop'), 'startup' => object_get($parsed, 'startup'), 'script_install' => object_get($parsed, 'scripts.installation.script'), diff --git a/app/Transformers/Api/Application/EggTransformer.php b/app/Transformers/Api/Application/EggTransformer.php index b6ebbc97d..90680152f 100644 --- a/app/Transformers/Api/Application/EggTransformer.php +++ b/app/Transformers/Api/Application/EggTransformer.php @@ -41,10 +41,9 @@ class EggTransformer extends Transformer 'docker_image' => count($model->docker_images) > 0 ? $model->docker_images[0] : '', 'docker_images' => $model->docker_images, 'config' => [ - 'files' => json_decode($model->config_files, true), - 'startup' => json_decode($model->config_startup, true), + 'files' => json_decode($model->config_files), + 'startup' => json_decode($model->config_startup), 'stop' => $model->config_stop, - 'logs' => json_decode($model->config_logs, true), 'file_denylist' => $model->file_denylist, 'extends' => $model->config_from, ], @@ -106,7 +105,6 @@ class EggTransformer extends Transformer 'files' => json_decode($model->inherit_config_files), 'startup' => json_decode($model->inherit_config_startup), 'stop' => $model->inherit_config_stop, - 'logs' => json_decode($model->inherit_config_logs), ]; }); } diff --git a/database/migrations/2021_10_23_185304_drop_config_logs_column_from_eggs_table.php b/database/migrations/2021_10_23_185304_drop_config_logs_column_from_eggs_table.php new file mode 100644 index 000000000..aa9e7511c --- /dev/null +++ b/database/migrations/2021_10_23_185304_drop_config_logs_column_from_eggs_table.php @@ -0,0 +1,32 @@ +dropColumn('config_logs'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('eggs', function (Blueprint $table) { + $table->text('config_logs')->nullable()->after('docker_image'); + }); + } +} diff --git a/resources/scripts/api/admin/eggs/createEgg.ts b/resources/scripts/api/admin/eggs/createEgg.ts index f269d696e..0ad08d9ee 100644 --- a/resources/scripts/api/admin/eggs/createEgg.ts +++ b/resources/scripts/api/admin/eggs/createEgg.ts @@ -1,7 +1,9 @@ import http from '@/api/http'; import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg'; -export default (egg: Partial): Promise => { +type Egg2 = Omit, 'configFiles'>, 'configStartup'> & { configFiles: string, configStartup: string }; + +export default (egg: Partial): Promise => { return new Promise((resolve, reject) => { http.post( '/api/application/eggs', diff --git a/resources/scripts/components/admin/AdminTable.tsx b/resources/scripts/components/admin/AdminTable.tsx index 232221e6b..3bf55345e 100644 --- a/resources/scripts/components/admin/AdminTable.tsx +++ b/resources/scripts/components/admin/AdminTable.tsx @@ -196,9 +196,9 @@ export const Loading = () => { ); }; -export const NoItems = () => { +export const NoItems = ({ className }: { className?: string }) => { return ( -
+
{'No
@@ -227,7 +227,8 @@ export const ContentWrapper = ({ checked, onSelectAllClick, onSearch, children } } setLoading(true); - onSearch(query).then(() => setLoading(false)); + onSearch(query) + .then(() => setLoading(false)); }, 200), [], ); diff --git a/resources/scripts/components/admin/nests/NewEggContainer.tsx b/resources/scripts/components/admin/nests/NewEggContainer.tsx index 28c5b470a..0c48c70a5 100644 --- a/resources/scripts/components/admin/nests/NewEggContainer.tsx +++ b/resources/scripts/components/admin/nests/NewEggContainer.tsx @@ -1,8 +1,51 @@ -import React from 'react'; +import createEgg from '@/api/admin/eggs/createEgg'; +import { EggImageContainer, EggInformationContainer, EggLifecycleContainer, EggProcessContainer, EggProcessContainerRef, EggStartupContainer } from '@/components/admin/nests/eggs/EggSettingsContainer'; +import Button from '@/components/elements/Button'; +import FlashMessageRender from '@/components/FlashMessageRender'; +import useFlash from '@/plugins/useFlash'; +import { Form, Formik, FormikHelpers } from 'formik'; +import React, { useRef } from 'react'; +import { useParams } from 'react-router'; +import { useHistory } from 'react-router-dom'; import tw from 'twin.macro'; import AdminContentBlock from '@/components/admin/AdminContentBlock'; +import { object } from 'yup'; + +interface Values { + name: string; + description: string; + startup: string; + dockerImages: string; + configStop: string; + configStartup: string; + configFiles: string; +} export default () => { + const params = useParams<{ nestId: string }>(); + const history = useHistory(); + + const { clearFlashes, clearAndAddHttpError } = useFlash(); + + const ref = useRef(); + + const submit = async (values: Values, { setSubmitting }: FormikHelpers) => { + clearFlashes('egg:create'); + + const nestId = Number(params.nestId); + + values.configStartup = await ref.current?.getStartupConfiguration() || ''; + values.configFiles = await ref.current?.getFilesConfiguration() || ''; + + createEgg({ ...values, dockerImages: values.dockerImages.split('\n'), nestId }) + .then(egg => history.push(`/admin/nests/${nestId}/eggs/${egg.id}`)) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'egg:create', error }); + }) + .then(() => setSubmitting(false)); + }; + return (
@@ -11,6 +54,48 @@ export default () => {

Add a new egg to the panel.

+ + + + + {({ isSubmitting, isValid }) => ( +
+
+ +
+ + + +
+ + +
+ + + +
+
+ +
+
+ + )} +
); }; diff --git a/resources/scripts/components/admin/nests/eggs/EggSettingsContainer.tsx b/resources/scripts/components/admin/nests/eggs/EggSettingsContainer.tsx index 9ee665f15..299cf4800 100644 --- a/resources/scripts/components/admin/nests/eggs/EggSettingsContainer.tsx +++ b/resources/scripts/components/admin/nests/eggs/EggSettingsContainer.tsx @@ -18,7 +18,7 @@ import tw from 'twin.macro'; import { object } from 'yup'; import { Form, Formik, FormikHelpers, useFormikContext } from 'formik'; -function EggInformationContainer () { +export function EggInformationContainer () { const { isSubmitting } = useFormikContext(); return ( @@ -72,7 +72,7 @@ function EggDetailsContainer ({ egg }: { egg: Egg }) { ); } -function EggStartupContainer ({ className }: { className?: string }) { +export function EggStartupContainer ({ className }: { className?: string }) { const { isSubmitting } = useFormikContext(); return ( @@ -90,7 +90,7 @@ function EggStartupContainer ({ className }: { className?: string }) { ); } -function EggImageContainer () { +export function EggImageContainer () { const { isSubmitting } = useFormikContext(); return ( @@ -107,7 +107,7 @@ function EggImageContainer () { ); } -function EggLifecycleContainer () { +export function EggLifecycleContainer () { const { isSubmitting } = useFormikContext(); return ( @@ -115,8 +115,8 @@ function EggLifecycleContainer () { Promise; getFilesConfiguration: () => Promise; } -const EggProcessContainer = forwardRef( - function EggProcessContainer ({ className, egg }, ref) { - const { isSubmitting } = useFormikContext(); +export const EggProcessContainer = forwardRef( + function EggProcessContainer ({ className }, ref) { + const { isSubmitting, values } = useFormikContext(); let fetchStartupConfiguration: (() => Promise) | null = null; let fetchFilesConfiguration: (() => Promise) | null = null; @@ -166,7 +165,7 @@ const EggProcessContainer = forwardRef( { fetchStartupConfiguration = value; @@ -178,7 +177,7 @@ const EggProcessContainer = forwardRef( { fetchFilesConfiguration = value; @@ -195,7 +194,7 @@ interface Values { description: string; startup: string; dockerImages: string; - stopCommand: string; + configStop: string; configStartup: string; configFiles: string; } @@ -229,9 +228,9 @@ export default function EggSettingsContainer ({ egg }: { egg: Egg }) { description: egg.description || '', startup: egg.startup, dockerImages: egg.dockerImages.join('\n'), - stopCommand: egg.configStop || '', - configStartup: '', - configFiles: '', + configStop: egg.configStop || '', + configStartup: JSON.stringify(egg.configStartup, null, '\t') || '', + configFiles: JSON.stringify(egg.configFiles, null, '\t') || '', }} validationSchema={object().shape({ })} @@ -250,17 +249,13 @@ export default function EggSettingsContainer ({ egg }: { egg: Egg }) {
- +
history.push('/admin/nests')} + onDeleted={() => history.push(`/admin/nests/${egg.nestId}`)} /> );