Untested code to perform an update of server build settings

This commit is contained in:
Dane Everitt 2019-11-24 15:08:54 -08:00
parent c17f9ba8a9
commit 547e8840e2
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
10 changed files with 306 additions and 141 deletions

View file

@ -0,0 +1,48 @@
<?php
namespace Pterodactyl\Http\Controllers\Admin\Nodes;
use Illuminate\Http\Request;
use Illuminate\Contracts\View\Factory;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Repositories\Eloquent\NodeRepository;
class NodeController extends Controller
{
/**
* @var \Illuminate\Contracts\View\Factory
*/
private $view;
/**
* @var \Pterodactyl\Repositories\Eloquent\NodeRepository
*/
private $repository;
/**
* NodeController constructor.
*
* @param \Pterodactyl\Repositories\Eloquent\NodeRepository $repository
* @param \Illuminate\Contracts\View\Factory $view
*/
public function __construct(NodeRepository $repository, Factory $view)
{
$this->view = $view;
$this->repository = $repository;
}
/**
* Returns a listing of nodes on the system.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request)
{
$nodes = $this->repository
->setSearchTerm($request->input('query'))
->getNodeListingData();
return $this->view->make('admin.nodes.index', compact('nodes'));
}
}

View file

@ -0,0 +1,160 @@
<?php
namespace Pterodactyl\Http\Controllers\Admin\Nodes;
use Illuminate\Http\Request;
use Pterodactyl\Models\Node;
use Illuminate\Support\Collection;
use Illuminate\Contracts\View\Factory;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Repositories\Eloquent\NodeRepository;
use Pterodactyl\Repositories\Eloquent\ServerRepository;
use Pterodactyl\Traits\Controllers\JavascriptInjection;
use Pterodactyl\Services\Helpers\SoftwareVersionService;
use Pterodactyl\Repositories\Eloquent\LocationRepository;
use Pterodactyl\Repositories\Eloquent\AllocationRepository;
class NodeViewController extends Controller
{
use JavascriptInjection;
/**
* @var \Pterodactyl\Repositories\Eloquent\NodeRepository
*/
private $repository;
/**
* @var \Illuminate\Contracts\View\Factory
*/
private $view;
/**
* @var \Pterodactyl\Services\Helpers\SoftwareVersionService
*/
private $versionService;
/**
* @var \Pterodactyl\Repositories\Eloquent\LocationRepository
*/
private $locationRepository;
/**
* @var \Pterodactyl\Repositories\Eloquent\AllocationRepository
*/
private $allocationRepository;
/**
* @var \Pterodactyl\Repositories\Eloquent\ServerRepository
*/
private $serverRepository;
/**
* NodeViewController constructor.
*
* @param \Pterodactyl\Repositories\Eloquent\AllocationRepository $allocationRepository
* @param \Pterodactyl\Repositories\Eloquent\LocationRepository $locationRepository
* @param \Pterodactyl\Repositories\Eloquent\NodeRepository $repository
* @param \Pterodactyl\Repositories\Eloquent\ServerRepository $serverRepository
* @param \Pterodactyl\Services\Helpers\SoftwareVersionService $versionService
* @param \Illuminate\Contracts\View\Factory $view
*/
public function __construct(
AllocationRepository $allocationRepository,
LocationRepository $locationRepository,
NodeRepository $repository,
ServerRepository $serverRepository,
SoftwareVersionService $versionService,
Factory $view
) {
$this->repository = $repository;
$this->view = $view;
$this->versionService = $versionService;
$this->locationRepository = $locationRepository;
$this->allocationRepository = $allocationRepository;
$this->serverRepository = $serverRepository;
}
/**
* Returns index view for a specific node on the system.
*
* @param \Illuminate\Http\Request $request
* @param \Pterodactyl\Models\Node $node
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request, Node $node)
{
$node = $this->repository->loadLocationAndServerCount($node);
return $this->view->make('admin.nodes.view.index', [
'node' => $node,
'stats' => $this->repository->getUsageStats($node),
'version' => $this->versionService,
]);
}
/**
* Returns the settings page for a specific node.
*
* @param \Illuminate\Http\Request $request
* @param \Pterodactyl\Models\Node $node
* @return \Illuminate\Contracts\View\View
*/
public function settings(Request $request, Node $node)
{
return $this->view->make('admin.nodes.view.settings', [
'node' => $node,
'locations' => $this->locationRepository->all(),
]);
}
/**
* Return the node configuration page for a specific node.
*
* @param \Illuminate\Http\Request $request
* @param \Pterodactyl\Models\Node $node
* @return \Illuminate\Contracts\View\View
*/
public function configuration(Request $request, Node $node)
{
return $this->view->make('admin.nodes.view.configuration', compact('node'));
}
/**
* Return the node allocation management page.
*
* @param \Illuminate\Http\Request $request
* @param \Pterodactyl\Models\Node $node
* @return \Illuminate\Contracts\View\View
*/
public function allocations(Request $request, Node $node)
{
$node = $this->repository->loadNodeAllocations($node);
$this->plainInject(['node' => Collection::wrap($node)->only(['id'])]);
return $this->view->make('admin.nodes.view.allocation', [
'node' => $node,
'allocations' => $this->allocationRepository->setColumns(['ip'])->getUniqueAllocationIpsForNode($node->id),
]);
}
/**
* Return a listing of servers that exist for this specific node.
*
* @param \Illuminate\Http\Request $request
* @param \Pterodactyl\Models\Node $node
* @return \Illuminate\Contracts\View\View
*/
public function servers(Request $request, Node $node)
{
$this->plainInject([
'node' => Collection::wrap($node->makeVisible('daemonSecret'))
->only(['scheme', 'fqdn', 'daemonListen', 'daemonSecret']),
]);
return $this->view->make('admin.nodes.view.servers', [
'node' => $node,
'servers' => $this->serverRepository->loadAllServersForNode($node->id, 25),
]);
}
}

View file

@ -9,7 +9,6 @@
namespace Pterodactyl\Http\Controllers\Admin;
use Javascript;
use Cake\Chronos\Chronos;
use Illuminate\Http\Request;
use Pterodactyl\Models\Node;
@ -138,19 +137,6 @@ class NodesController extends Controller
$this->versionService = $versionService;
}
/**
* Displays the index page listing all nodes on the panel.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function index(Request $request)
{
return view('admin.nodes.index', [
'nodes' => $this->repository->setSearchTerm($request->input('query'))->getNodeListingData(),
]);
}
/**
* Displays create new node page.
*
@ -184,79 +170,6 @@ class NodesController extends Controller
return redirect()->route('admin.nodes.view.allocation', $node->id);
}
/**
* Shows the index overview page for a specific node.
*
* @param \Pterodactyl\Models\Node $node
* @return \Illuminate\View\View
*/
public function viewIndex(Node $node)
{
return view('admin.nodes.view.index', [
'node' => $this->repository->loadLocationAndServerCount($node),
'stats' => $this->repository->getUsageStats($node),
'version' => $this->versionService,
]);
}
/**
* Shows the settings page for a specific node.
*
* @param \Pterodactyl\Models\Node $node
* @return \Illuminate\View\View
*/
public function viewSettings(Node $node)
{
return view('admin.nodes.view.settings', [
'node' => $node,
'locations' => $this->locationRepository->all(),
]);
}
/**
* Shows the configuration page for a specific node.
*
* @param \Pterodactyl\Models\Node $node
* @return \Illuminate\View\View
*/
public function viewConfiguration(Node $node)
{
return view('admin.nodes.view.configuration', ['node' => $node]);
}
/**
* Shows the allocation page for a specific node.
*
* @param \Pterodactyl\Models\Node $node
* @return \Illuminate\View\View
*/
public function viewAllocation(Node $node)
{
$this->repository->loadNodeAllocations($node);
Javascript::put(['node' => collect($node)->only(['id'])]);
return view('admin.nodes.view.allocation', [
'allocations' => $this->allocationRepository->setColumns(['ip'])->getUniqueAllocationIpsForNode($node->id),
'node' => $node,
]);
}
/**
* Shows the server listing page for a specific node.
*
* @param \Pterodactyl\Models\Node $node
* @return \Illuminate\View\View
*/
public function viewServers(Node $node)
{
$servers = $this->serverRepository->loadAllServersForNode($node->id, 25);
Javascript::put([
'node' => collect($node->makeVisible('daemonSecret'))->only(['scheme', 'fqdn', 'daemonListen', 'daemonSecret']),
]);
return view('admin.nodes.view.servers', ['node' => $node, 'servers' => $servers]);
}
/**
* Updates settings for a node.
*

View file

@ -170,6 +170,18 @@ class Server extends Validable
return Schema::getColumnListing($this->getTable());
}
/**
* Returns the format for server allocations when communicating with the Daemon.
*
* @return array
*/
public function getAllocationMappings(): array
{
return $this->allocations->groupBy('ip')->map(function ($item) {
return $item->pluck('port');
})->toArray();
}
/**
* @return bool
*/

View file

@ -31,27 +31,31 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa
*/
public function getUsageStats(Node $node): array
{
$stats = $this->getBuilder()->select(
$this->getBuilder()->raw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk')
)->join('servers', 'servers.node_id', '=', 'nodes.id')->where('node_id', $node->id)->first();
$stats = $this->getBuilder()
->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk')
->join('servers', 'servers.node_id', '=', 'nodes.id')
->where('node_id', '=', $node->id)
->first();
return collect(['disk' => $stats->sum_disk, 'memory' => $stats->sum_memory])->mapWithKeys(function ($value, $key) use ($node) {
$maxUsage = $node->{$key};
if ($node->{$key . '_overallocate'} > 0) {
$maxUsage = $node->{$key} * (1 + ($node->{$key . '_overallocate'} / 100));
}
return Collection::make(['disk' => $stats->sum_disk, 'memory' => $stats->sum_memory])
->mapWithKeys(function ($value, $key) use ($node) {
$maxUsage = $node->{$key};
if ($node->{$key . '_overallocate'} > 0) {
$maxUsage = $node->{$key} * (1 + ($node->{$key . '_overallocate'} / 100));
}
$percent = ($value / $maxUsage) * 100;
$percent = ($value / $maxUsage) * 100;
return [
$key => [
'value' => number_format($value),
'max' => number_format($maxUsage),
'percent' => $percent,
'css' => ($percent <= self::THRESHOLD_PERCENTAGE_LOW) ? 'green' : (($percent > self::THRESHOLD_PERCENTAGE_MEDIUM) ? 'red' : 'yellow'),
],
];
})->toArray();
return [
$key => [
'value' => number_format($value),
'max' => number_format($maxUsage),
'percent' => $percent,
'css' => ($percent <= self::THRESHOLD_PERCENTAGE_LOW) ? 'green' : (($percent > self::THRESHOLD_PERCENTAGE_MEDIUM) ? 'red' : 'yellow'),
],
];
})
->toArray();
}
/**
@ -132,7 +136,12 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa
public function loadNodeAllocations(Node $node, bool $refresh = false): Node
{
$node->setRelation('allocations',
$node->allocations()->orderByRaw('server_id IS NOT NULL DESC, server_id IS NULL')->orderByRaw('INET_ATON(ip) ASC')->orderBy('port', 'asc')->with('server:id,name')->paginate(50)
$node->allocations()
->orderByRaw('server_id IS NOT NULL DESC, server_id IS NULL')
->orderByRaw('INET_ATON(ip) ASC')
->orderBy('port', 'asc')
->with('server:id,name')
->paginate(50)
);
return $node;

View file

@ -51,4 +51,22 @@ class DaemonServerRepository extends DaemonRepository
throw new DaemonConnectionException($exception);
}
}
/**
* Updates details about a server on the Daemon.
*
* @param array $data
*
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
*/
public function update(array $data): void
{
Assert::isInstanceOf($this->server, Server::class);
try {
$this->getHttpClient()->patch('/api/servers/' . $this->server->uuid, ['json' => $data]);
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
}
}

View file

@ -6,10 +6,10 @@ use Pterodactyl\Models\Node;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Repositories\Daemon\ConfigurationRepository;
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
use Pterodactyl\Exceptions\Service\Node\ConfigurationNotPersistedException;
use Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface;
class NodeUpdateService
{
@ -32,12 +32,12 @@ class NodeUpdateService
* UpdateService constructor.
*
* @param \Illuminate\Database\ConnectionInterface $connection
* @param \Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface $configurationRepository
* @param \Pterodactyl\Repositories\Daemon\ConfigurationRepository $configurationRepository
* @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $repository
*/
public function __construct(
ConnectionInterface $connection,
ConfigurationRepositoryInterface $configurationRepository,
ConfigurationRepository $configurationRepository,
NodeRepositoryInterface $repository
) {
$this->connection = $connection;
@ -58,6 +58,8 @@ class NodeUpdateService
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
* @throws \Pterodactyl\Exceptions\Service\Node\ConfigurationNotPersistedException
*
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function handle(Node $node, array $data, bool $resetToken = false)
{

View file

@ -6,11 +6,11 @@ use Pterodactyl\Models\Server;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface;
class BuildModificationService
{
@ -25,7 +25,7 @@ class BuildModificationService
private $connection;
/**
* @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface
* @var \Pterodactyl\Repositories\Wings\DaemonServerRepository
*/
private $daemonServerRepository;
@ -39,13 +39,13 @@ class BuildModificationService
*
* @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository
* @param \Illuminate\Database\ConnectionInterface $connection
* @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonServerRepository
* @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonServerRepository
* @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
*/
public function __construct(
AllocationRepositoryInterface $allocationRepository,
ConnectionInterface $connection,
DaemonServerRepositoryInterface $daemonServerRepository,
DaemonServerRepository $daemonServerRepository,
ServerRepositoryInterface $repository
) {
$this->allocationRepository = $allocationRepository;
@ -67,23 +67,22 @@ class BuildModificationService
*/
public function handle(Server $server, array $data)
{
$build = [];
$this->connection->beginTransaction();
$this->processAllocations($server, $data);
if (isset($data['allocation_id']) && $data['allocation_id'] != $server->allocation_id) {
try {
$allocation = $this->allocationRepository->findFirstWhere([
$this->allocationRepository->findFirstWhere([
['id', '=', $data['allocation_id']],
['server_id', '=', $server->id],
]);
} catch (RecordNotFoundException $ex) {
throw new DisplayException(trans('admin/server.exceptions.default_allocation_not_found'));
}
$build['default'] = ['ip' => $allocation->ip, 'port' => $allocation->port];
}
/** @var \Pterodactyl\Models\Server $server */
$server = $this->repository->withFreshModel()->update($server->id, [
'oom_disabled' => array_get($data, 'oom_disabled'),
'memory' => array_get($data, 'memory'),
@ -96,20 +95,28 @@ class BuildModificationService
'allocation_limit' => array_get($data, 'allocation_limit'),
]);
$allocations = $this->allocationRepository->findWhere([['server_id', '=', $server->id]]);
$build['oom_disabled'] = $server->oom_disabled;
$build['memory'] = (int) $server->memory;
$build['swap'] = (int) $server->swap;
$build['io'] = (int) $server->io;
$build['cpu'] = (int) $server->cpu;
$build['disk'] = (int) $server->disk;
$build['ports|overwrite'] = $allocations->groupBy('ip')->map(function ($item) {
return $item->pluck('port');
})->toArray();
$updateData = [
'allocations' => [
'default' => [
'ip' => $server->allocation->ip,
'port' => $server->allocation->port,
],
'mappings' => [$server->getAllocationMappings()],
],
'build' => [
'memory' => $server->memory,
'swap' => $server->swap,
'io' => $server->io,
'cpu' => $server->cpu,
'disk' => $server->disk,
],
'container' => [
'oom_disabled' => $server->oom_disabled,
],
];
try {
$this->daemonServerRepository->setServer($server)->update(['build' => $build]);
$this->daemonServerRepository->setServer($server)->update($updateData);
$this->connection->commit();
} catch (RequestException $exception) {
throw new DaemonConnectionException($exception);

View file

@ -76,7 +76,6 @@ class ServerConfigurationStructureService
'suspended' => (bool) $server->suspended,
'environment' => $this->environment->handle($server),
'build' => [
'oom_disabled' => $server->oom_disabled,
'memory' => $server->memory,
'swap' => $server->swap,
'io' => $server->io,
@ -90,6 +89,7 @@ class ServerConfigurationStructureService
],
'container' => [
'image' => $server->image,
'oom_disabled' => $server->oom_disabled,
'requires_rebuild' => false,
],
'allocations' => [
@ -97,11 +97,7 @@ class ServerConfigurationStructureService
'ip' => $server->allocation->ip,
'port' => $server->allocation->port,
],
'mappings' => [
$server->allocations->groupBy('ip')->map(function ($item) {
return $item->pluck('port');
})->toArray(),
],
'mappings' => [$server->getAllocationMappings()],
],
];
}

View file

@ -143,13 +143,13 @@ Route::group(['prefix' => 'servers'], function () {
|
*/
Route::group(['prefix' => 'nodes'], function () {
Route::get('/', 'NodesController@index')->name('admin.nodes');
Route::get('/', 'Nodes\NodeController@index')->name('admin.nodes');
Route::get('/new', 'NodesController@create')->name('admin.nodes.new');
Route::get('/view/{node}', 'NodesController@viewIndex')->name('admin.nodes.view');
Route::get('/view/{node}/settings', 'NodesController@viewSettings')->name('admin.nodes.view.settings');
Route::get('/view/{node}/configuration', 'NodesController@viewConfiguration')->name('admin.nodes.view.configuration');
Route::get('/view/{node}/allocation', 'NodesController@viewAllocation')->name('admin.nodes.view.allocation');
Route::get('/view/{node}/servers', 'NodesController@viewServers')->name('admin.nodes.view.servers');
Route::get('/view/{node}', 'Nodes\NodeViewController@index')->name('admin.nodes.view');
Route::get('/view/{node}/settings', 'Nodes\NodeViewController@settings')->name('admin.nodes.view.settings');
Route::get('/view/{node}/configuration', 'Nodes\NodeViewController@configuration')->name('admin.nodes.view.configuration');
Route::get('/view/{node}/allocation', 'Nodes\NodeViewController@allocations')->name('admin.nodes.view.allocation');
Route::get('/view/{node}/servers', 'Nodes\NodeViewController@servers')->name('admin.nodes.view.servers');
Route::get('/view/{node}/settings/token', 'NodesController@setToken')->name('admin.nodes.view.configuration.token');
Route::post('/new', 'NodesController@store');