Add initial go at user created databases for servers, still needs cleaning

This commit is contained in:
Dane Everitt 2018-03-01 21:27:37 -06:00
parent 87b96bdfc8
commit 07893effa3
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
11 changed files with 314 additions and 25 deletions

View file

@ -0,0 +1,13 @@
<?php
namespace Pterodactyl\Exceptions\Service\Database;
use Pterodactyl\Exceptions\PterodactylException;
class DatabaseClientFeatureNotEnabledException extends PterodactylException
{
public function __construct()
{
parent::__construct('Client database creation is not enabled in this Panel.');
}
}

View file

@ -0,0 +1,16 @@
<?php
namespace Pterodactyl\Exceptions\Service\Database;
use Pterodactyl\Exceptions\DisplayException;
class NoSuitableDatabaseHostException extends DisplayException
{
/**
* NoSuitableDatabaseHostException constructor.
*/
public function __construct()
{
parent::__construct('No database host was found that meets the requirements for this server.');
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace Pterodactyl\Exceptions\Service\Database;
use Pterodactyl\Exceptions\DisplayException;
class TooManyDatabasesException extends DisplayException
{
public function __construct()
{
parent::__construct('Operation aborted: creating a new database would put this server over the defined limit.');
}
}

View file

@ -5,33 +5,64 @@ namespace Pterodactyl\Http\Controllers\Server;
use Illuminate\View\View;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Prologue\Alerts\AlertsMessageBag;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Traits\Controllers\JavascriptInjection;
use Pterodactyl\Services\Databases\DatabasePasswordService;
use Pterodactyl\Services\Databases\DeployServerDatabaseService;
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface;
use Pterodactyl\Http\Requests\Server\Database\StoreServerDatabaseRequest;
class DatabaseController extends Controller
{
use JavascriptInjection;
/**
* @var \Prologue\Alerts\AlertsMessageBag
*/
private $alert;
/**
* @var \Pterodactyl\Services\Databases\DeployServerDatabaseService
*/
private $deployServerDatabaseService;
/**
* @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface
*/
private $databaseHostRepository;
/**
* @var \Pterodactyl\Services\Databases\DatabasePasswordService
*/
protected $passwordService;
private $passwordService;
/**
* @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface
*/
protected $repository;
private $repository;
/**
* DatabaseController constructor.
*
* @param \Pterodactyl\Services\Databases\DatabasePasswordService $passwordService
* @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $repository
* @param \Prologue\Alerts\AlertsMessageBag $alert
* @param \Pterodactyl\Services\Databases\DeployServerDatabaseService $deployServerDatabaseService
* @param \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface $databaseHostRepository
* @param \Pterodactyl\Services\Databases\DatabasePasswordService $passwordService
* @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $repository
*/
public function __construct(DatabasePasswordService $passwordService, DatabaseRepositoryInterface $repository)
{
public function __construct(
AlertsMessageBag $alert,
DeployServerDatabaseService $deployServerDatabaseService,
DatabaseHostRepositoryInterface $databaseHostRepository,
DatabasePasswordService $passwordService,
DatabaseRepositoryInterface $repository
) {
$this->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.
*

View file

@ -0,0 +1,43 @@
<?php
namespace Pterodactyl\Http\Requests\Server\Database;
use Pterodactyl\Http\Requests\Server\ServerFormRequest;
class StoreServerDatabaseRequest extends ServerFormRequest
{
/**
* @return bool
*/
public function authorize()
{
if (! parent::authorize()) {
return false;
}
return config('pterodactyl.client_features.databases.enabled');
}
/**
* Return the user permission to validate this request aganist.
*
* @return string
*/
protected function permission(): string
{
return 'create-database';
}
/**
* Rules to validate this request aganist.
*
* @return array
*/
public function rules()
{
return [
'database' => 'required|string|min:1',
'remote' => 'required|string|regex:/^[0-9%.]{1,15}$/',
];
}
}

View file

@ -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');
}
}

View file

@ -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
*/

View file

@ -0,0 +1,87 @@
<?php
namespace Pterodactyl\Services\Databases;
use Pterodactyl\Models\Server;
use Pterodactyl\Models\Database;
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
use Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface;
use Pterodactyl\Exceptions\Service\Database\TooManyDatabasesException;
use Pterodactyl\Exceptions\Service\Database\NoSuitableDatabaseHostException;
use Pterodactyl\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException;
class DeployServerDatabaseService
{
/**
* @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface
*/
private $databaseHostRepository;
/**
* @var \Pterodactyl\Services\Databases\DatabaseManagementService
*/
private $managementService;
/**
* @var \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface
*/
private $repository;
/**
* ServerDatabaseCreationService constructor.
*
* @param \Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface $repository
* @param \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface $databaseHostRepository
* @param \Pterodactyl\Services\Databases\DatabaseManagementService $managementService
*/
public function __construct(
DatabaseRepositoryInterface $repository,
DatabaseHostRepositoryInterface $databaseHostRepository,
DatabaseManagementService $managementService
) {
$this->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'),
]);
}
}

View file

@ -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

View file

@ -21,15 +21,10 @@
@section('content')
<div class="row">
<div class="col-xs-12">
<div class="{{ $allowCreation ? 'col-xs-12 col-sm-8' : 'col-xs-12' }}">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">@lang('server.config.database.your_dbs')</h3>
@if(auth()->user()->root_admin)
<div class="box-tools">
<a href="{{ route('admin.servers.view.database', ['server' => $server->id]) }}" target="_blank" class="btn btn-sm btn-success">Create New</a>
</div>
@endif
</div>
@if(count($databases) > 0)
<div class="box-body table-responsive no-padding">
@ -69,17 +64,49 @@
<div class="box-body">
<div class="alert alert-info no-margin-bottom">
@lang('server.config.database.no_dbs')
@if(Auth::user()->root_admin === 1)
<a href="{{ route('admin.servers.view', [
'id' => $server->id,
'tab' => 'tab_database'
]) }}" target="_blank">@lang('server.config.database.add_db')</a>
@endif
</div>
</div>
@endif
</div>
</div>
@if($allowCreation)
<div class="col-xs-12 col-sm-4">
<div class="box box-success">
<div class="box-header with-border">
<h3 class="box-title">Create New Database</h3>
</div>
@if($overLimit)
<div class="box-body">
<div class="alert alert-danger no-margin">
You are currently using <strong>{{ count($databases) }}</strong> of your <strong>{{ $server->database_limit ?? '&infin;' }}</strong> allowed databases.
</div>
</div>
@else
<form action="{{ route('server.databases.new', $server->uuidShort) }}" method="POST">
<div class="box-body">
<div class="form-group">
<label for="pDatabaseName" class="control-label">Database</label>
<div class="input-group">
<span class="input-group-addon">s{{ $server->id }}_</span>
<input id="pDatabaseName" type="text" name="database" class="form-control" placeholder="database" />
</div>
</div>
<div class="form-group">
<label for="pRemote" class="control-label">Connections</label>
<input id="pRemote" type="text" name="remote" class="form-control" value="%" />
<p class="text-muted small">This should reflect the IP address that connections are allowed from. Uses standard MySQL notation. If unsure leave as <code>%</code>.</p>
</div>
</div>
<div class="box-footer">
{!! csrf_field() !!}
<p class="text-muted small">You are currently using <strong>{{ count($databases) }}</strong> of <strong>{{ $server->database_limit ?? '&infin;' }}</strong> databases. A username and password for this database will be randomly generated after form submission.</p>
<input type="submit" class="btn btn-sm btn-success pull-right" value="Create Database" />
</div>
</form>
@endif
</div>
</div>
@endif
</div>
@endsection

View file

@ -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');
});