Begin working on administrative server view changes

Also includes tests for the DatabaseCreation service.
This commit is contained in:
Dane Everitt 2017-07-21 21:17:42 -05:00
parent 0c513f24d5
commit 580e5ac569
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
18 changed files with 584 additions and 135 deletions

View file

@ -34,4 +34,12 @@ interface AllocationRepositoryInterface extends RepositoryInterface
* @return int
*/
public function assignAllocationsToServer($server, array $ids);
/**
* Return all of the allocations for a specific node.
*
* @param int $node
* @return \Illuminate\Database\Eloquent\Collection
*/
public function getAllocationsForNode($node);
}

View file

@ -32,7 +32,7 @@ interface ServerRepositoryInterface extends BaseRepositoryInterface
* @param int $id
* @param array $overrides
* @param bool $start
* @return mixed
* @return \Psr\Http\Message\ResponseInterface
*/
public function create($id, $overrides = [], $start = false);
}

View file

@ -28,5 +28,11 @@ use Pterodactyl\Contracts\Repository\Attributes\SearchableInterface;
interface NodeRepositoryInterface extends RepositoryInterface, SearchableInterface
{
//
/**
* Return a collection of nodes beloning to a specific location for use on frontend display.
*
* @param int $location
* @return mixed
*/
public function getNodesForLocation($location);
}

View file

@ -41,6 +41,8 @@ interface ServerRepositoryInterface extends RepositoryInterface, SearchableInter
*
* @param int $id
* @return mixed
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function findWithVariables($id);
@ -49,7 +51,30 @@ interface ServerRepositoryInterface extends RepositoryInterface, SearchableInter
* default if there is no value defined for the specific server requested.
*
* @param int $id
* @return array
* @param bool $returnAsObject
* @return array|object
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function getVariablesWithValues($id);
public function getVariablesWithValues($id, $returnAsObject = false);
/**
* Return enough data to be used for the creation of a server via the daemon.
*
* @param int $id
* @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Model
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function getDataForCreation($id);
/**
* Return a server as well as associated databases and their hosts.
*
* @param int $id
* @return mixed
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function getWithDatabases($id);
}

View file

@ -2,7 +2,6 @@
namespace Pterodactyl\Exceptions;
use Log;
use Exception;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
@ -21,6 +20,7 @@ class Handler extends ExceptionHandler
\Illuminate\Database\Eloquent\ModelNotFoundException::class,
\Illuminate\Session\TokenMismatchException::class,
\Illuminate\Validation\ValidationException::class,
\Pterodactyl\Exceptions\Model\DataValidationException::class,
];
/**
@ -28,20 +28,23 @@ class Handler extends ExceptionHandler
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* @param \Exception $exception
* @return void
* @param \Exception $exception
*
* @throws \Exception
*/
public function report(Exception $exception)
{
return parent::report($exception);
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @return \Illuminate\Http\Response
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @return \Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response
*
* @throws \Exception
*/
public function render($request, Exception $exception)
{

View file

@ -28,8 +28,10 @@ use Illuminate\Contracts\Config\Repository as ConfigRepository;
use Log;
use Alert;
use Javascript;
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
use Pterodactyl\Contracts\Repository\LocationRepositoryInterface;
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Contracts\Repository\ServiceRepositoryInterface;
use Pterodactyl\Http\Requests\Admin\ServerFormRequest;
@ -38,14 +40,19 @@ use Illuminate\Http\Request;
use GuzzleHttp\Exception\TransferException;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository;
use Pterodactyl\Repositories\ServerRepository;
use Pterodactyl\Repositories\DatabaseRepository;
use Pterodactyl\Exceptions\AutoDeploymentException;
use Pterodactyl\Exceptions\DisplayValidationException;
use Pterodactyl\Services\Servers\ServerService;
use Pterodactyl\Services\Servers\CreationService;
class ServersController extends Controller
{
/**
* @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface
*/
protected $allocationRepository;
/**
* @var \Illuminate\Contracts\Config\Repository
*/
@ -56,18 +63,28 @@ class ServersController extends Controller
*/
protected $databaseRepository;
/**
* @var \Pterodactyl\Contracts\Repository\DatabaseHostRepositoryInterface
*/
protected $databaseHostRepository;
/**
* @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface
*/
protected $locationRepository;
/**
* @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface
*/
protected $nodeRepository;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
*/
protected $repository;
/**
* @var \Pterodactyl\Services\Servers\ServerService
* @var \Pterodactyl\Services\Servers\CreationService
*/
protected $service;
@ -77,16 +94,22 @@ class ServersController extends Controller
protected $serviceRepository;
public function __construct(
AllocationRepositoryInterface $allocationRepository,
ConfigRepository $config,
CreationService $service,
DatabaseRepositoryInterface $databaseRepository,
DatabaseHostRepository $databaseHostRepository,
LocationRepositoryInterface $locationRepository,
ServerService $service,
NodeRepositoryInterface $nodeRepository,
ServerRepositoryInterface $repository,
ServiceRepositoryInterface $serviceRepository
) {
$this->allocationRepository = $allocationRepository;
$this->config = $config;
$this->databaseRepository = $databaseRepository;
$this->databaseHostRepository = $databaseHostRepository;
$this->locationRepository = $locationRepository;
$this->nodeRepository = $nodeRepository;
$this->repository = $repository;
$this->service = $service;
$this->serviceRepository = $serviceRepository;
@ -132,35 +155,25 @@ class ServersController extends Controller
}
/**
* Create server controller method.
* Handle POST of server creation form.
*
* @param \Illuminate\Http\Request $request
* @param \Pterodactyl\Http\Requests\Admin\ServerFormRequest $request
* @return \Illuminate\Http\RedirectResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
*/
public function store(Request $request)
public function store(ServerFormRequest $request)
{
$this->service->create($request->all());
try {
$server = $this->service->create($request->except('_token'));
return redirect()->route('admin.servers.view', $server->id);
} catch (TransferException $ex) {
Log::warning($ex);
Alert::danger('A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.')->flash();
}
return redirect()->route('admin.servers.new')->withInput();
// try {
// $repo = new ServerRepository;
// $server = $repo->create($request->except('_token'));
//
// return redirect()->route('admin.servers.view', $server->id);
// } catch (DisplayValidationException $ex) {
// return redirect()->route('admin.servers.new')->withErrors(json_decode($ex->getMessage()))->withInput();
// } catch (DisplayException $ex) {
// Alert::danger($ex->getMessage())->flash();
// } catch (AutoDeploymentException $ex) {
// Alert::danger('Auto-Deployment Exception: ' . $ex->getMessage())->flash();
// } catch (TransferException $ex) {
// Log::warning($ex);
// Alert::danger('A TransferException was encountered while trying to contact the daemon, please ensure it is online and accessible. This error has been logged.')->flash();
// } catch (\Exception $ex) {
// Log::error($ex);
// Alert::danger('An unhandled exception occured while attemping to add this server. Please try again.')->flash();
// }
//
// return redirect()->route('admin.servers.new')->withInput();
}
/**
@ -171,26 +184,7 @@ class ServersController extends Controller
*/
public function nodes(Request $request)
{
$nodes = Models\Node::with('allocations')->where('location_id', $request->input('location'))->get();
return $nodes->map(function ($item) {
$filtered = $item->allocations->where('server_id', null)->map(function ($map) {
return collect($map)->only(['id', 'ip', 'port']);
});
$item->ports = $filtered->map(function ($map) use ($item) {
return [
'id' => $map['id'],
'text' => $map['ip'] . ':' . $map['port'],
];
})->values();
return [
'id' => $item->id,
'text' => $item->name,
'allocations' => $item->ports,
];
})->values();
return $this->nodeRepository->getNodesForLocation($request->input('location'));
}
/**
@ -202,7 +196,7 @@ class ServersController extends Controller
*/
public function viewIndex(Request $request, $id)
{
return view('admin.servers.view.index', ['server' => Models\Server::findOrFail($id)]);
return view('admin.servers.view.index', ['server' => $this->repository->find($id)]);
}
/**
@ -214,9 +208,12 @@ class ServersController extends Controller
*/
public function viewDetails(Request $request, $id)
{
$server = Models\Server::where('installed', 1)->findOrFail($id);
return view('admin.servers.view.details', ['server' => $server]);
return view('admin.servers.view.details', [
'server' => $this->repository->findFirstWhere([
['id', '=', $id],
['installed', '=', 1],
]),
]);
}
/**
@ -228,12 +225,17 @@ class ServersController extends Controller
*/
public function viewBuild(Request $request, $id)
{
$server = Models\Server::where('installed', 1)->with('node.allocations')->findOrFail($id);
$server = $this->repository->findFirstWhere([
['id', '=', $id],
['installed', '=', 1],
]);
$allocations = $this->allocationRepository->getAllocationsForNode($server->node_id);
return view('admin.servers.view.build', [
'server' => $server,
'assigned' => $server->node->allocations->where('server_id', $server->id)->sortBy('port')->sortBy('ip'),
'unassigned' => $server->node->allocations->where('server_id', null)->sortBy('port')->sortBy('ip'),
'assigned' => $allocations->where('server_id', $server->id)->sortBy('port')->sortBy('ip'),
'unassigned' => $allocations->where('server_id', null)->sortBy('port')->sortBy('ip'),
]);
}
@ -246,29 +248,24 @@ class ServersController extends Controller
*/
public function viewStartup(Request $request, $id)
{
$server = Models\Server::where('installed', 1)->with('option.variables', 'variables')->findOrFail($id);
$server->option->variables->transform(function ($item, $key) use ($server) {
$item->server_value = $server->variables->where('variable_id', $item->id)->pluck('variable_value')->first();
$parameters = $this->repository->getVariablesWithValues($id, true);
if (! $parameters->server->installed) {
abort(404);
}
return $item;
});
$services = $this->serviceRepository->getWithOptions();
$services = Models\Service::with('options.packs', 'options.variables')->get();
Javascript::put([
'services' => $services->map(function ($item) {
return array_merge($item->toArray(), [
'options' => $item->options->keyBy('id')->toArray(),
]);
})->keyBy('id'),
'server_variables' => $server->variables->mapWithKeys(function ($item) {
return ['env_' . $item->variable_id => [
'value' => $item->variable_value,
]];
})->toArray(),
'server_variables' => $parameters->data,
]);
return view('admin.servers.view.startup', [
'server' => $server,
'server' => $parameters->server,
'services' => $services,
]);
}
@ -282,10 +279,10 @@ class ServersController extends Controller
*/
public function viewDatabase(Request $request, $id)
{
$server = Models\Server::where('installed', 1)->with('databases.host')->findOrFail($id);
$server = $this->repository->getWithDatabases($id);
return view('admin.servers.view.database', [
'hosts' => Models\DatabaseHost::all(),
'hosts' => $this->databaseHostRepository->all(),
'server' => $server,
]);
}
@ -299,7 +296,7 @@ class ServersController extends Controller
*/
public function viewManage(Request $request, $id)
{
return view('admin.servers.view.manage', ['server' => Models\Server::findOrFail($id)]);
return view('admin.servers.view.manage', ['server' => $this->repository->find($id)]);
}
/**
@ -311,7 +308,7 @@ class ServersController extends Controller
*/
public function viewDelete(Request $request, $id)
{
return view('admin.servers.view.delete', ['server' => Models\Server::findOrFail($id)]);
return view('admin.servers.view.delete', ['server' => $this->repository->find($id)]);
}
/**

View file

@ -78,6 +78,15 @@ class ServerFormRequest extends AdminFormRequest
], function ($input) {
return ! ($input->auto_deploy);
});
if ($this->input('pack_id') !== 0) {
$validator->sometimes('pack_id', [
Rule::exists('packs', 'id')->where(function ($query) {
$query->where('selectable', 1);
$query->where('option_id', $this->input('option_id'));
}),
]);
}
});
}
}

View file

@ -79,8 +79,8 @@ class ServerRepository extends BaseRepository implements ServerRepositoryInterfa
array_set($data, $key, $value);
}
// $this->getHttpClient()->request('POST', '/servers', [
// 'json' => $data,
// ]);
return $this->getHttpClient()->request('POST', '/servers', [
'json' => $data,
]);
}
}

View file

@ -44,4 +44,12 @@ class AllocationRepository extends EloquentRepository implements AllocationRepos
{
return $this->getBuilder()->whereIn('id', $ids)->update(['server_id' => $server]);
}
/**
* {@inheritdoc}
*/
public function getAllocationsForNode($node)
{
return $this->getBuilder()->where('node_id', $node)->get();
}
}

View file

@ -37,4 +37,31 @@ class NodeRepository extends SearchableRepository implements NodeRepositoryInter
{
return Node::class;
}
/**
* {@inheritdoc}
*/
public function getNodesForLocation($location)
{
$instance = $this->getBuilder()->with('allocations')->where('location_id', $location)->get();
return $instance->map(function ($item) {
$filtered = $item->allocations->where('server_id', null)->map(function ($map) {
return collect($map)->only(['id', 'ip', 'port']);
});
$item->ports = $filtered->map(function ($map) {
return [
'id' => $map['id'],
'text' => sprintf('%s:%s', $map['ip'], $map['port']),
];
})->values();
return [
'id' => $item->id,
'text' => $item->name,
'allocations' => $item->ports,
];
})->values();
}
}

View file

@ -24,8 +24,8 @@
namespace Pterodactyl\Repositories\Eloquent;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Pterodactyl\Models\Server;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Repositories\Eloquent\Attributes\SearchableRepository;
@ -60,8 +60,8 @@ class ServerRepository extends SearchableRepository implements ServerRepositoryI
public function findWithVariables($id)
{
$instance = $this->getBuilder()->with('option.variables', 'variables')
->where($this->getModel()->getKeyName(), '=', $id)
->first($this->getColumns());
->where($this->getModel()->getKeyName(), '=', $id)
->first($this->getColumns());
if (is_null($instance)) {
throw new RecordNotFoundException();
@ -73,10 +73,10 @@ class ServerRepository extends SearchableRepository implements ServerRepositoryI
/**
* {@inheritdoc}
*/
public function getVariablesWithValues($id)
public function getVariablesWithValues($id, $returnWithObject = false)
{
$instance = $this->getBuilder()->with('variables', 'option.variables')
->find($id, $this->getColumns());
->find($id, $this->getColumns());
if (! $instance) {
throw new RecordNotFoundException();
@ -89,13 +89,39 @@ class ServerRepository extends SearchableRepository implements ServerRepositoryI
$data[$item->env_variable] = $display ?? $item->default_value;
});
if ($returnWithObject) {
return (object) [
'data' => $data,
'server' => $instance,
];
}
return $data;
}
/**
* {@inheritdoc}
*/
public function getDataForCreation($id)
{
$instance = $this->getBuilder()->with('allocation', 'allocations', 'pack', 'option.service')
->find($id, $this->getColumns());
->find($id, $this->getColumns());
if (! $instance) {
throw new RecordNotFoundException();
}
return $instance;
}
/**
* {@inheritdoc}
*/
public function getWithDatabases($id)
{
$instance = $this->getBuilder()->with('databases.host')
->where('installed', 1)
->find($id, $this->getColumns());
if (! $instance) {
throw new RecordNotFoundException();

View file

@ -118,7 +118,7 @@ class CreationService
$this->repository->dropUser($database->username, $database->remote, 'dynamic');
$this->repository->flush('dynamic');
}
} catch (\Exception $ex) {
} catch (\Exception $exTwo) {
// ignore an exception
}
@ -153,7 +153,7 @@ class CreationService
]);
$this->repository->dropUser($database->username, $database->remote, 'dynamic');
$this->repository->createUser($database->username, $database->remote, $password);
$this->repository->createUser($database->username, $database->remote, $password, 'dynamic');
$this->repository->assignUserToDatabase(
$database->database, $database->username, $database->remote, 'dynamic'
);

View file

@ -73,7 +73,7 @@ class LocationService
/**
* Delete a model from the DB.
*
* @param int $id
* @param int $id
* @return bool
*
* @throws \Pterodactyl\Exceptions\DisplayException

View file

@ -33,34 +33,66 @@ use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
use Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface;
use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface;
class ServerService
class CreationService
{
/**
* @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface
*/
protected $allocationRepository;
/**
* @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface
*/
protected $daemonServerRepository;
/**
* @var \Illuminate\Database\ConnectionInterface
*/
protected $database;
/**
* @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface
*/
protected $nodeRepository;
/**
* @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
*/
protected $repository;
/**
* @var \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface
*/
protected $serverVariableRepository;
/**
* @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
*/
protected $userRepository;
protected $database;
protected $repository;
/**
* @var \Pterodactyl\Services\Servers\UsernameGenerationService
*/
protected $usernameService;
protected $serverVariableRepository;
protected $daemonServerRepository;
/**
* @var \Pterodactyl\Services\Servers\VariableValidatorService
*/
protected $validatorService;
/**
* CreationService constructor.
*
* @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository
* @param \Illuminate\Database\ConnectionInterface $database
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
* @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository
* @param \Pterodactyl\Contracts\Repository\ServerVariableRepositoryInterface $serverVariableRepository
* @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $nodeRepository
* @param \Pterodactyl\Services\Servers\UsernameGenerationService $usernameService
* @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository
* @param \Pterodactyl\Services\Servers\VariableValidatorService $validatorService
*/
public function __construct(
AllocationRepositoryInterface $allocationRepository,
ConnectionInterface $database,
@ -83,9 +115,17 @@ class ServerService
$this->daemonServerRepository = $daemonServerRepository;
}
/**
* Create a server on both the panel and daemon.
*
* @param array $data
* @return mixed
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
*/
public function create(array $data)
{
// @todo auto-deployment and packs
// @todo auto-deployment
$data['user_id'] = 1;
$node = $this->nodeRepository->find($data['node_id']);
@ -141,8 +181,11 @@ class ServerService
$this->serverVariableRepository->insert($records);
// Create the server on the daemon & commit it to the database.
$this->daemonServerRepository->setNode($server->node_id)->setAccessToken($node->daemonSecret)->create($server->id);
$this->database->rollBack();
$this->daemonServerRepository->setNode($server->node_id)
->setAccessToken($node->daemonSecret)
->create($server->id);
$this->database->commit();
return $server;
}

View file

@ -24,8 +24,8 @@
namespace Pterodactyl\Services\Servers;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Models\Server;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
class EnvironmentService
{

View file

@ -143,11 +143,11 @@ class VariableValidatorService
if ($validator->fails()) {
throw new DisplayValidationException(json_encode(
collect([
'notice' => [
sprintf('There was a validation error with the %s variable.', $item->name),
],
])->merge($validator->errors()->toArray())
collect([
'notice' => [
sprintf('There was a validation error with the %s variable.', $item->name),
],
])->merge($validator->errors()->toArray())
));
}