From 6362731d55987f018502e569e91612ae644d0af7 Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Sun, 12 Sep 2021 22:15:45 -0600 Subject: [PATCH] ui(admin): implement basic server editing --- .../Application/Servers/ServerController.php | 33 ++++++- .../UpdateServerBuildConfigurationRequest.php | 2 +- .../Servers/UpdateServerRequest.php | 57 +++++++++++ .../Api/Application/ServerTransformer.php | 1 + .../scripts/api/admin/servers/getServers.ts | 4 + .../scripts/api/admin/servers/updateServer.ts | 33 ++++++- .../admin/servers/ServerDeleteButton.tsx | 58 +++++++++++ .../components/admin/servers/ServerRouter.tsx | 8 +- .../admin/servers/ServerSettingsContainer.tsx | 98 +++++-------------- .../admin/servers/ServerStartupContainer.tsx | 14 +-- routes/api-application.php | 2 +- 11 files changed, 220 insertions(+), 90 deletions(-) create mode 100644 app/Http/Requests/Api/Application/Servers/UpdateServerRequest.php create mode 100644 resources/scripts/components/admin/servers/ServerDeleteButton.tsx diff --git a/app/Http/Controllers/Api/Application/Servers/ServerController.php b/app/Http/Controllers/Api/Application/Servers/ServerController.php index 9d650c025..40436fb7e 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerController.php @@ -8,6 +8,8 @@ use Illuminate\Http\JsonResponse; use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Services\Servers\ServerCreationService; use Pterodactyl\Services\Servers\ServerDeletionService; +use Pterodactyl\Services\Servers\BuildModificationService; +use Pterodactyl\Services\Servers\DetailsModificationService; use Pterodactyl\Transformers\Api\Application\ServerTransformer; use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException; use Pterodactyl\Http\Requests\Api\Application\Servers\GetServerRequest; @@ -15,23 +17,32 @@ use Pterodactyl\Http\Requests\Api\Application\Servers\GetServersRequest; use Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest; use Pterodactyl\Http\Requests\Api\Application\Servers\StoreServerRequest; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; +use Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerRequest; class ServerController extends ApplicationApiController { private ServerCreationService $creationService; private ServerDeletionService $deletionService; + private BuildModificationService $buildModificationService; + private DetailsModificationService $detailsModificationService; + /** * ServerController constructor. */ public function __construct( ServerCreationService $creationService, - ServerDeletionService $deletionService + ServerDeletionService $deletionService, + BuildModificationService $buildModificationService, + DetailsModificationService $detailsModificationService ) { parent::__construct(); $this->creationService = $creationService; $this->deletionService = $deletionService; + + $this->buildModificationService = $buildModificationService; + $this->detailsModificationService = $detailsModificationService; } /** @@ -99,4 +110,24 @@ class ServerController extends ApplicationApiController return $this->returnNoContent(); } + + /** + * Update a server. + * + * @throws \Throwable + * @throws \Illuminate\Validation\ValidationException + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException + * @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException + */ + public function update(UpdateServerRequest $request, Server $server): array + { + $server = $this->buildModificationService->handle($server, $request->validated()); + $server = $this->detailsModificationService->returnUpdatedModel()->handle($server, $request->validated()); + + return $this->fractal->item($server) + ->transformWith(ServerTransformer::class) + ->toArray(); + } } diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php index 27f831e9f..7045c1e08 100644 --- a/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerBuildConfigurationRequest.php @@ -26,7 +26,7 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest 'limits.threads' => $this->requiredToOptional('threads', $rules['threads'], true), 'limits.disk' => $this->requiredToOptional('disk', $rules['disk'], true), - // Legacy rules to maintain backwards compatable API support without requiring + // Legacy rules to maintain backwards compatible API support without requiring // a major version bump. // // @see https://github.com/pterodactyl/panel/issues/1500 diff --git a/app/Http/Requests/Api/Application/Servers/UpdateServerRequest.php b/app/Http/Requests/Api/Application/Servers/UpdateServerRequest.php new file mode 100644 index 000000000..21a114061 --- /dev/null +++ b/app/Http/Requests/Api/Application/Servers/UpdateServerRequest.php @@ -0,0 +1,57 @@ + $rules['external_id'], + 'name' => $rules['name'], + 'description' => array_merge(['nullable'], $rules['description']), + 'owner_id' => $rules['owner_id'], + 'oom_killer' => 'sometimes|boolean', + + 'memory' => $rules['memory'], + 'swap' => $rules['swap'], + 'disk' => $rules['disk'], + 'io' => $rules['io'], + 'threads' => $rules['threads'], + 'cpu' => $rules['cpu'], + + 'databases' => $rules['database_limit'], + 'allocations' => $rules['allocation_limit'], + 'backups' => $rules['backup_limit'], + ]; + } + + public function validated(): array + { + $data = parent::validated(); + + return [ + 'external_id' => array_get($data, 'external_id'), + 'name' => array_get($data, 'name'), + 'description' => array_get($data, 'description'), + 'owner_id' => array_get($data, 'owner_id'), + 'oom_disabled' => !array_get($data, 'oom_killer'), + + 'memory' => array_get($data, 'memory'), + 'swap' => array_get($data, 'swap'), + 'disk' => array_get($data, 'disk'), + 'io' => array_get($data, 'io'), + 'threads' => array_get($data, 'threads'), + 'cpu' => array_get($data, 'cpu'), + + 'database_limit' => array_get($data, 'databases'), + 'allocation_limit' => array_get($data, 'allocations'), + 'backup_limit' => array_get($data, 'backups'), + ]; + } +} diff --git a/app/Transformers/Api/Application/ServerTransformer.php b/app/Transformers/Api/Application/ServerTransformer.php index fb2c7ceda..9aa3d7b77 100644 --- a/app/Transformers/Api/Application/ServerTransformer.php +++ b/app/Transformers/Api/Application/ServerTransformer.php @@ -72,6 +72,7 @@ class ServerTransformer extends Transformer 'image' => $model->image, 'environment' => $this->environmentService->handle($model), ], + 'oom_killer' => !$model->oom_disabled, 'updated_at' => self::formatTimestamp($model->updated_at), 'created_at' => self::formatTimestamp($model->created_at), ]; diff --git a/resources/scripts/api/admin/servers/getServers.ts b/resources/scripts/api/admin/servers/getServers.ts index c0f546069..92c170238 100644 --- a/resources/scripts/api/admin/servers/getServers.ts +++ b/resources/scripts/api/admin/servers/getServers.ts @@ -43,6 +43,8 @@ export interface Server { environment: Map; } + oomKiller: boolean; + createdAt: Date; updatedAt: Date; @@ -90,6 +92,8 @@ export const rawDataToServer = ({ attributes }: FractalResponseData): Server => environment: attributes.container.environment, }, + oomKiller: attributes.oom_killer, + createdAt: new Date(attributes.created_at), updatedAt: new Date(attributes.updated_at), diff --git a/resources/scripts/api/admin/servers/updateServer.ts b/resources/scripts/api/admin/servers/updateServer.ts index d02870d83..d338a0cdc 100644 --- a/resources/scripts/api/admin/servers/updateServer.ts +++ b/resources/scripts/api/admin/servers/updateServer.ts @@ -1,11 +1,36 @@ import http from '@/api/http'; import { Server, rawDataToServer } from '@/api/admin/servers/getServers'; -export default (id: number, server: Partial, include: string[] = []): Promise => { +export interface Values { + externalId: string; + name: string; + ownerId: number; + oomKiller: boolean; + + memory: number; + swap: number; + disk: number; + io: number; + cpu: number; + threads: string; + + databases: number; + allocations: number; + backups: number; +} + +export default (id: number, server: Partial, include: string[] = []): Promise => { + const data = {}; + + Object.keys(server).forEach((key) => { + const key2 = key.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`); + // @ts-ignore + data[key2] = server[key]; + }); + console.log(data); + return new Promise((resolve, reject) => { - http.patch(`/api/application/servers/${id}`, { - ...server, - }, { params: { include: include.join(',') } }) + http.patch(`/api/application/servers/${id}`, data, { params: { include: include.join(',') } }) .then(({ data }) => resolve(rawDataToServer(data))) .catch(reject); }); diff --git a/resources/scripts/components/admin/servers/ServerDeleteButton.tsx b/resources/scripts/components/admin/servers/ServerDeleteButton.tsx new file mode 100644 index 000000000..4cbd3295e --- /dev/null +++ b/resources/scripts/components/admin/servers/ServerDeleteButton.tsx @@ -0,0 +1,58 @@ +import React, { useState } from 'react'; +import { Actions, useStoreActions } from 'easy-peasy'; +import { ApplicationStore } from '@/state'; +import tw from 'twin.macro'; +import Button from '@/components/elements/Button'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import deleteServer from '@/api/admin/servers/deleteServer'; + +interface Props { + serverId: number; + onDeleted: () => void; +} + +export default ({ serverId, onDeleted }: Props) => { + const [ visible, setVisible ] = useState(false); + const [ loading, setLoading ] = useState(false); + + const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions) => actions.flashes); + + const onDelete = () => { + setLoading(true); + clearFlashes('server'); + + deleteServer(serverId) + .then(() => { + setLoading(false); + onDeleted(); + }) + .catch(error => { + console.error(error); + clearAndAddHttpError({ key: 'server', error }); + + setLoading(false); + setVisible(false); + }); + }; + + return ( + <> + setVisible(false)} + > + Are you sure you want to delete this server? + + + + + ); +}; diff --git a/resources/scripts/components/admin/servers/ServerRouter.tsx b/resources/scripts/components/admin/servers/ServerRouter.tsx index 7051fa1f1..2e67dcdb1 100644 --- a/resources/scripts/components/admin/servers/ServerRouter.tsx +++ b/resources/scripts/components/admin/servers/ServerRouter.tsx @@ -40,7 +40,7 @@ const ServerRouter = () => { useEffect(() => { clearFlashes('server'); - getServer(Number(match.params?.id), [ 'egg' ]) + getServer(Number(match.params?.id), [ 'user' ]) .then(server => setServer(server)) .catch(error => { console.error(error); @@ -52,7 +52,7 @@ const ServerRouter = () => { if (loading || server === undefined) { return ( - +
@@ -62,7 +62,7 @@ const ServerRouter = () => { } return ( - +

{server.name}

@@ -70,7 +70,7 @@ const ServerRouter = () => {
- + diff --git a/resources/scripts/components/admin/servers/ServerSettingsContainer.tsx b/resources/scripts/components/admin/servers/ServerSettingsContainer.tsx index 54c4b764e..2dcf6c96b 100644 --- a/resources/scripts/components/admin/servers/ServerSettingsContainer.tsx +++ b/resources/scripts/components/admin/servers/ServerSettingsContainer.tsx @@ -1,8 +1,11 @@ +import { Server } from '@/api/admin/servers/getServers'; +import ServerDeleteButton from '@/components/admin/servers/ServerDeleteButton'; import React from 'react'; import AdminBox from '@/components/admin/AdminBox'; +import { useHistory } from 'react-router-dom'; import tw from 'twin.macro'; import { object } from 'yup'; -import updateServer from '@/api/admin/servers/updateServer'; +import updateServer, { Values } from '@/api/admin/servers/updateServer'; import Field from '@/components/elements/Field'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import { Form, Formik, FormikHelpers, useFormikContext } from 'formik'; @@ -13,42 +16,9 @@ import OwnerSelect from '@/components/admin/servers/OwnerSelect'; import Button from '@/components/elements/Button'; import FormikSwitch from '@/components/elements/FormikSwitch'; -interface Values { - id: number; - externalId: string; - uuid: string; - identifier: string; - name: string; - - memory: number; - swap: number; - disk: number; - io: number; - cpu: number; - threads: string; - - databases: number; - allocations: number; - backups: number; - - ownerId: number; - nodeId: number; - allocationId: number; - nestId: number; - eggId: number; -} - -const ServerFeatureContainer = () => { +export function ServerFeatureContainer () { const { isSubmitting } = useFormikContext(); - const server = Context.useStoreState(state => state.server); - - if (server === undefined) { - return ( - <> - ); - } - return ( @@ -77,7 +47,7 @@ const ServerFeatureContainer = () => {
{
); -}; +} -const ServerResourceContainer = () => { +export function ServerResourceContainer () { const { isSubmitting } = useFormikContext(); - const server = Context.useStoreState(state => state.server); - - if (server === undefined) { - return ( - <> - ); - } - return ( @@ -171,7 +133,7 @@ const ServerResourceContainer = () => {
@@ -179,19 +141,11 @@ const ServerResourceContainer = () => {
); -}; +} -const ServerSettingsContainer = () => { +export function ServerSettingsContainer ({ server }: { server?: Server }) { const { isSubmitting } = useFormikContext(); - const server = Context.useStoreState(state => state.server); - - if (server === undefined) { - return ( - <> - ); - } - return ( @@ -218,14 +172,16 @@ const ServerSettingsContainer = () => {
- +
); -}; +} + +export default function ServerSettingsContainer2 () { + const history = useHistory(); -export default () => { const { clearFlashes, clearAndAddHttpError } = useStoreActions((actions: Actions) => actions.flashes); const server = Context.useStoreState(state => state.server); @@ -239,6 +195,7 @@ export default () => { const submit = (values: Values, { setSubmitting }: FormikHelpers) => { clearFlashes('server'); + console.log(values); updateServer(server.id, values) .then(() => setServer({ ...server, ...values })) @@ -253,11 +210,10 @@ export default () => { { databases: server.featureLimits.databases, allocations: server.featureLimits.allocations, backups: server.featureLimits.backups, - - ownerId: server.ownerId, - nodeId: server.nodeId, - allocationId: server.allocationId, - nestId: server.nestId, - eggId: server.eggId, }} validationSchema={object().shape({ })} @@ -285,7 +235,7 @@ export default () => {
- +
@@ -295,8 +245,12 @@ export default () => {
-
+
+ history.push('/admin/servers')} + /> @@ -309,4 +263,4 @@ export default () => { } ); -}; +} diff --git a/resources/scripts/components/admin/servers/ServerStartupContainer.tsx b/resources/scripts/components/admin/servers/ServerStartupContainer.tsx index 4120155e5..be2139d03 100644 --- a/resources/scripts/components/admin/servers/ServerStartupContainer.tsx +++ b/resources/scripts/components/admin/servers/ServerStartupContainer.tsx @@ -158,13 +158,13 @@ export default () => { const submit = (values: Values, { setSubmitting }: FormikHelpers) => { clearFlashes('server'); - updateServer(server.id, values) - .then(() => setServer({ ...server, ...values })) - .catch(error => { - console.error(error); - clearAndAddHttpError({ key: 'server', error }); - }) - .then(() => setSubmitting(false)); + // updateServer(server.id, values) + // .then(() => setServer({ ...server, ...values })) + // .catch(error => { + // console.error(error); + // clearAndAddHttpError({ key: 'server', error }); + // }) + // .then(() => setSubmitting(false)); }; return ( diff --git a/routes/api-application.php b/routes/api-application.php index fc6e6c8f4..de67ddf0e 100644 --- a/routes/api-application.php +++ b/routes/api-application.php @@ -164,7 +164,7 @@ Route::group(['prefix' => '/servers'], function () { Route::get('/{server}', [\Pterodactyl\Http\Controllers\Api\Application\Servers\ServerController::class, 'view']); Route::get('/external/{external_id}', [\Pterodactyl\Http\Controllers\Api\Application\Servers\ExternalServerController::class, 'index']); - Route::patch('/{server}/details', [\Pterodactyl\Http\Controllers\Api\Application\Servers\ServerDetailsController::class, 'details']); + Route::patch('/{server}', [\Pterodactyl\Http\Controllers\Api\Application\Servers\ServerController::class, 'update']); Route::patch('/{server}/build', [\Pterodactyl\Http\Controllers\Api\Application\Servers\ServerDetailsController::class, 'build']); Route::patch('/{server}/startup', [\Pterodactyl\Http\Controllers\Api\Application\Servers\StartupController::class, 'index']);