diff --git a/app/Exceptions/Service/Database/DatabaseClientFeatureNotEnabledException.php b/app/Exceptions/Service/Database/DatabaseClientFeatureNotEnabledException.php new file mode 100644 index 000000000..809cb4fbf --- /dev/null +++ b/app/Exceptions/Service/Database/DatabaseClientFeatureNotEnabledException.php @@ -0,0 +1,13 @@ +alert = $alert; + $this->databaseHostRepository = $databaseHostRepository; + $this->deployServerDatabaseService = $deployServerDatabaseService; $this->passwordService = $passwordService; $this->repository = $repository; } @@ -50,11 +81,42 @@ class DatabaseController extends Controller $this->authorize('view-databases', $server); $this->setRequest($request)->injectJavascript(); + $canCreateDatabase = config('pterodactyl.client_features.databases.enabled'); + $allowRandom = config('pterodactyl.client_features.databases.allow_random'); + + if ($this->databaseHostRepository->findCountWhere([['node_id', '=', $server->node_id]]) === 0) { + if ($canCreateDatabase && ! $allowRandom) { + $canCreateDatabase = false; + } + } + + $databases = $this->repository->getDatabasesForServer($server->id); + return view('server.databases.index', [ - 'databases' => $this->repository->getDatabasesForServer($server->id), + 'allowCreation' => $canCreateDatabase, + 'overLimit' => ! is_null($server->database_limit) && count($databases) >= $server->database_limit, + 'databases' => $databases, ]); } + /** + * Handle a request from a user to create a new database for the server. + * + * @param \Pterodactyl\Http\Requests\Server\Database\StoreServerDatabaseRequest $request + * @return \Illuminate\Http\RedirectResponse + * + * @throws \Exception + * @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException + */ + public function store(StoreServerDatabaseRequest $request): RedirectResponse + { + $this->deployServerDatabaseService->handle($request->getServer(), $request->validated()); + + $this->alert->success('Successfully created a new database.')->flash(); + + return redirect()->route('server.databases.index', $request->getServer()->uuidShort); + } + /** * Handle a request to update the password for a specific database. * diff --git a/app/Http/Requests/Server/Database/StoreServerDatabaseRequest.php b/app/Http/Requests/Server/Database/StoreServerDatabaseRequest.php new file mode 100644 index 000000000..2af23a914 --- /dev/null +++ b/app/Http/Requests/Server/Database/StoreServerDatabaseRequest.php @@ -0,0 +1,43 @@ + 'required|string|min:1', + 'remote' => 'required|string|regex:/^[0-9%.]{1,15}$/', + ]; + } +} diff --git a/app/Http/Requests/Server/ServerFormRequest.php b/app/Http/Requests/Server/ServerFormRequest.php index b796a21e0..f59ea3ae3 100644 --- a/app/Http/Requests/Server/ServerFormRequest.php +++ b/app/Http/Requests/Server/ServerFormRequest.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Http\Requests\Server; +use Pterodactyl\Models\Server; use Pterodactyl\Http\Requests\FrontendUserFormRequest; abstract class ServerFormRequest extends FrontendUserFormRequest @@ -24,6 +25,11 @@ abstract class ServerFormRequest extends FrontendUserFormRequest return false; } - return $this->user()->can($this->permission(), $this->attributes->get('server')); + return $this->user()->can($this->permission(), $this->getServer()); + } + + public function getServer(): Server + { + return $this->attributes->get('server'); } } diff --git a/app/Services/Databases/DatabaseManagementService.php b/app/Services/Databases/DatabaseManagementService.php index b05ddc3ff..dc91e11f9 100644 --- a/app/Services/Databases/DatabaseManagementService.php +++ b/app/Services/Databases/DatabaseManagementService.php @@ -13,22 +13,27 @@ class DatabaseManagementService /** * @var \Illuminate\Database\DatabaseManager */ - protected $database; + private $database; /** * @var \Pterodactyl\Extensions\DynamicDatabaseConnection */ - protected $dynamic; + private $dynamic; /** * @var \Illuminate\Contracts\Encryption\Encrypter */ - protected $encrypter; + private $encrypter; /** * @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface */ - protected $repository; + private $repository; + + /** + * @var bool + */ + protected $useRandomHost = false; /** * CreationService constructor. @@ -55,7 +60,7 @@ class DatabaseManagementService * * @param int $server * @param array $data - * @return \Illuminate\Database\Eloquent\Model + * @return \Pterodactyl\Models\Database * * @throws \Exception */ diff --git a/app/Services/Databases/DeployServerDatabaseService.php b/app/Services/Databases/DeployServerDatabaseService.php new file mode 100644 index 000000000..2932b87f2 --- /dev/null +++ b/app/Services/Databases/DeployServerDatabaseService.php @@ -0,0 +1,87 @@ +databaseHostRepository = $databaseHostRepository; + $this->managementService = $managementService; + $this->repository = $repository; + } + + /** + * @param \Pterodactyl\Models\Server $server + * @param array $data + * @return \Pterodactyl\Models\Database + * + * @throws \Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException + * @throws \Exception + */ + public function handle(Server $server, array $data): Database + { + if (! config('pterodactyl.client_features.databases.enabled')) { + throw new DatabaseClientFeatureNotEnabledException; + } + + $databases = $this->repository->findCountWhere([['server_id', '=', $server->id]]); + if (! is_null($server->database_limit) && $databases >= $server->database_limit) { + throw new TooManyDatabasesException; + } + + $allowRandom = config('pterodactyl.client_features.databases.allow_random'); + $host = $this->databaseHostRepository->setColumns(['id'])->findWhere([ + ['node_id', '=', $server->node_id], + ])->random(); + + if (empty($host) && ! $allowRandom) { + throw new NoSuitableDatabaseHostException; + } + + if (empty($host)) { + $host = $this->databaseHostRepository->setColumns(['id'])->all()->random(); + if (empty($host)) { + throw new NoSuitableDatabaseHostException; + } + } + + return $this->managementService->create($server->id, [ + 'database_host_id' => $host->id, + 'database' => array_get($data, 'database'), + 'remote' => array_get($data, 'remote'), + ]); + } +} diff --git a/config/pterodactyl.php b/config/pterodactyl.php index 523080ae3..e06c709ef 100644 --- a/config/pterodactyl.php +++ b/config/pterodactyl.php @@ -163,6 +163,21 @@ return [ 'in_context' => env('PHRASE_IN_CONTEXT', false), ], + /* + |-------------------------------------------------------------------------- + | Language Editor + |-------------------------------------------------------------------------- + | + | Set `PHRASE_IN_CONTEXT` to true to enable the PhaseApp in-context editor + | on this site which allows you to translate the panel, from the panel. + */ + 'client_features' => [ + 'databases' => [ + 'enabled' => env('PTERODACTYL_CLIENT_DATABASES_ENABLED', true), + 'allow_random' => env('PTERODACTYL_CLIENT_DATABASES_ALLOW_RANDOM', true), + ], + ], + /* |-------------------------------------------------------------------------- | File Editor diff --git a/resources/themes/pterodactyl/server/databases/index.blade.php b/resources/themes/pterodactyl/server/databases/index.blade.php index 6ba24c1ad..b8e01db84 100644 --- a/resources/themes/pterodactyl/server/databases/index.blade.php +++ b/resources/themes/pterodactyl/server/databases/index.blade.php @@ -21,15 +21,10 @@ @section('content')
-
+

@lang('server.config.database.your_dbs')

- @if(auth()->user()->root_admin) - - @endif
@if(count($databases) > 0)
@@ -69,17 +64,49 @@
@lang('server.config.database.no_dbs') - @if(Auth::user()->root_admin === 1) - @lang('server.config.database.add_db') - @endif
@endif
+ @if($allowCreation) +
+
+
+

Create New Database

+
+ @if($overLimit) +
+
+ You are currently using {{ count($databases) }} of your {{ $server->database_limit ?? '∞' }} allowed databases. +
+
+ @else +
+
+
+ +
+ s{{ $server->id }}_ + +
+
+
+ + +

This should reflect the IP address that connections are allowed from. Uses standard MySQL notation. If unsure leave as %.

+
+
+ +
+ @endif +
+
+ @endif
@endsection diff --git a/routes/server.php b/routes/server.php index 85283df9e..93005aaec 100644 --- a/routes/server.php +++ b/routes/server.php @@ -38,6 +38,8 @@ Route::group(['prefix' => 'settings'], function () { Route::group(['prefix' => 'databases'], function () { Route::get('/', 'DatabaseController@index')->name('server.databases.index'); + Route::post('/new', 'DatabaseController@store')->name('server.databases.new'); + Route::patch('/password', 'DatabaseController@update')->middleware('server..database')->name('server.databases.password'); });