ui(admin): new egg page
This commit is contained in:
parent
336923ec18
commit
cddf2ce41c
15 changed files with 210 additions and 84 deletions
|
@ -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)
|
||||
|
|
|
@ -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',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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' => [
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class DropConfigLogsColumnFromEggsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('eggs', function (Blueprint $table) {
|
||||
$table->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');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
import http from '@/api/http';
|
||||
import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg';
|
||||
|
||||
export default (egg: Partial<Egg>): Promise<Egg> => {
|
||||
type Egg2 = Omit<Omit<Partial<Egg>, 'configFiles'>, 'configStartup'> & { configFiles: string, configStartup: string };
|
||||
|
||||
export default (egg: Partial<Egg2>): Promise<Egg> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post(
|
||||
'/api/application/eggs',
|
||||
|
|
|
@ -196,9 +196,9 @@ export const Loading = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export const NoItems = () => {
|
||||
export const NoItems = ({ className }: { className?: string }) => {
|
||||
return (
|
||||
<div css={tw`w-full flex flex-col items-center justify-center py-6 px-8`}>
|
||||
<div css={tw`w-full flex flex-col items-center justify-center py-6 px-8`} className={className}>
|
||||
<div css={tw`h-48 flex`}>
|
||||
<img src={'/assets/svgs/not_found.svg'} alt={'No Items'} css={tw`h-full select-none`}/>
|
||||
</div>
|
||||
|
@ -227,7 +227,8 @@ export const ContentWrapper = ({ checked, onSelectAllClick, onSearch, children }
|
|||
}
|
||||
|
||||
setLoading(true);
|
||||
onSearch(query).then(() => setLoading(false));
|
||||
onSearch(query)
|
||||
.then(() => setLoading(false));
|
||||
}, 200),
|
||||
[],
|
||||
);
|
||||
|
|
|
@ -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<EggProcessContainerRef>();
|
||||
|
||||
const submit = async (values: Values, { setSubmitting }: FormikHelpers<Values>) => {
|
||||
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 (
|
||||
<AdminContentBlock title={'New Egg'}>
|
||||
<div css={tw`w-full flex flex-row items-center mb-8`}>
|
||||
|
@ -11,6 +54,48 @@ export default () => {
|
|||
<p css={tw`text-base text-neutral-400 whitespace-nowrap overflow-ellipsis overflow-hidden`}>Add a new egg to the panel.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FlashMessageRender key={'egg:create'} css={tw`mb-4`}/>
|
||||
|
||||
<Formik
|
||||
onSubmit={submit}
|
||||
initialValues={{
|
||||
name: '',
|
||||
description: '',
|
||||
startup: '',
|
||||
dockerImages: '',
|
||||
configStop: '',
|
||||
configStartup: '{}',
|
||||
configFiles: '{}',
|
||||
}}
|
||||
validationSchema={object().shape({
|
||||
})}
|
||||
>
|
||||
{({ isSubmitting, isValid }) => (
|
||||
<Form>
|
||||
<div css={tw`grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 mb-6`}>
|
||||
<EggInformationContainer/>
|
||||
</div>
|
||||
|
||||
<EggStartupContainer css={tw`mb-6`}/>
|
||||
|
||||
<div css={tw`grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6 mb-6`}>
|
||||
<EggImageContainer/>
|
||||
<EggLifecycleContainer/>
|
||||
</div>
|
||||
|
||||
<EggProcessContainer ref={ref} css={tw`mb-6`}/>
|
||||
|
||||
<div css={tw`bg-neutral-700 rounded shadow-md py-2 px-6 mb-16`}>
|
||||
<div css={tw`flex flex-row`}>
|
||||
<Button type="submit" size="small" css={tw`ml-auto`} disabled={isSubmitting || !isValid}>
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</AdminContentBlock>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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 () {
|
|||
<SpinnerOverlay visible={isSubmitting}/>
|
||||
|
||||
<Field
|
||||
id={'stopCommand'}
|
||||
name={'stopCommand'}
|
||||
id={'configStop'}
|
||||
name={'configStop'}
|
||||
label={'Stop Command'}
|
||||
type={'text'}
|
||||
css={tw`mb-1`}
|
||||
|
@ -127,17 +127,16 @@ function EggLifecycleContainer () {
|
|||
|
||||
interface EggProcessContainerProps {
|
||||
className?: string;
|
||||
egg: Egg;
|
||||
}
|
||||
|
||||
interface EggProcessContainerRef {
|
||||
export interface EggProcessContainerRef {
|
||||
getStartupConfiguration: () => Promise<string | null>;
|
||||
getFilesConfiguration: () => Promise<string | null>;
|
||||
}
|
||||
|
||||
const EggProcessContainer = forwardRef<any, EggProcessContainerProps>(
|
||||
function EggProcessContainer ({ className, egg }, ref) {
|
||||
const { isSubmitting } = useFormikContext();
|
||||
export const EggProcessContainer = forwardRef<any, EggProcessContainerProps>(
|
||||
function EggProcessContainer ({ className }, ref) {
|
||||
const { isSubmitting, values } = useFormikContext<Values>();
|
||||
|
||||
let fetchStartupConfiguration: (() => Promise<string>) | null = null;
|
||||
let fetchFilesConfiguration: (() => Promise<string>) | null = null;
|
||||
|
@ -166,7 +165,7 @@ const EggProcessContainer = forwardRef<any, EggProcessContainerProps>(
|
|||
<Label>Startup Configuration</Label>
|
||||
<Editor
|
||||
mode={jsonLanguage}
|
||||
initialContent={JSON.stringify(egg.configStartup, null, '\t') || ''}
|
||||
initialContent={values.configStartup}
|
||||
overrides={tw`h-32 rounded`}
|
||||
fetchContent={value => {
|
||||
fetchStartupConfiguration = value;
|
||||
|
@ -178,7 +177,7 @@ const EggProcessContainer = forwardRef<any, EggProcessContainerProps>(
|
|||
<Label>Configuration Files</Label>
|
||||
<Editor
|
||||
mode={jsonLanguage}
|
||||
initialContent={JSON.stringify(egg.configFiles, null, '\t') || ''}
|
||||
initialContent={values.configFiles}
|
||||
overrides={tw`h-48 rounded`}
|
||||
fetchContent={value => {
|
||||
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 }) {
|
|||
<EggLifecycleContainer/>
|
||||
</div>
|
||||
|
||||
<EggProcessContainer
|
||||
ref={ref}
|
||||
egg={egg}
|
||||
css={tw`mb-6`}
|
||||
/>
|
||||
<EggProcessContainer ref={ref} css={tw`mb-6`}/>
|
||||
|
||||
<div css={tw`bg-neutral-700 rounded shadow-md py-2 px-6 mb-16`}>
|
||||
<div css={tw`flex flex-row`}>
|
||||
<EggDeleteButton
|
||||
eggId={egg.id}
|
||||
onDeleted={() => history.push('/admin/nests')}
|
||||
onDeleted={() => history.push(`/admin/nests/${egg.nestId}`)}
|
||||
/>
|
||||
<Button type="submit" size="small" css={tw`ml-auto`} disabled={isSubmitting || !isValid}>
|
||||
Save Changes
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import deleteEggVariable from '@/api/admin/eggs/deleteEggVariable';
|
||||
import { NoItems } from '@/components/admin/AdminTable';
|
||||
import ConfirmationModal from '@/components/elements/ConfirmationModal';
|
||||
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
|
||||
import React, { useState } from 'react';
|
||||
|
@ -160,28 +161,32 @@ export default function EggVariablesContainer ({ egg }: { egg: Egg }) {
|
|||
{({ isSubmitting, isValid }) => (
|
||||
<Form>
|
||||
<div css={tw`flex flex-col mb-16`}>
|
||||
<div css={tw`grid grid-cols-1 lg:grid-cols-2 gap-x-8 gap-y-6`}>
|
||||
{egg.relations?.variables?.map((v, i) => (
|
||||
<EggVariableBox
|
||||
key={i}
|
||||
prefix={`[${i}].`}
|
||||
variable={v}
|
||||
onDeleteClick={(success) => {
|
||||
deleteEggVariable(egg.id, v.id)
|
||||
.then(async () => {
|
||||
await mutate(egg => ({
|
||||
...egg!,
|
||||
relations: {
|
||||
variables: egg!.relations.variables!.filter(v2 => v.id === v2.id),
|
||||
},
|
||||
}));
|
||||
success();
|
||||
})
|
||||
.catch(error => clearAndAddHttpError({ key: 'egg', error }));
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{egg.relations?.variables?.length === 0 ?
|
||||
<NoItems css={tw`bg-neutral-700 rounded-md shadow-md`}/>
|
||||
:
|
||||
<div css={tw`grid grid-cols-1 lg:grid-cols-2 gap-x-8 gap-y-6`}>
|
||||
{egg.relations?.variables?.map((v, i) => (
|
||||
<EggVariableBox
|
||||
key={i}
|
||||
prefix={`[${i}].`}
|
||||
variable={v}
|
||||
onDeleteClick={(success) => {
|
||||
deleteEggVariable(egg.id, v.id)
|
||||
.then(async () => {
|
||||
await mutate(egg => ({
|
||||
...egg!,
|
||||
relations: {
|
||||
variables: egg!.relations.variables!.filter(v2 => v.id === v2.id),
|
||||
},
|
||||
}));
|
||||
success();
|
||||
})
|
||||
.catch(error => clearAndAddHttpError({ key: 'egg', error }));
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div css={tw`bg-neutral-700 rounded shadow-md py-2 px-4 mt-6`}>
|
||||
<div css={tw`flex flex-row`}>
|
||||
|
|
|
@ -80,7 +80,7 @@ export default function NewVariableButton ({ eggId }: { eggId: number }) {
|
|||
</Formik>
|
||||
|
||||
<Button type={'button'} color={'green'} onClick={() => setVisible(true)}>
|
||||
Add a fucking variable
|
||||
New Variable
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue