From e1a01fd59516fc81b6bc7680736639a3669d0857 Mon Sep 17 00:00:00 2001 From: Caleb Date: Thu, 24 Sep 2020 14:43:22 -0400 Subject: [PATCH 01/15] changed package.json to be ignored --- .gitignore | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 58250120e..99c244c1d 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ resources/lang/locales.js resources/assets/pterodactyl/scripts/helpers/ziggy.js resources/assets/scripts/helpers/ziggy.js .phpunit.result.cache +package.json diff --git a/package.json b/package.json index 15e96c4bd..0456b3676 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "watch": "cross-env NODE_ENV=development ./node_modules/.bin/webpack --watch --progress", "build": "cross-env NODE_ENV=development ./node_modules/.bin/webpack --progress", "build:production": "yarn run clean && cross-env NODE_ENV=production ./node_modules/.bin/webpack --mode production", - "serve": "yarn run clean && cross-env PUBLIC_PATH=https://pterodactyl.test:8080 NODE_ENV=development TSC_WATCHFILE=UseFsEventsWithFallbackDynamicPolling webpack-dev-server --host 0.0.0.0 --hot --https --key /etc/ssl/private/pterodactyl.test-key.pem --cert /etc/ssl/private/pterodactyl.test.pem" + "serve": "yarn run clean && cross-env PUBLIC_PATH=https://ptero.test:8080 NODE_ENV=development TSC_WATCHFILE=UseFsEventsWithFallbackDynamicPolling webpack-dev-server --host 0.0.0.0 --hot" }, "browserslist": [ "> 0.5%", From 8f8bd0be8353c9291395b1673c00cbd9abb8f490 Mon Sep 17 00:00:00 2001 From: Caleb Date: Mon, 28 Sep 2020 11:50:34 -0400 Subject: [PATCH 02/15] Added Automatic Allocations Known issues: - Port range to auto create is hard coded - React interface is still a WIP. --- .env.bkup | 41 +++++++++++ .../Servers/NetworkAllocationController.php | 73 ++++++++++++++++++- .../Servers/Network/NewAllocationRequest.php | 20 +++++ .../api/server/network/newServerAllocation.ts | 11 +++ .../server/network/AllocationRow.tsx | 2 +- .../server/network/NetworkContainer.tsx | 40 +++++++++- routes/api-client.php | 1 + webpack.config.js | 2 +- 8 files changed, 186 insertions(+), 4 deletions(-) create mode 100644 .env.bkup create mode 100644 app/Http/Requests/Api/Client/Servers/Network/NewAllocationRequest.php create mode 100644 resources/scripts/api/server/network/newServerAllocation.ts diff --git a/.env.bkup b/.env.bkup new file mode 100644 index 000000000..1f1e7dea2 --- /dev/null +++ b/.env.bkup @@ -0,0 +1,41 @@ +APP_ENV=local +APP_DEBUG=true +APP_KEY=base64:TdNqOn+57eXjhYF9xaM4WZZ0JAAyeGcHF/5dcclZhhk= +APP_THEME=pterodactyl +APP_TIMEZONE=America/Los_Angeles +APP_CLEAR_TASKLOG=720 +APP_DELETE_MINUTES=10 +APP_ENVIRONMENT_ONLY=false +LOG_CHANNEL=daily +APP_LOCALE=en + +DB_HOST=127.0.0.1 +DB_PORT=33060 +DB_DATABASE=panel +DB_USERNAME=pterodactyl +DB_PASSWORD=pterodactyl + +HASHIDS_SALT=g9nStpee2fwL2bUsfQqy +HASHIDS_LENGTH=8 + +MAIL_DRIVER=smtp +MAIL_HOST=host.pterodactyl.test +MAIL_PORT=1025 +MAIL_USERNAME= +MAIL_PASSWORD= +MAIL_ENCRYPTION=tls +MAIL_FROM="outgoing@example.com" + +QUEUE_HIGH=high +QUEUE_STANDARD=standard +QUEUE_LOW=low + +APP_SERVICE_AUTHOR="you@example.com" +APP_URL="http://pterodactyl.test" +CACHE_DRIVER=redis +SESSION_DRIVER=redis +QUEUE_CONNECTION=redis +REDIS_HOST=host.pterodactyl.test +REDIS_PASSWORD=null +REDIS_PORT=6379 +MAIL_FROM_NAME="Pterodactyl Panel" \ No newline at end of file diff --git a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php index 162d815d9..edaa8dc67 100644 --- a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php +++ b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php @@ -13,7 +13,10 @@ use Pterodactyl\Http\Controllers\Api\Client\ClientApiController; use Pterodactyl\Http\Requests\Api\Client\Servers\Network\GetNetworkRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Network\DeleteAllocationRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Network\UpdateAllocationRequest; +use Pterodactyl\Http\Requests\Api\Client\Servers\Network\NewAllocationRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Network\SetPrimaryAllocationRequest; +use Pterodactyl\Services\Allocations\AssignmentService; +use Illuminate\Support\Facades\Log; class NetworkAllocationController extends ClientApiController { @@ -27,20 +30,28 @@ class NetworkAllocationController extends ClientApiController */ private $serverRepository; + /** + * @var \Pterodactyl\Services\Allocations\AssignmentService + */ + protected $assignmentService; + /** * NetworkController constructor. * * @param \Pterodactyl\Repositories\Eloquent\AllocationRepository $repository * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $serverRepository + * @param \Pterodactyl\Services\Allocations\AssignmentService $assignmentService */ public function __construct( AllocationRepository $repository, - ServerRepository $serverRepository + ServerRepository $serverRepository, + AssignmentService $assignmentService ) { parent::__construct(); $this->repository = $repository; $this->serverRepository = $serverRepository; + $this->assignmentService = $assignmentService; } /** @@ -100,6 +111,66 @@ class NetworkAllocationController extends ClientApiController ->toArray(); } + /** + * Set the notes for the allocation for a server. + *s + * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Network\NewAllocationRequest $request + * @param \Pterodactyl\Models\Server $server + * @param \Pterodactyl\Models\Allocation $allocation + * @return array + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Model\DataValidationException + * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + */ + public function addNew(NewAllocationRequest $request, Server $server): array + { + Log::info('addNew()'); + $topRange = 25700; + $bottomRange = 25565; + + if($server->allocation_limit <= $server->allocations->count()) { + Log::error('You have created the maximum number of allocations!'); + throw new DisplayException( + 'You have created the maximum number of allocations!' + ); + } + + $allocation = $server->node->allocations()->where('ip',$server->allocation->ip)->whereNull('server_id')->first(); + + if(!$allocation) { + if($server->node->allocations()->where('ip',$server->allocation->ip)->count() >= $topRange-$bottomRange) { + Log::error('No allocations available!'); + throw new DisplayException( + 'No more allocations available!' + ); + } + Log::info('Creating new allocation...'); + $allPorts = $server->node->allocations()->select(['port'])->where('ip',$server->allocation->ip)->get()->pluck('port')->toArray(); + + do { + $port = rand($bottomRange, $topRange); + Log::info('Picking port....'); + // TODO ADD ITERATOR THAT TIMES OUT AFTER SEARCHING FOR SO MUCH TIME? + } while(array_search($port, $allPorts)); + + $this->assignmentService->handle($server->node,[ + 'allocation_ip'=>$server->allocation->ip, + 'allocation_ports'=>[$port], + 'server_id'=>$server->id + ]); + + $allocation = $server->node->allocations()->where('ip',$server->allocation->ip)->where('port', $port)->first(); + + } + + $allocation->update(['server_id' => $server->id]); + + return $this->fractal->item($allocation) + ->transformWith($this->getTransformer(AllocationTransformer::class)) + ->toArray(); + } + /** * Delete an allocation from a server. * diff --git a/app/Http/Requests/Api/Client/Servers/Network/NewAllocationRequest.php b/app/Http/Requests/Api/Client/Servers/Network/NewAllocationRequest.php new file mode 100644 index 000000000..7628afaaf --- /dev/null +++ b/app/Http/Requests/Api/Client/Servers/Network/NewAllocationRequest.php @@ -0,0 +1,20 @@ + => { + return new Promise((resolve, reject) => { + http.get(`/api/client/servers/${uuid}/network/allocations/new`) + .then(({ data }) => resolve(rawDataToServerAllocation(data))) + .catch(reject); + }); +}; diff --git a/resources/scripts/components/server/network/AllocationRow.tsx b/resources/scripts/components/server/network/AllocationRow.tsx index c8b7870e7..e182e888e 100644 --- a/resources/scripts/components/server/network/AllocationRow.tsx +++ b/resources/scripts/components/server/network/AllocationRow.tsx @@ -42,7 +42,7 @@ const AllocationRow = ({ allocation, onSetPrimary, onNotesChanged }: Props) => { return ( 0 ? tw`mt-2 overflow-x-auto` : tw`overflow-x-auto`} + css={tw`mt-2 overflow-x-auto`} >
diff --git a/resources/scripts/components/server/network/NetworkContainer.tsx b/resources/scripts/components/server/network/NetworkContainer.tsx index 1cbab974d..d64b11de4 100644 --- a/resources/scripts/components/server/network/NetworkContainer.tsx +++ b/resources/scripts/components/server/network/NetworkContainer.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import useSWR from 'swr'; import getServerAllocations from '@/api/server/network/getServerAllocations'; import { Allocation } from '@/api/server/getServer'; @@ -9,10 +9,15 @@ import { ServerContext } from '@/state/server'; import { useDeepMemoize } from '@/plugins/useDeepMemoize'; import AllocationRow from '@/components/server/network/AllocationRow'; import setPrimaryServerAllocation from '@/api/server/network/setPrimaryServerAllocation'; +import Button from '@/components/elements/Button'; +import newServerAllocation from '@/api/server/network/newServerAllocation'; +import tw from 'twin.macro'; +import GreyRowBox from '@/components/elements/GreyRowBox'; const NetworkContainer = () => { const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const allocations = useDeepMemoize(ServerContext.useStoreState(state => state.server.data!.allocations)); + const [ addingAllocation, setAddingAllocation ] = useState(false); const { clearFlashes, clearAndAddHttpError } = useFlash(); const { data, error, mutate } = useSWR(uuid, key => getServerAllocations(key), { @@ -39,6 +44,22 @@ const NetworkContainer = () => { }); }, []); + const getNewAllocation = () => { + clearFlashes('server:network'); + setAddingAllocation(true); + + newServerAllocation(uuid) + .then(allocation => { + mutate(data => ({ ...data, items: data.concat(allocation) }), false); + setAddingAllocation(false); + }) + .catch(error => { + clearAndAddHttpError({ key: 'server:network', error }); + mutate(data, false); + setAddingAllocation(false); + }); + }; + const onNotesAdded = useCallback((id: number, notes: string) => { mutate(data?.map(a => a.id === id ? { ...a, notes } : a), false); }, []); @@ -57,6 +78,23 @@ const NetworkContainer = () => { /> )) } + + {addingAllocation ? + + : + + } + ); }; diff --git a/routes/api-client.php b/routes/api-client.php index 51a2f0dec..5a6719567 100644 --- a/routes/api-client.php +++ b/routes/api-client.php @@ -83,6 +83,7 @@ Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServ Route::get('/allocations', 'Servers\NetworkAllocationController@index'); Route::post('/allocations/{allocation}', 'Servers\NetworkAllocationController@update'); Route::post('/allocations/{allocation}/primary', 'Servers\NetworkAllocationController@setPrimary'); + Route::get('/allocations/new', 'Servers\NetworkAllocationController@addNew'); Route::delete('/allocations/{allocation}', 'Servers\NetworkAllocationController@delete'); }); diff --git a/webpack.config.js b/webpack.config.js index 492c74bf9..c6d38c211 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -112,7 +112,7 @@ module.exports = { contentBase: path.join(__dirname, '/public'), publicPath: (process.env.PUBLIC_PATH || '') + '/assets/', allowedHosts: [ - '.pterodactyl.test', + 'pterodactyl.test', ], headers: { 'Access-Control-Allow-Origin': '*', From d80660f04705a4a6bae4fcddb6dd88b53f33a020 Mon Sep 17 00:00:00 2001 From: Caleb Date: Mon, 28 Sep 2020 16:14:14 -0400 Subject: [PATCH 03/15] Added admin configuration This is not working just yet but the fields have been added to the admin control area. --- .../Servers/NetworkAllocationController.php | 22 +++++++-- .../Settings/AdvancedSettingsFormRequest.php | 6 +++ package.json | 2 +- .../server/network/NetworkContainer.tsx | 47 +++++++++++-------- .../views/admin/settings/advanced.blade.php | 33 +++++++++++++ 5 files changed, 85 insertions(+), 25 deletions(-) diff --git a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php index edaa8dc67..a4890e0c1 100644 --- a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php +++ b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers; +use Illuminate\Contracts\Config\Repository; use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; use Pterodactyl\Models\Allocation; @@ -16,7 +17,6 @@ use Pterodactyl\Http\Requests\Api\Client\Servers\Network\UpdateAllocationRequest use Pterodactyl\Http\Requests\Api\Client\Servers\Network\NewAllocationRequest; use Pterodactyl\Http\Requests\Api\Client\Servers\Network\SetPrimaryAllocationRequest; use Pterodactyl\Services\Allocations\AssignmentService; -use Illuminate\Support\Facades\Log; class NetworkAllocationController extends ClientApiController { @@ -41,17 +41,27 @@ class NetworkAllocationController extends ClientApiController * @param \Pterodactyl\Repositories\Eloquent\AllocationRepository $repository * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $serverRepository * @param \Pterodactyl\Services\Allocations\AssignmentService $assignmentService + * @param \Illuminate\Contracts\Config\Repository $config */ + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + private $config; + public function __construct( AllocationRepository $repository, ServerRepository $serverRepository, - AssignmentService $assignmentService + AssignmentService $assignmentService, + Repository $config + ) { parent::__construct(); $this->repository = $repository; $this->serverRepository = $serverRepository; $this->assignmentService = $assignmentService; + $this->config = $config; } /** @@ -126,8 +136,10 @@ class NetworkAllocationController extends ClientApiController public function addNew(NewAllocationRequest $request, Server $server): array { Log::info('addNew()'); - $topRange = 25700; - $bottomRange = 25565; + $topRange = config('pterodactyl.allocation.start'); + $bottomRange = config('pterodactyl.allocation.stop'); + Log::error($bottomRange); + Log::error($topRange); if($server->allocation_limit <= $server->allocations->count()) { Log::error('You have created the maximum number of allocations!'); @@ -139,7 +151,7 @@ class NetworkAllocationController extends ClientApiController $allocation = $server->node->allocations()->where('ip',$server->allocation->ip)->whereNull('server_id')->first(); if(!$allocation) { - if($server->node->allocations()->where('ip',$server->allocation->ip)->count() >= $topRange-$bottomRange) { + if($server->node->allocations()->where('ip',$server->allocation->ip)->where([['port', '>=', $bottomRange ], ['port', '<=', $topRange],])->count() >= $topRange-$bottomRange || config('pterodactyl.allocation.enabled', 0)) { Log::error('No allocations available!'); throw new DisplayException( 'No more allocations available!' diff --git a/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php index a80d8dab9..023b2d89a 100644 --- a/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php +++ b/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php @@ -21,6 +21,9 @@ class AdvancedSettingsFormRequest extends AdminFormRequest 'pterodactyl:guzzle:connect_timeout' => 'required|integer|between:1,60', 'pterodactyl:console:count' => 'required|integer|min:1', 'pterodactyl:console:frequency' => 'required|integer|min:10', + 'allocation:enabled' => 'required|in:true,false', + 'pterodactyl:allocation:start' => 'required|integer|between:2000,65535', + 'pterodactyl:allocation:stop' => 'required|integer|between:2000,65535', ]; } @@ -37,6 +40,9 @@ class AdvancedSettingsFormRequest extends AdminFormRequest 'pterodactyl:guzzle:connect_timeout' => 'HTTP Connection Timeout', 'pterodactyl:console:count' => 'Console Message Count', 'pterodactyl:console:frequency' => 'Console Frequency Tick', + 'allocation:enabled' => 'Auto Create Allocations Enabled', + 'pterodactyl:allocation:start' => 'Starting Port', + 'pterodactyl:allocation:stop' => 'Ending Port', ]; } } diff --git a/package.json b/package.json index 0456b3676..15e96c4bd 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "watch": "cross-env NODE_ENV=development ./node_modules/.bin/webpack --watch --progress", "build": "cross-env NODE_ENV=development ./node_modules/.bin/webpack --progress", "build:production": "yarn run clean && cross-env NODE_ENV=production ./node_modules/.bin/webpack --mode production", - "serve": "yarn run clean && cross-env PUBLIC_PATH=https://ptero.test:8080 NODE_ENV=development TSC_WATCHFILE=UseFsEventsWithFallbackDynamicPolling webpack-dev-server --host 0.0.0.0 --hot" + "serve": "yarn run clean && cross-env PUBLIC_PATH=https://pterodactyl.test:8080 NODE_ENV=development TSC_WATCHFILE=UseFsEventsWithFallbackDynamicPolling webpack-dev-server --host 0.0.0.0 --hot --https --key /etc/ssl/private/pterodactyl.test-key.pem --cert /etc/ssl/private/pterodactyl.test.pem" }, "browserslist": [ "> 0.5%", diff --git a/resources/scripts/components/server/network/NetworkContainer.tsx b/resources/scripts/components/server/network/NetworkContainer.tsx index d64b11de4..d28e10c60 100644 --- a/resources/scripts/components/server/network/NetworkContainer.tsx +++ b/resources/scripts/components/server/network/NetworkContainer.tsx @@ -16,6 +16,7 @@ import GreyRowBox from '@/components/elements/GreyRowBox'; const NetworkContainer = () => { const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const allocationLimit = ServerContext.useStoreState(state => state.server.data!.featureLimits.allocations); const allocations = useDeepMemoize(ServerContext.useStoreState(state => state.server.data!.allocations)); const [ addingAllocation, setAddingAllocation ] = useState(false); @@ -48,14 +49,16 @@ const NetworkContainer = () => { clearFlashes('server:network'); setAddingAllocation(true); + const initial = data; + newServerAllocation(uuid) .then(allocation => { - mutate(data => ({ ...data, items: data.concat(allocation) }), false); + mutate(data?.concat(allocation), false); setAddingAllocation(false); }) .catch(error => { clearAndAddHttpError({ key: 'server:network', error }); - mutate(data, false); + mutate(initial, false); setAddingAllocation(false); }); }; @@ -78,23 +81,29 @@ const NetworkContainer = () => { /> )) } - - {addingAllocation ? - - : - - } - + {allocationLimit > data!.length ? + + {addingAllocation ? + + : + + } + + : +

+ You have reached the max number of allocations allowed for your server. +

+ } ); }; diff --git a/resources/views/admin/settings/advanced.blade.php b/resources/views/admin/settings/advanced.blade.php index 7a1d616c5..fee99b359 100644 --- a/resources/views/admin/settings/advanced.blade.php +++ b/resources/views/admin/settings/advanced.blade.php @@ -105,6 +105,39 @@
+
+
+

Automatic Allocation Creation

+
+
+
+
+ +
+ +

If enabled, the panel will attempt to auto create a new allocation in the range specified if there are no more allocations already created on the node.

+
+
+
+ +
+ +

The starting port in the range that can be automatically allocated.

+
+
+
+ +
+ +

The ending port in the range that can be automatically allocated.

+
+
+
+
+
diff --git a/resources/views/admin/settings/advanced.blade.php b/resources/views/admin/settings/advanced.blade.php index fee99b359..f749511f3 100644 --- a/resources/views/admin/settings/advanced.blade.php +++ b/resources/views/admin/settings/advanced.blade.php @@ -115,8 +115,8 @@

If enabled, the panel will attempt to auto create a new allocation in the range specified if there are no more allocations already created on the node.

From 24cad5587b4ae4e5a365fde7c712897173b8e799 Mon Sep 17 00:00:00 2001 From: Caleb Date: Tue, 29 Sep 2020 12:03:14 -0400 Subject: [PATCH 05/15] Update .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 99c244c1d..58250120e 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,3 @@ resources/lang/locales.js resources/assets/pterodactyl/scripts/helpers/ziggy.js resources/assets/scripts/helpers/ziggy.js .phpunit.result.cache -package.json From 0017ab5397b78626a579ad7beff0be6abbb18f6d Mon Sep 17 00:00:00 2001 From: Caleb Date: Tue, 29 Sep 2020 12:04:16 -0400 Subject: [PATCH 06/15] Delete .env.bkup --- .env.bkup | 41 ----------------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 .env.bkup diff --git a/.env.bkup b/.env.bkup deleted file mode 100644 index 1f1e7dea2..000000000 --- a/.env.bkup +++ /dev/null @@ -1,41 +0,0 @@ -APP_ENV=local -APP_DEBUG=true -APP_KEY=base64:TdNqOn+57eXjhYF9xaM4WZZ0JAAyeGcHF/5dcclZhhk= -APP_THEME=pterodactyl -APP_TIMEZONE=America/Los_Angeles -APP_CLEAR_TASKLOG=720 -APP_DELETE_MINUTES=10 -APP_ENVIRONMENT_ONLY=false -LOG_CHANNEL=daily -APP_LOCALE=en - -DB_HOST=127.0.0.1 -DB_PORT=33060 -DB_DATABASE=panel -DB_USERNAME=pterodactyl -DB_PASSWORD=pterodactyl - -HASHIDS_SALT=g9nStpee2fwL2bUsfQqy -HASHIDS_LENGTH=8 - -MAIL_DRIVER=smtp -MAIL_HOST=host.pterodactyl.test -MAIL_PORT=1025 -MAIL_USERNAME= -MAIL_PASSWORD= -MAIL_ENCRYPTION=tls -MAIL_FROM="outgoing@example.com" - -QUEUE_HIGH=high -QUEUE_STANDARD=standard -QUEUE_LOW=low - -APP_SERVICE_AUTHOR="you@example.com" -APP_URL="http://pterodactyl.test" -CACHE_DRIVER=redis -SESSION_DRIVER=redis -QUEUE_CONNECTION=redis -REDIS_HOST=host.pterodactyl.test -REDIS_PASSWORD=null -REDIS_PORT=6379 -MAIL_FROM_NAME="Pterodactyl Panel" \ No newline at end of file From 4770af453b8515558a03163ddc744e798fc2b206 Mon Sep 17 00:00:00 2001 From: Caleb Date: Tue, 29 Sep 2020 14:42:02 -0400 Subject: [PATCH 07/15] Removed loggin and reverted changes to webpack --- .../Api/Client/Servers/NetworkAllocationController.php | 8 +------- webpack.config.js | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php index 341d4e926..6fcad7750 100644 --- a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php +++ b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php @@ -136,14 +136,10 @@ class NetworkAllocationController extends ClientApiController */ public function addNew(NewAllocationRequest $request, Server $server): array { - Log::info('addNew()'); $topRange = config('pterodactyl.allocation.stop',0); $bottomRange = config('pterodactyl.allocation.start',0); - Log::error($bottomRange); - Log::error($topRange); if($server->allocation_limit <= $server->allocations->count()) { - Log::error('You have created the maximum number of allocations!'); throw new DisplayException( 'You have created the maximum number of allocations!' ); @@ -153,17 +149,15 @@ class NetworkAllocationController extends ClientApiController if(!$allocation) { if($server->node->allocations()->where('ip',$server->allocation->ip)->where([['port', '>=', $bottomRange ], ['port', '<=', $topRange],])->count() >= $topRange-$bottomRange+1 || !config('allocation.enabled', false)) { - Log::error('No allocations available!'); throw new DisplayException( 'No more allocations available!' ); } - Log::info('Creating new allocation...'); + $allPorts = $server->node->allocations()->select(['port'])->where('ip',$server->allocation->ip)->get()->pluck('port')->toArray(); do { $port = rand($bottomRange, $topRange); - Log::info('Picking port....'); } while(array_search($port, $allPorts)); $this->assignmentService->handle($server->node,[ diff --git a/webpack.config.js b/webpack.config.js index c6d38c211..492c74bf9 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -112,7 +112,7 @@ module.exports = { contentBase: path.join(__dirname, '/public'), publicPath: (process.env.PUBLIC_PATH || '') + '/assets/', allowedHosts: [ - 'pterodactyl.test', + '.pterodactyl.test', ], headers: { 'Access-Control-Allow-Origin': '*', From 59305f14dd74ea60a894095dee6bd0211e6f0641 Mon Sep 17 00:00:00 2001 From: Pterodactyl CI Date: Tue, 13 Oct 2020 04:04:58 +0000 Subject: [PATCH 08/15] bump version for release --- config/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/app.php b/config/app.php index f57036f95..f1fb42d83 100644 --- a/config/app.php +++ b/config/app.php @@ -9,7 +9,7 @@ return [ | change this value if you are not maintaining your own internal versions. */ - 'version' => 'canary', + 'version' => '1.0.1', /* |-------------------------------------------------------------------------- From db7e4e749f8afc0b980e3579a2469c8f83a959e9 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 31 Oct 2020 14:10:53 -0700 Subject: [PATCH 09/15] UI cleanup for add allocation button --- ...llocation.ts => createServerAllocation.ts} | 0 .../server/network/NetworkContainer.tsx | 86 ++++++++----------- 2 files changed, 37 insertions(+), 49 deletions(-) rename resources/scripts/api/server/network/{newServerAllocation.ts => createServerAllocation.ts} (100%) diff --git a/resources/scripts/api/server/network/newServerAllocation.ts b/resources/scripts/api/server/network/createServerAllocation.ts similarity index 100% rename from resources/scripts/api/server/network/newServerAllocation.ts rename to resources/scripts/api/server/network/createServerAllocation.ts diff --git a/resources/scripts/components/server/network/NetworkContainer.tsx b/resources/scripts/components/server/network/NetworkContainer.tsx index d28e10c60..cd789657d 100644 --- a/resources/scripts/components/server/network/NetworkContainer.tsx +++ b/resources/scripts/components/server/network/NetworkContainer.tsx @@ -10,15 +10,16 @@ import { useDeepMemoize } from '@/plugins/useDeepMemoize'; import AllocationRow from '@/components/server/network/AllocationRow'; import setPrimaryServerAllocation from '@/api/server/network/setPrimaryServerAllocation'; import Button from '@/components/elements/Button'; -import newServerAllocation from '@/api/server/network/newServerAllocation'; +import createServerAllocation from '@/api/server/network/createServerAllocation'; import tw from 'twin.macro'; -import GreyRowBox from '@/components/elements/GreyRowBox'; +import Can from '@/components/elements/Can'; +import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; const NetworkContainer = () => { + const [ loading, setLoading ] = useState(false); const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const allocationLimit = ServerContext.useStoreState(state => state.server.data!.featureLimits.allocations); const allocations = useDeepMemoize(ServerContext.useStoreState(state => state.server.data!.allocations)); - const [ addingAllocation, setAddingAllocation ] = useState(false); const { clearFlashes, clearAndAddHttpError } = useFlash(); const { data, error, mutate } = useSWR(uuid, key => getServerAllocations(key), { @@ -32,7 +33,7 @@ const NetworkContainer = () => { } }, [ error ]); - const setPrimaryAllocation = useCallback((id: number) => { + const setPrimaryAllocation = (id: number) => { clearFlashes('server:network'); const initial = data; @@ -43,24 +44,16 @@ const NetworkContainer = () => { clearAndAddHttpError({ key: 'server:network', error }); mutate(initial, false); }); - }, []); + }; - const getNewAllocation = () => { + const onCreateAllocation = () => { clearFlashes('server:network'); - setAddingAllocation(true); - const initial = data; - - newServerAllocation(uuid) - .then(allocation => { - mutate(data?.concat(allocation), false); - setAddingAllocation(false); - }) - .catch(error => { - clearAndAddHttpError({ key: 'server:network', error }); - mutate(initial, false); - setAddingAllocation(false); - }); + setLoading(true); + createServerAllocation(uuid) + .then(allocation => mutate(data?.concat(allocation), false)) + .catch(error => clearAndAddHttpError({ key: 'server:network', error })) + .then(() => setLoading(false)); }; const onNotesAdded = useCallback((id: number, notes: string) => { @@ -72,37 +65,32 @@ const NetworkContainer = () => { {!data ? : - data.map(allocation => ( - - )) - } - {allocationLimit > data!.length ? - - {addingAllocation ? - - : - + <> + { + data.map(allocation => ( + + )) } - - : -

- You have reached the max number of allocations allowed for your server. -

+ + +
+

+ You are currently using {data.length} of {allocationLimit} allowed allocations for this + server. +

+ {allocationLimit > data.length && + + } +
+
+ } ); From c6bd7ff661fe5f5c79604a8f04a159ab790a7b05 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 31 Oct 2020 14:58:15 -0700 Subject: [PATCH 10/15] Improve logic handle auto-allocation of ports for a server --- .../AutoAllocationNotEnabledException.php | 18 +++ ...oAutoAllocationSpaceAvailableException.php | 18 +++ .../Servers/NetworkAllocationController.php | 68 ++------- .../Settings/AdvancedSettingsFormRequest.php | 12 +- app/Providers/SettingsServiceProvider.php | 6 +- .../FindAssignableAllocationService.php | 130 ++++++++++++++++++ config/pterodactyl.php | 6 + .../server/network/createServerAllocation.ts | 10 +- .../views/admin/settings/advanced.blade.php | 10 +- routes/api-client.php | 2 +- 10 files changed, 201 insertions(+), 79 deletions(-) create mode 100644 app/Exceptions/Service/Allocation/AutoAllocationNotEnabledException.php create mode 100644 app/Exceptions/Service/Allocation/NoAutoAllocationSpaceAvailableException.php create mode 100644 app/Services/Allocations/FindAssignableAllocationService.php diff --git a/app/Exceptions/Service/Allocation/AutoAllocationNotEnabledException.php b/app/Exceptions/Service/Allocation/AutoAllocationNotEnabledException.php new file mode 100644 index 000000000..a593347e6 --- /dev/null +++ b/app/Exceptions/Service/Allocation/AutoAllocationNotEnabledException.php @@ -0,0 +1,18 @@ +repository = $repository; $this->serverRepository = $serverRepository; - $this->assignmentService = $assignmentService; - $this->config = $config; + $this->assignableAllocationService = $assignableAllocationService; } /** @@ -125,52 +113,16 @@ class NetworkAllocationController extends ClientApiController /** * Set the notes for the allocation for a server. *s + * * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Network\NewAllocationRequest $request * @param \Pterodactyl\Models\Server $server - * @param \Pterodactyl\Models\Allocation $allocation * @return array * * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ - public function addNew(NewAllocationRequest $request, Server $server): array + public function store(NewAllocationRequest $request, Server $server): array { - $topRange = config('pterodactyl.allocation.stop',0); - $bottomRange = config('pterodactyl.allocation.start',0); - - if($server->allocation_limit <= $server->allocations->count()) { - throw new DisplayException( - 'You have created the maximum number of allocations!' - ); - } - - $allocation = $server->node->allocations()->where('ip',$server->allocation->ip)->whereNull('server_id')->first(); - - if(!$allocation) { - if($server->node->allocations()->where('ip',$server->allocation->ip)->where([['port', '>=', $bottomRange ], ['port', '<=', $topRange],])->count() >= $topRange-$bottomRange+1 || !config('allocation.enabled', false)) { - throw new DisplayException( - 'No more allocations available!' - ); - } - - $allPorts = $server->node->allocations()->select(['port'])->where('ip',$server->allocation->ip)->get()->pluck('port')->toArray(); - - do { - $port = rand($bottomRange, $topRange); - } while(array_search($port, $allPorts)); - - $this->assignmentService->handle($server->node,[ - 'allocation_ip'=>$server->allocation->ip, - 'allocation_ports'=>[$port], - 'server_id'=>$server->id - ]); - - $allocation = $server->node->allocations()->where('ip',$server->allocation->ip)->where('port', $port)->first(); - - } - - $allocation->update(['server_id' => $server->id]); + $allocation = $this->assignableAllocationService->handle($server); return $this->fractal->item($allocation) ->transformWith($this->getTransformer(AllocationTransformer::class)) diff --git a/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php b/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php index 496268293..76f786cf9 100644 --- a/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php +++ b/app/Http/Requests/Admin/Settings/AdvancedSettingsFormRequest.php @@ -21,9 +21,9 @@ class AdvancedSettingsFormRequest extends AdminFormRequest 'pterodactyl:guzzle:connect_timeout' => 'required|integer|between:1,60', 'pterodactyl:console:count' => 'required|integer|min:1', 'pterodactyl:console:frequency' => 'required|integer|min:10', - 'allocation:enabled' => 'required|in:true,false', - 'pterodactyl:allocation:start' => 'required|integer|between:2000,65535', - 'pterodactyl:allocation:stop' => 'required|integer|between:2000,65535', + 'pterodactyl:client_features:allocations:enabled' => 'required|in:true,false', + 'pterodactyl:client_features:allocations:range_start' => 'required|integer|between:1024,65535', + 'pterodactyl:client_features:allocations:range_end' => 'required|integer|between:1024,65535', ]; } @@ -40,9 +40,9 @@ class AdvancedSettingsFormRequest extends AdminFormRequest 'pterodactyl:guzzle:connect_timeout' => 'HTTP Connection Timeout', 'pterodactyl:console:count' => 'Console Message Count', 'pterodactyl:console:frequency' => 'Console Frequency Tick', - 'allocation:enabled' => 'Auto Create Allocations Enabled', - 'pterodactyl:allocation:start' => 'Starting Port', - 'pterodactyl:allocation:stop' => 'Ending Port', + 'pterodactyl:client_features:allocations:enabled' => 'Auto Create Allocations Enabled', + 'pterodactyl:client_features:allocations:range_start' => 'Starting Port', + 'pterodactyl:client_features:allocations:range_end' => 'Ending Port', ]; } } diff --git a/app/Providers/SettingsServiceProvider.php b/app/Providers/SettingsServiceProvider.php index 042c12bf9..5cbcec105 100644 --- a/app/Providers/SettingsServiceProvider.php +++ b/app/Providers/SettingsServiceProvider.php @@ -30,9 +30,9 @@ class SettingsServiceProvider extends ServiceProvider 'pterodactyl:console:count', 'pterodactyl:console:frequency', 'pterodactyl:auth:2fa_required', - 'allocation:enabled', - 'pterodactyl:allocation:stop', - 'pterodactyl:allocation:start', + 'pterodactyl:client_features:allocations:enabled', + 'pterodactyl:client_features:allocations:range_start', + 'pterodactyl:client_features:allocations:range_end', ]; /** diff --git a/app/Services/Allocations/FindAssignableAllocationService.php b/app/Services/Allocations/FindAssignableAllocationService.php new file mode 100644 index 000000000..43b4c9d9d --- /dev/null +++ b/app/Services/Allocations/FindAssignableAllocationService.php @@ -0,0 +1,130 @@ +service = $service; + } + + /** + * Finds an existing unassigned allocation and attempts to assign it to the given server. If + * no allocation can be found, a new one will be created with a random port between the defined + * range from the configuration. + * + * @param \Pterodactyl\Models\Server $server + * @return \Pterodactyl\Models\Allocation + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException + * @throws \Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException + * @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException + * @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException + */ + public function handle(Server $server) + { + if (!config('pterodactyl.client_features.allocations.enabled')) { + throw new AutoAllocationNotEnabledException; + } + + if ($server->allocations()->count() >= $server->allocation_limit) { + throw new DisplayException( + 'Cannot assign additional allocations to this server: limit has been reached.' + ); + } + + // Attempt to find a given available allocation for a server. If one cannot be found + // we will fall back to attempting to create a new allocation that can be used for the + // server. + /** @var \Pterodactyl\Models\Allocation|null $allocation */ + $allocation = $server->node->allocations() + ->where('ip', $server->allocation->ip) + ->whereNull('server_id') + ->inRandomOrder() + ->first(); + + $allocation = $allocation ?? $this->createNewAllocation($server); + + $allocation->update(['server_id' => $server->id]); + + return $allocation->refresh(); + } + + /** + * @param \Pterodactyl\Models\Server $server + * @return \Pterodactyl\Models\Allocation + * + * @throws \Pterodactyl\Exceptions\DisplayException + * @throws \Pterodactyl\Exceptions\Service\Allocation\CidrOutOfRangeException + * @throws \Pterodactyl\Exceptions\Service\Allocation\InvalidPortMappingException + * @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException + * @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException + */ + protected function createNewAllocation(Server $server): Allocation + { + $start = config('pterodactyl.client_features.allocations.range_start', null); + $end = config('pterodactyl.client_features.allocations.range_end', null); + + if (!$start || !$end) { + throw new NoAutoAllocationSpaceAvailableException; + } + + Assert::integerish($start); + Assert::integerish($end); + + // Get all of the currently allocated ports for the node so that we can figure out + // which port might be available. + $ports = $server->node->allocations() + ->where('ip', $server->allocation->ip) + ->whereBetween('port', [$start, $end]) + ->pluck('port'); + + // Compute the difference of the range and the currently created ports, finding + // any port that does not already exist in the database. We will then use this + // array of ports to create a new allocation to assign to the server. + $available = array_diff(range($start, $end), $ports->toArray()); + + // If we've already allocated all of the ports, just abort. + if (empty($available)) { + throw new NoAutoAllocationSpaceAvailableException; + } + + // dd($available, array_rand($available)); + // Pick a random port out of the remaining available ports. + /** @var int $port */ + $port = $available[array_rand($available)]; + + $this->service->handle($server->node, [ + 'server_id' => $server->id, + 'allocation_ip' => $server->allocation->ip, + 'allocation_ports' => [$port], + ]); + + /** @var \Pterodactyl\Models\Allocation $allocation */ + $allocation = $server->node->allocations() + ->where('ip', $server->allocation->ip) + ->where('port', $port) + ->firstOrFail(); + + return $allocation; + } +} diff --git a/config/pterodactyl.php b/config/pterodactyl.php index aba0d729a..404f7aa34 100644 --- a/config/pterodactyl.php +++ b/config/pterodactyl.php @@ -143,6 +143,12 @@ return [ // The total number of tasks that can exist for any given schedule at once. 'per_schedule_task_limit' => 10, ], + + 'allocations' => [ + 'enabled' => env('PTERODACTYL_CLIENT_ALLOCATIONS_ENABLED', false), + 'range_start' => env('PTERODACTYL_CLIENT_ALLOCATIONS_RANGE_START'), + 'range_end' => env('PTERODACTYL_CLIENT_ALLOCATIONS_RANGE_END'), + ], ], /* diff --git a/resources/scripts/api/server/network/createServerAllocation.ts b/resources/scripts/api/server/network/createServerAllocation.ts index 39fc56afe..3e94cf7f6 100644 --- a/resources/scripts/api/server/network/createServerAllocation.ts +++ b/resources/scripts/api/server/network/createServerAllocation.ts @@ -2,10 +2,8 @@ import { Allocation } from '@/api/server/getServer'; import http from '@/api/http'; import { rawDataToServerAllocation } from '@/api/transformers'; -export default (uuid: string): Promise => { - return new Promise((resolve, reject) => { - http.get(`/api/client/servers/${uuid}/network/allocations/new`) - .then(({ data }) => resolve(rawDataToServerAllocation(data))) - .catch(reject); - }); +export default async (uuid: string): Promise => { + const { data } = await http.post(`/api/client/servers/${uuid}/network/allocations`); + + return rawDataToServerAllocation(data); }; diff --git a/resources/views/admin/settings/advanced.blade.php b/resources/views/admin/settings/advanced.blade.php index f749511f3..b903b2435 100644 --- a/resources/views/admin/settings/advanced.blade.php +++ b/resources/views/admin/settings/advanced.blade.php @@ -114,24 +114,24 @@
- - + -

If enabled, the panel will attempt to auto create a new allocation in the range specified if there are no more allocations already created on the node.

+

If enabled users will have the option to automatically create new allocations for their server via the frontend.

- +

The starting port in the range that can be automatically allocated.

- +

The ending port in the range that can be automatically allocated.

diff --git a/routes/api-client.php b/routes/api-client.php index 9f6a3f8d4..170828be1 100644 --- a/routes/api-client.php +++ b/routes/api-client.php @@ -82,9 +82,9 @@ Route::group(['prefix' => '/servers/{server}', 'middleware' => [AuthenticateServ Route::group(['prefix' => '/network', 'middleware' => [AllocationBelongsToServer::class]], function () { Route::get('/allocations', 'Servers\NetworkAllocationController@index'); + Route::post('/allocations', 'Servers\NetworkAllocationController@store'); Route::post('/allocations/{allocation}', 'Servers\NetworkAllocationController@update'); Route::post('/allocations/{allocation}/primary', 'Servers\NetworkAllocationController@setPrimary'); - Route::get('/allocations/new', 'Servers\NetworkAllocationController@addNew'); Route::delete('/allocations/{allocation}', 'Servers\NetworkAllocationController@delete'); }); From 365f5e0806e499f48c12c3077378475dd2d395a9 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 31 Oct 2020 14:59:06 -0700 Subject: [PATCH 11/15] server_id is irrelevant at this stage --- app/Services/Allocations/FindAssignableAllocationService.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/Services/Allocations/FindAssignableAllocationService.php b/app/Services/Allocations/FindAssignableAllocationService.php index 43b4c9d9d..c529547d6 100644 --- a/app/Services/Allocations/FindAssignableAllocationService.php +++ b/app/Services/Allocations/FindAssignableAllocationService.php @@ -114,7 +114,6 @@ class FindAssignableAllocationService $port = $available[array_rand($available)]; $this->service->handle($server->node, [ - 'server_id' => $server->id, 'allocation_ip' => $server->allocation->ip, 'allocation_ports' => [$port], ]); From b2be067f38efab987e1614599bff749dabcbbb66 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 31 Oct 2020 21:22:44 -0700 Subject: [PATCH 12/15] Support deleting an allocation for a server --- .../Servers/NetworkAllocationController.php | 9 ++-- .../FindAssignableAllocationService.php | 1 - .../server/network/getServerAllocations.ts | 9 ---- .../scripts/api/swr/getServerAllocations.ts | 15 ++++++ .../components/elements/ConfirmationModal.tsx | 2 +- .../server/network/AllocationRow.tsx | 33 +++++++----- .../server/network/DeleteAllocationButton.tsx | 51 +++++++++++++++++++ .../server/network/NetworkContainer.tsx | 9 +--- 8 files changed, 94 insertions(+), 35 deletions(-) delete mode 100644 resources/scripts/api/server/network/getServerAllocations.ts create mode 100644 resources/scripts/api/swr/getServerAllocations.ts create mode 100644 resources/scripts/components/server/network/DeleteAllocationButton.tsx diff --git a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php index 532054993..b26107649 100644 --- a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php +++ b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php @@ -138,18 +138,19 @@ class NetworkAllocationController extends ClientApiController * @return \Illuminate\Http\JsonResponse * * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException */ public function delete(DeleteAllocationRequest $request, Server $server, Allocation $allocation) { if ($allocation->id === $server->allocation_id) { throw new DisplayException( - 'Cannot delete the primary allocation for a server.' + 'You cannot delete the primary allocation for this server.' ); } - $this->repository->update($allocation->id, ['server_id' => null, 'notes' => null]); + Allocation::query()->where('id', $allocation->id)->update([ + 'notes' => null, + 'server_id' => null, + ]); return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } diff --git a/app/Services/Allocations/FindAssignableAllocationService.php b/app/Services/Allocations/FindAssignableAllocationService.php index c529547d6..ec816cb81 100644 --- a/app/Services/Allocations/FindAssignableAllocationService.php +++ b/app/Services/Allocations/FindAssignableAllocationService.php @@ -108,7 +108,6 @@ class FindAssignableAllocationService throw new NoAutoAllocationSpaceAvailableException; } - // dd($available, array_rand($available)); // Pick a random port out of the remaining available ports. /** @var int $port */ $port = $available[array_rand($available)]; diff --git a/resources/scripts/api/server/network/getServerAllocations.ts b/resources/scripts/api/server/network/getServerAllocations.ts deleted file mode 100644 index 7309bd266..000000000 --- a/resources/scripts/api/server/network/getServerAllocations.ts +++ /dev/null @@ -1,9 +0,0 @@ -import http from '@/api/http'; -import { rawDataToServerAllocation } from '@/api/transformers'; -import { Allocation } from '@/api/server/getServer'; - -export default async (uuid: string): Promise => { - const { data } = await http.get(`/api/client/servers/${uuid}/network/allocations`); - - return (data.data || []).map(rawDataToServerAllocation); -}; diff --git a/resources/scripts/api/swr/getServerAllocations.ts b/resources/scripts/api/swr/getServerAllocations.ts new file mode 100644 index 000000000..00af65c87 --- /dev/null +++ b/resources/scripts/api/swr/getServerAllocations.ts @@ -0,0 +1,15 @@ +import { ServerContext } from '@/state/server'; +import useSWR from 'swr'; +import http from '@/api/http'; +import { rawDataToServerAllocation } from '@/api/transformers'; +import { Allocation } from '@/api/server/getServer'; + +export default (initialData?: Allocation[]) => { + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + + return useSWR([ 'server:allocations', uuid ], async () => { + const { data } = await http.get(`/api/client/servers/${uuid}/network/allocations`); + + return (data.data || []).map(rawDataToServerAllocation); + }, { initialData, revalidateOnFocus: false }); +}; diff --git a/resources/scripts/components/elements/ConfirmationModal.tsx b/resources/scripts/components/elements/ConfirmationModal.tsx index 1b5b3ce1e..7233731f5 100644 --- a/resources/scripts/components/elements/ConfirmationModal.tsx +++ b/resources/scripts/components/elements/ConfirmationModal.tsx @@ -20,7 +20,7 @@ const ConfirmationModal = ({ title, children, buttonText, onConfirmed }: Props)

{title}

{children}

- - + <> + + + + + + + }
diff --git a/resources/scripts/components/server/network/DeleteAllocationButton.tsx b/resources/scripts/components/server/network/DeleteAllocationButton.tsx new file mode 100644 index 000000000..e52ba3916 --- /dev/null +++ b/resources/scripts/components/server/network/DeleteAllocationButton.tsx @@ -0,0 +1,51 @@ +import React, { useState } from 'react'; +import { faTrashAlt } from '@fortawesome/free-solid-svg-icons'; +import tw from 'twin.macro'; +import Icon from '@/components/elements/Icon'; +import ConfirmationModal from '@/components/elements/ConfirmationModal'; +import { ServerContext } from '@/state/server'; +import deleteServerAllocation from '@/api/server/network/deleteServerAllocation'; +import getServerAllocations from '@/api/swr/getServerAllocations'; +import useFlash from '@/plugins/useFlash'; + +interface Props { + allocation: number; +} + +const DeleteAllocationButton = ({ allocation }: Props) => { + const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const [ confirm, setConfirm ] = useState(false); + const { mutate } = getServerAllocations(); + const { clearFlashes, clearAndAddHttpError } = useFlash(); + + const deleteAllocation = () => { + clearFlashes('server:network'); + + mutate(data => data?.filter(a => a.id !== allocation), false); + deleteServerAllocation(uuid, allocation) + .catch(error => clearAndAddHttpError({ key: 'server:network', error })); + }; + + return ( + <> + setConfirm(false)} + > + This allocation will be immediately removed from your server. Are you sure you want to continue? + + + + ); +}; + +export default DeleteAllocationButton; diff --git a/resources/scripts/components/server/network/NetworkContainer.tsx b/resources/scripts/components/server/network/NetworkContainer.tsx index cd789657d..39855fcf3 100644 --- a/resources/scripts/components/server/network/NetworkContainer.tsx +++ b/resources/scripts/components/server/network/NetworkContainer.tsx @@ -1,7 +1,4 @@ import React, { useCallback, useEffect, useState } from 'react'; -import useSWR from 'swr'; -import getServerAllocations from '@/api/server/network/getServerAllocations'; -import { Allocation } from '@/api/server/getServer'; import Spinner from '@/components/elements/Spinner'; import useFlash from '@/plugins/useFlash'; import ServerContentBlock from '@/components/elements/ServerContentBlock'; @@ -14,6 +11,7 @@ import createServerAllocation from '@/api/server/network/createServerAllocation' import tw from 'twin.macro'; import Can from '@/components/elements/Can'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; +import getServerAllocations from '@/api/swr/getServerAllocations'; const NetworkContainer = () => { const [ loading, setLoading ] = useState(false); @@ -22,10 +20,7 @@ const NetworkContainer = () => { const allocations = useDeepMemoize(ServerContext.useStoreState(state => state.server.data!.allocations)); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const { data, error, mutate } = useSWR(uuid, key => getServerAllocations(key), { - initialData: allocations, - revalidateOnFocus: false, - }); + const { data, error, mutate } = getServerAllocations(allocations); useEffect(() => { if (error) { From d49368551833418c27f94721021e13fb9da7218c Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 31 Oct 2020 21:57:27 -0700 Subject: [PATCH 13/15] Add test coverage for allocation assignment endpoint --- .../Servers/NetworkAllocationController.php | 6 ++ .../FindAssignableAllocationService.php | 6 -- .../Allocation/CreateNewAllocationTest.php | 97 +++++++++++++++++++ .../Allocation/DeleteAllocationTest.php | 91 +++++++++++++++++ .../NetworkAllocationControllerTest.php | 81 ++-------------- .../Traits/Integration/CreatesTestModels.php | 2 + 6 files changed, 204 insertions(+), 79 deletions(-) create mode 100644 tests/Integration/Api/Client/Server/Allocation/CreateNewAllocationTest.php create mode 100644 tests/Integration/Api/Client/Server/Allocation/DeleteAllocationTest.php diff --git a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php index b26107649..43140f622 100644 --- a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php +++ b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php @@ -122,6 +122,12 @@ class NetworkAllocationController extends ClientApiController */ public function store(NewAllocationRequest $request, Server $server): array { + if ($server->allocations()->count() >= $server->allocation_limit) { + throw new DisplayException( + 'Cannot assign additional allocations to this server: limit has been reached.' + ); + } + $allocation = $this->assignableAllocationService->handle($server); return $this->fractal->item($allocation) diff --git a/app/Services/Allocations/FindAssignableAllocationService.php b/app/Services/Allocations/FindAssignableAllocationService.php index ec816cb81..ed3638265 100644 --- a/app/Services/Allocations/FindAssignableAllocationService.php +++ b/app/Services/Allocations/FindAssignableAllocationService.php @@ -46,12 +46,6 @@ class FindAssignableAllocationService throw new AutoAllocationNotEnabledException; } - if ($server->allocations()->count() >= $server->allocation_limit) { - throw new DisplayException( - 'Cannot assign additional allocations to this server: limit has been reached.' - ); - } - // Attempt to find a given available allocation for a server. If one cannot be found // we will fall back to attempting to create a new allocation that can be used for the // server. diff --git a/tests/Integration/Api/Client/Server/Allocation/CreateNewAllocationTest.php b/tests/Integration/Api/Client/Server/Allocation/CreateNewAllocationTest.php new file mode 100644 index 000000000..ab33ba81b --- /dev/null +++ b/tests/Integration/Api/Client/Server/Allocation/CreateNewAllocationTest.php @@ -0,0 +1,97 @@ +set('pterodactyl.client_features.allocations.enabled', true); + config()->set('pterodactyl.client_features.allocations.range_start', 5000); + config()->set('pterodactyl.client_features.allocations.range_end', 5050); + } + + /** + * Tests that a new allocation can be properly assigned to a server. + * + * @param array $permission + * @dataProvider permissionDataProvider + */ + public function testNewAllocationCanBeAssignedToServer(array $permission) + { + /** @var \Pterodactyl\Models\Server $server */ + [$user, $server] = $this->generateTestAccount($permission); + $server->update(['allocation_limit' => 2]); + + $response = $this->actingAs($user)->postJson($this->link($server, "/network/allocations")); + $response->assertJsonPath('object', Allocation::RESOURCE_NAME); + + $matched = Allocation::query()->findOrFail($response->json('attributes.id')); + + $this->assertSame($server->id, $matched->server_id); + $this->assertJsonTransformedWith($response->json('attributes'), $matched); + } + + /** + * Test that a user without the required permissions cannot create an allocation for + * the server instance. + */ + public function testAllocationCannotBeCreatedIfUserDoesNotHavePermission() + { + /** @var \Pterodactyl\Models\Server $server */ + [$user, $server] = $this->generateTestAccount([Permission::ACTION_ALLOCATION_UPDATE]); + $server->update(['allocation_limit' => 2]); + + $this->actingAs($user)->postJson($this->link($server, "/network/allocations"))->assertForbidden(); + } + + /** + * Test that an error is returned to the user if this feature is not enabled on the system. + */ + public function testAllocationCannotBeCreatedIfNotEnabled() + { + config()->set('pterodactyl.client_features.allocations.enabled', false); + + /** @var \Pterodactyl\Models\Server $server */ + [$user, $server] = $this->generateTestAccount(); + $server->update(['allocation_limit' => 2]); + + $this->actingAs($user)->postJson($this->link($server, "/network/allocations")) + ->assertStatus(Response::HTTP_BAD_REQUEST) + ->assertJsonPath('errors.0.code', 'AutoAllocationNotEnabledException') + ->assertJsonPath('errors.0.detail', 'Server auto-allocation is not enabled for this instance.'); + } + + /** + * Test that an allocation cannot be created if the server has reached it's allocation limit. + */ + public function testAllocationCannotBeCreatedIfServerIsAtLimit() + { + /** @var \Pterodactyl\Models\Server $server */ + [$user, $server] = $this->generateTestAccount(); + $server->update(['allocation_limit' => 1]); + + $this->actingAs($user)->postJson($this->link($server, "/network/allocations")) + ->assertStatus(Response::HTTP_BAD_REQUEST) + ->assertJsonPath('errors.0.code', 'DisplayException') + ->assertJsonPath('errors.0.detail', 'Cannot assign additional allocations to this server: limit has been reached.'); + } + + /** + * @return array + */ + public function permissionDataProvider() + { + return [[[Permission::ACTION_ALLOCATION_CREATE]], [[]]]; + } +} diff --git a/tests/Integration/Api/Client/Server/Allocation/DeleteAllocationTest.php b/tests/Integration/Api/Client/Server/Allocation/DeleteAllocationTest.php new file mode 100644 index 000000000..5b14c1a7e --- /dev/null +++ b/tests/Integration/Api/Client/Server/Allocation/DeleteAllocationTest.php @@ -0,0 +1,91 @@ +generateTestAccount($permission); + + /** @var \Pterodactyl\Models\Allocation $allocation */ + $allocation = factory(Allocation::class)->create([ + 'server_id' => $server->id, + 'node_id' => $server->node_id, + 'notes' => 'hodor', + ]); + + $this->actingAs($user)->deleteJson($this->link($allocation))->assertStatus(Response::HTTP_NO_CONTENT); + + $this->assertDatabaseHas('allocations', ['id' => $allocation->id, 'server_id' => null, 'notes' => null]); + } + + /** + * Test that an error is returned if the user does not have permissiont to delete an allocation. + */ + public function testErrorIsReturnedIfUserDoesNotHavePermission() + { + /** @var \Pterodactyl\Models\Server $server */ + [$user, $server] = $this->generateTestAccount([Permission::ACTION_ALLOCATION_CREATE]); + + /** @var \Pterodactyl\Models\Allocation $allocation */ + $allocation = factory(Allocation::class)->create([ + 'server_id' => $server->id, + 'node_id' => $server->node_id, + 'notes' => 'hodor', + ]); + + $this->actingAs($user)->deleteJson($this->link($allocation))->assertForbidden(); + + $this->assertDatabaseHas('allocations', ['id' => $allocation->id, 'server_id' => $server->id]); + } + + /** + * Test that an allocation is not deleted if it is currently marked as the primary allocation + * for the server. + */ + public function testErrorIsReturnedIfAllocationIsPrimary() + { + /** @var \Pterodactyl\Models\Server $server */ + [$user, $server] = $this->generateTestAccount(); + + $this->actingAs($user)->deleteJson($this->link($server->allocation)) + ->assertStatus(Response::HTTP_BAD_REQUEST) + ->assertJsonPath('errors.0.code', 'DisplayException') + ->assertJsonPath('errors.0.detail', 'You cannot delete the primary allocation for this server.'); + } + + /** + * Test that an allocation cannot be deleted if it does not belong to the server instance. + */ + public function testErrorIsReturnedIfAllocationDoesNotBelongToServer() + { + /** @var \Pterodactyl\Models\Server $server */ + [$user, $server] = $this->generateTestAccount(); + [, $server2] = $this->generateTestAccount(); + + $this->actingAs($user)->deleteJson($this->link($server2->allocation))->assertNotFound(); + $this->actingAs($user)->deleteJson($this->link($server, "/network/allocations/{$server2->allocation_id}"))->assertNotFound(); + } + + /** + * @return array + */ + public function permissionDataProvider() + { + return [[[Permission::ACTION_ALLOCATION_DELETE]], [[]]]; + } +} diff --git a/tests/Integration/Api/Client/Server/NetworkAllocationControllerTest.php b/tests/Integration/Api/Client/Server/NetworkAllocationControllerTest.php index ffebc83b8..185375e86 100644 --- a/tests/Integration/Api/Client/Server/NetworkAllocationControllerTest.php +++ b/tests/Integration/Api/Client/Server/NetworkAllocationControllerTest.php @@ -4,7 +4,6 @@ namespace Pterodactyl\Tests\Integration\Api\Client\Server; use Pterodactyl\Models\User; use Illuminate\Http\Response; -use Pterodactyl\Models\Server; use Pterodactyl\Models\Allocation; use Pterodactyl\Models\Permission; use Pterodactyl\Tests\Integration\Api\Client\ClientApiIntegrationTestCase; @@ -17,7 +16,6 @@ class NetworkAllocationControllerTest extends ClientApiIntegrationTestCase public function testServerAllocationsAreReturned() { [$user, $server] = $this->generateTestAccount(); - $allocation = $this->getAllocation($server); $response = $this->actingAs($user)->getJson($this->link($server, '/network/allocations')); @@ -25,7 +23,7 @@ class NetworkAllocationControllerTest extends ClientApiIntegrationTestCase $response->assertJsonPath('object', 'list'); $response->assertJsonCount(1, 'data'); - $this->assertJsonTransformedWith($response->json('data.0.attributes'), $allocation); + $this->assertJsonTransformedWith($response->json('data.0.attributes'), $server->allocation); } /** @@ -57,7 +55,7 @@ class NetworkAllocationControllerTest extends ClientApiIntegrationTestCase public function testAllocationNotesCanBeUpdated(array $permissions) { [$user, $server] = $this->generateTestAccount($permissions); - $allocation = $this->getAllocation($server); + $allocation = $server->allocation; $this->assertNull($allocation->notes); @@ -92,13 +90,11 @@ class NetworkAllocationControllerTest extends ClientApiIntegrationTestCase $server->owner_id = $user2->id; $server->save(); - $this->actingAs($user)->postJson($this->link($this->getAllocation($server))) - ->assertNotFound(); + $this->actingAs($user)->postJson($this->link($server->allocation))->assertNotFound(); [$user, $server] = $this->generateTestAccount([Permission::ACTION_ALLOCATION_CREATE]); - $this->actingAs($user)->postJson($this->link($this->getAllocation($server))) - ->assertForbidden(); + $this->actingAs($user)->postJson($this->link($server->allocation))->assertForbidden(); } /** @@ -108,8 +104,8 @@ class NetworkAllocationControllerTest extends ClientApiIntegrationTestCase public function testPrimaryAllocationCanBeModified(array $permissions) { [$user, $server] = $this->generateTestAccount($permissions); - $allocation = $this->getAllocation($server); - $allocation2 = $this->getAllocation($server); + $allocation = $server->allocation; + $allocation2 = factory(Allocation::class)->create(['node_id' => $server->node_id, 'server_id' => $server->id]); $server->allocation_id = $allocation->id; $server->save(); @@ -130,61 +126,12 @@ class NetworkAllocationControllerTest extends ClientApiIntegrationTestCase $server->owner_id = $user2->id; $server->save(); - $this->actingAs($user)->postJson($this->link($this->getAllocation($server), '/primary')) + $this->actingAs($user)->postJson($this->link($server->allocation, '/primary')) ->assertNotFound(); [$user, $server] = $this->generateTestAccount([Permission::ACTION_ALLOCATION_CREATE]); - $this->actingAs($user)->postJson($this->link($this->getAllocation($server), '/primary')) - ->assertForbidden(); - } - - /** - * @param array $permissions - * @dataProvider deletePermissionsDataProvider - */ - public function testAllocationCanBeDeleted(array $permissions) - { - [$user, $server] = $this->generateTestAccount($permissions); - $allocation = $this->getAllocation($server); - $allocation2 = $this->getAllocation($server); - - $allocation2->notes = 'Filled notes'; - $allocation2->save(); - - $server->allocation_id = $allocation->id; - $server->save(); - - $this->actingAs($user)->deleteJson($this->link($allocation)) - ->assertStatus(Response::HTTP_BAD_REQUEST) - ->assertJsonPath('errors.0.code', 'DisplayException') - ->assertJsonPath('errors.0.detail', 'Cannot delete the primary allocation for a server.'); - - $this->actingAs($user)->deleteJson($this->link($allocation2)) - ->assertStatus(Response::HTTP_NO_CONTENT); - - $server = $server->refresh(); - $allocation2 = $allocation2->refresh(); - - $this->assertSame($allocation->id, $server->allocation_id); - $this->assertNull($allocation2->server_id); - $this->assertNull($allocation2->notes); - } - - public function testAllocationCannotBeDeletedByInvalidUser() - { - [$user, $server] = $this->generateTestAccount(); - $user2 = factory(User::class)->create(); - - $server->owner_id = $user2->id; - $server->save(); - - $this->actingAs($user)->deleteJson($this->link($this->getAllocation($server))) - ->assertNotFound(); - - [$user, $server] = $this->generateTestAccount([Permission::ACTION_ALLOCATION_CREATE]); - - $this->actingAs($user)->deleteJson($this->link($this->getAllocation($server))) + $this->actingAs($user)->postJson($this->link($server->allocation, '/primary')) ->assertForbidden(); } @@ -197,16 +144,4 @@ class NetworkAllocationControllerTest extends ClientApiIntegrationTestCase { return [[[]], [[Permission::ACTION_ALLOCATION_DELETE]]]; } - - /** - * @param \Pterodactyl\Models\Server $server - * @return \Pterodactyl\Models\Allocation - */ - protected function getAllocation(Server $server): Allocation - { - return factory(Allocation::class)->create([ - 'server_id' => $server->id, - 'node_id' => $server->node_id, - ]); - } } diff --git a/tests/Traits/Integration/CreatesTestModels.php b/tests/Traits/Integration/CreatesTestModels.php index ecd4e0d53..0d3390f63 100644 --- a/tests/Traits/Integration/CreatesTestModels.php +++ b/tests/Traits/Integration/CreatesTestModels.php @@ -71,6 +71,8 @@ trait CreatesTestModels $server = $factory->of(Server::class)->create($attributes); + Allocation::query()->where('id', $server->allocation_id)->update(['server_id' => $server->id]); + return Server::with([ 'location', 'user', 'node', 'allocation', 'nest', 'egg', ])->findOrFail($server->id); From 6cb21fb920f17e2cd9e771a07486a3ba49c2168e Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 31 Oct 2020 22:17:32 -0700 Subject: [PATCH 14/15] Add test coverage for allocation auto-assignment service --- .../FindAssignableAllocationService.php | 9 +- .../FindAssignableAllocationServiceTest.php | 179 ++++++++++++++++++ 2 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 tests/Integration/Services/Allocations/FindAssignableAllocationServiceTest.php diff --git a/app/Services/Allocations/FindAssignableAllocationService.php b/app/Services/Allocations/FindAssignableAllocationService.php index ed3638265..6f523663a 100644 --- a/app/Services/Allocations/FindAssignableAllocationService.php +++ b/app/Services/Allocations/FindAssignableAllocationService.php @@ -5,7 +5,6 @@ namespace Pterodactyl\Services\Allocations; use Webmozart\Assert\Assert; use Pterodactyl\Models\Server; use Pterodactyl\Models\Allocation; -use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\Service\Allocation\AutoAllocationNotEnabledException; use Pterodactyl\Exceptions\Service\Allocation\NoAutoAllocationSpaceAvailableException; @@ -42,7 +41,7 @@ class FindAssignableAllocationService */ public function handle(Server $server) { - if (!config('pterodactyl.client_features.allocations.enabled')) { + if (! config('pterodactyl.client_features.allocations.enabled')) { throw new AutoAllocationNotEnabledException; } @@ -64,6 +63,10 @@ class FindAssignableAllocationService } /** + * Create a new allocation on the server's node with a random port from the defined range + * in the settings. If there are no matches in that range, or something is wrong with the + * range information provided an exception will be raised. + * * @param \Pterodactyl\Models\Server $server * @return \Pterodactyl\Models\Allocation * @@ -78,7 +81,7 @@ class FindAssignableAllocationService $start = config('pterodactyl.client_features.allocations.range_start', null); $end = config('pterodactyl.client_features.allocations.range_end', null); - if (!$start || !$end) { + if (! $start || ! $end) { throw new NoAutoAllocationSpaceAvailableException; } diff --git a/tests/Integration/Services/Allocations/FindAssignableAllocationServiceTest.php b/tests/Integration/Services/Allocations/FindAssignableAllocationServiceTest.php new file mode 100644 index 000000000..ce49a1873 --- /dev/null +++ b/tests/Integration/Services/Allocations/FindAssignableAllocationServiceTest.php @@ -0,0 +1,179 @@ +set('pterodactyl.client_features.allocations.enabled', true); + config()->set('pterodactyl.client_features.allocations.range_start', 0); + config()->set('pterodactyl.client_features.allocations.range_end', 0); + } + + /** + * Test that an unassigned allocation is prefered rather than creating an entirely new + * allocation for the server. + */ + public function testExistingAllocationIsPreferred() + { + $server = $this->createServerModel(); + + $created = factory(Allocation::class)->create([ + 'node_id' => $server->node_id, + 'ip' => $server->allocation->ip, + ]); + + $response = $this->getService()->handle($server); + + $this->assertSame($created->id, $response->id); + $this->assertSame($server->allocation->ip, $response->ip); + $this->assertSame($server->node_id, $response->node_id); + $this->assertSame($server->id, $response->server_id); + $this->assertNotSame($server->allocation_id, $response->id); + } + + /** + * Test that a new allocation is created if there is not a free one available. + */ + public function testNewAllocationIsCreatedIfOneIsNotFound() + { + $server = $this->createServerModel(); + config()->set('pterodactyl.client_features.allocations.range_start', 5000); + config()->set('pterodactyl.client_features.allocations.range_end', 5005); + + $response = $this->getService()->handle($server); + $this->assertSame($server->id, $response->server_id); + $this->assertSame($server->allocation->ip, $response->ip); + $this->assertSame($server->node_id, $response->node_id); + $this->assertNotSame($server->allocation_id, $response->id); + $this->assertTrue($response->port >= 5000 && $response->port <= 5005); + } + + /** + * Test that a currently assigned port is never assigned to a server. + */ + public function testOnlyPortNotInUseIsCreated() + { + $server = $this->createServerModel(); + $server2 = $this->createServerModel(['node_id' => $server->node_id]); + + config()->set('pterodactyl.client_features.allocations.range_start', 5000); + config()->set('pterodactyl.client_features.allocations.range_end', 5001); + + factory(Allocation::class)->create([ + 'server_id' => $server2->id, + 'node_id' => $server->node_id, + 'ip' => $server->allocation->ip, + 'port' => 5000, + ]); + + $response = $this->getService()->handle($server); + $this->assertSame(5001, $response->port); + } + + public function testExceptionIsThrownIfNoMoreAllocationsCanBeCreatedInRange() + { + $server = $this->createServerModel(); + $server2 = $this->createServerModel(['node_id' => $server->node_id]); + config()->set('pterodactyl.client_features.allocations.range_start', 5000); + config()->set('pterodactyl.client_features.allocations.range_end', 5005); + + for ($i = 5000; $i <= 5005; $i++) { + factory(Allocation::class)->create([ + 'ip' => $server->allocation->ip, + 'port' => $i, + 'node_id' => $server->node_id, + 'server_id' => $server2->id, + ]); + } + + $this->expectException(NoAutoAllocationSpaceAvailableException::class); + $this->expectExceptionMessage('Cannot assign additional allocation: no more space available on node.'); + + $this->getService()->handle($server); + } + + /** + * Test that we only auto-allocate from the current server's IP address space, and not a random + * IP address available on that node. + */ + public function testExceptionIsThrownIfOnlyFreePortIsOnADifferentIp() + { + $server = $this->createServerModel(); + + factory(Allocation::class)->times(5)->create(['node_id' => $server->node_id]); + + $this->expectException(NoAutoAllocationSpaceAvailableException::class); + $this->expectExceptionMessage('Cannot assign additional allocation: no more space available on node.'); + + $this->getService()->handle($server); + } + + public function testExceptionIsThrownIfStartOrEndRangeIsNotDefined() + { + $server = $this->createServerModel(); + + $this->expectException(NoAutoAllocationSpaceAvailableException::class); + $this->expectExceptionMessage('Cannot assign additional allocation: no more space available on node.'); + + $this->getService()->handle($server); + } + + public function testExceptionIsThrownIfStartOrEndRangeIsNotNumeric() + { + $server = $this->createServerModel(); + config()->set('pterodactyl.client_features.allocations.range_start', 'hodor'); + config()->set('pterodactyl.client_features.allocations.range_end', 10); + + try { + $this->getService()->handle($server); + $this->assertTrue(false, 'This assertion should not be reached.'); + } catch (Exception $exception) { + $this->assertInstanceOf(InvalidArgumentException::class, $exception); + $this->assertSame('Expected an integerish value. Got: string', $exception->getMessage()); + } + + config()->set('pterodactyl.client_features.allocations.range_start', 10); + config()->set('pterodactyl.client_features.allocations.range_end', 'hodor'); + + try { + $this->getService()->handle($server); + $this->assertTrue(false, 'This assertion should not be reached.'); + } catch (Exception $exception) { + $this->assertInstanceOf(InvalidArgumentException::class, $exception); + $this->assertSame('Expected an integerish value. Got: string', $exception->getMessage()); + } + } + + public function testExceptionIsThrownIfFeatureIsNotEnabled() + { + config()->set('pterodactyl.client_features.allocations.enabled', false); + $server = $this->createServerModel(); + + $this->expectException(AutoAllocationNotEnabledException::class); + + $this->getService()->handle($server); + } + + /** + * @return \Pterodactyl\Services\Allocations\FindAssignableAllocationService + */ + private function getService() + { + return $this->app->make(FindAssignableAllocationService::class); + } +} From 49c29aae47650074b70b9f6e2b9bec7098022e2d Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 31 Oct 2020 22:30:03 -0700 Subject: [PATCH 15/15] Logic fixes --- .../scripts/api/swr/getServerAllocations.ts | 4 +-- .../server/network/AllocationRow.tsx | 26 +++++++++++--- .../server/network/NetworkContainer.tsx | 34 ++++++------------- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/resources/scripts/api/swr/getServerAllocations.ts b/resources/scripts/api/swr/getServerAllocations.ts index 00af65c87..a5591a396 100644 --- a/resources/scripts/api/swr/getServerAllocations.ts +++ b/resources/scripts/api/swr/getServerAllocations.ts @@ -4,12 +4,12 @@ import http from '@/api/http'; import { rawDataToServerAllocation } from '@/api/transformers'; import { Allocation } from '@/api/server/getServer'; -export default (initialData?: Allocation[]) => { +export default () => { const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); return useSWR([ 'server:allocations', uuid ], async () => { const { data } = await http.get(`/api/client/servers/${uuid}/network/allocations`); return (data.data || []).map(rawDataToServerAllocation); - }, { initialData, revalidateOnFocus: false }); + }, { revalidateOnFocus: false, revalidateOnMount: false }); }; diff --git a/resources/scripts/components/server/network/AllocationRow.tsx b/resources/scripts/components/server/network/AllocationRow.tsx index 99bb121a9..5eec25277 100644 --- a/resources/scripts/components/server/network/AllocationRow.tsx +++ b/resources/scripts/components/server/network/AllocationRow.tsx @@ -1,4 +1,4 @@ -import React, { memo, useState } from 'react'; +import React, { memo, useCallback, useState } from 'react'; import isEqual from 'react-fast-compare'; import tw from 'twin.macro'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -16,20 +16,25 @@ import useFlash from '@/plugins/useFlash'; import { ServerContext } from '@/state/server'; import CopyOnClick from '@/components/elements/CopyOnClick'; import DeleteAllocationButton from '@/components/server/network/DeleteAllocationButton'; +import setPrimaryServerAllocation from '@/api/server/network/setPrimaryServerAllocation'; +import getServerAllocations from '@/api/swr/getServerAllocations'; const Code = styled.code`${tw`font-mono py-1 px-2 bg-neutral-900 rounded text-sm inline-block`}`; const Label = styled.label`${tw`uppercase text-xs mt-1 text-neutral-400 block px-1 select-none transition-colors duration-150`}`; interface Props { allocation: Allocation; - onSetPrimary: (id: number) => void; - onNotesChanged: (id: number, notes: string) => void; } -const AllocationRow = ({ allocation, onSetPrimary, onNotesChanged }: Props) => { +const AllocationRow = ({ allocation }: Props) => { const [ loading, setLoading ] = useState(false); const { clearFlashes, clearAndAddHttpError } = useFlash(); const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); + const { mutate } = getServerAllocations(); + + const onNotesChanged = useCallback((id: number, notes: string) => { + mutate(data => data?.map(a => a.id === id ? { ...a, notes } : a), false); + }, []); const setAllocationNotes = debounce((notes: string) => { setLoading(true); @@ -41,6 +46,17 @@ const AllocationRow = ({ allocation, onSetPrimary, onNotesChanged }: Props) => { .then(() => setLoading(false)); }, 750); + const setPrimaryAllocation = () => { + clearFlashes('server:network'); + mutate(data => data?.map(a => ({ ...a, isDefault: a.id === allocation.id })), false); + + setPrimaryServerAllocation(uuid, allocation.id) + .catch(error => { + clearAndAddHttpError({ key: 'server:network', error }); + mutate(); + }); + }; + return (
@@ -81,7 +97,7 @@ const AllocationRow = ({ allocation, onSetPrimary, onNotesChanged }: Props) => { isSecondary size={'xsmall'} color={'primary'} - onClick={() => onSetPrimary(allocation.id)} + onClick={setPrimaryAllocation} > Make Primary diff --git a/resources/scripts/components/server/network/NetworkContainer.tsx b/resources/scripts/components/server/network/NetworkContainer.tsx index 39855fcf3..8583b137a 100644 --- a/resources/scripts/components/server/network/NetworkContainer.tsx +++ b/resources/scripts/components/server/network/NetworkContainer.tsx @@ -1,26 +1,31 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import Spinner from '@/components/elements/Spinner'; import useFlash from '@/plugins/useFlash'; import ServerContentBlock from '@/components/elements/ServerContentBlock'; import { ServerContext } from '@/state/server'; -import { useDeepMemoize } from '@/plugins/useDeepMemoize'; import AllocationRow from '@/components/server/network/AllocationRow'; -import setPrimaryServerAllocation from '@/api/server/network/setPrimaryServerAllocation'; import Button from '@/components/elements/Button'; import createServerAllocation from '@/api/server/network/createServerAllocation'; import tw from 'twin.macro'; import Can from '@/components/elements/Can'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import getServerAllocations from '@/api/swr/getServerAllocations'; +import isEqual from 'react-fast-compare'; +import { Allocation } from '@/api/server/getServer'; const NetworkContainer = () => { const [ loading, setLoading ] = useState(false); const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const allocationLimit = ServerContext.useStoreState(state => state.server.data!.featureLimits.allocations); - const allocations = useDeepMemoize(ServerContext.useStoreState(state => state.server.data!.allocations)); + // @ts-ignore + const allocations: Allocation[] = ServerContext.useStoreState(state => state.server.data!.allocations, isEqual); const { clearFlashes, clearAndAddHttpError } = useFlash(); - const { data, error, mutate } = getServerAllocations(allocations); + const { data, error, mutate } = getServerAllocations(); + + useEffect(() => { + mutate(allocations, false); + }, []); useEffect(() => { if (error) { @@ -28,19 +33,6 @@ const NetworkContainer = () => { } }, [ error ]); - const setPrimaryAllocation = (id: number) => { - clearFlashes('server:network'); - - const initial = data; - mutate(data?.map(a => a.id === id ? { ...a, isDefault: true } : { ...a, isDefault: false }), false); - - setPrimaryServerAllocation(uuid, id) - .catch(error => { - clearAndAddHttpError({ key: 'server:network', error }); - mutate(initial, false); - }); - }; - const onCreateAllocation = () => { clearFlashes('server:network'); @@ -51,10 +43,6 @@ const NetworkContainer = () => { .then(() => setLoading(false)); }; - const onNotesAdded = useCallback((id: number, notes: string) => { - mutate(data?.map(a => a.id === id ? { ...a, notes } : a), false); - }, []); - return ( {!data ? @@ -66,8 +54,6 @@ const NetworkContainer = () => { )) }