Replace node repository

This commit is contained in:
Lance Pioch 2022-10-23 22:17:24 -04:00
parent 7266c66ebf
commit aeb7590a6d
14 changed files with 101 additions and 250 deletions

View file

@ -1,38 +0,0 @@
<?php
namespace Pterodactyl\Contracts\Repository;
use Pterodactyl\Models\Node;
use Illuminate\Support\Collection;
interface NodeRepositoryInterface extends RepositoryInterface
{
public const THRESHOLD_PERCENTAGE_LOW = 75;
public const THRESHOLD_PERCENTAGE_MEDIUM = 90;
/**
* Return the usage stats for a single node.
*/
public function getUsageStats(Node $node): array;
/**
* Return the usage stats for a single node.
*/
public function getUsageStatsRaw(Node $node): array;
/**
* Return a single node with location and server information.
*/
public function loadLocationAndServerCount(Node $node, bool $refresh = false): Node;
/**
* Attach a paginated set of allocations to a node mode including
* any servers that are also attached to those allocations.
*/
public function loadNodeAllocations(Node $node, bool $refresh = false): Node;
/**
* Return a collection of nodes for all locations to use in server creation UI.
*/
public function getNodesForServerCreation(): Collection;
}

View file

@ -9,7 +9,6 @@ use Illuminate\Support\Collection;
use Pterodactyl\Models\Allocation; use Pterodactyl\Models\Allocation;
use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Http\Controllers\Controller;
use Illuminate\Contracts\View\Factory as ViewFactory; use Illuminate\Contracts\View\Factory as ViewFactory;
use Pterodactyl\Repositories\Eloquent\NodeRepository;
use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Repositories\Eloquent\ServerRepository;
use Pterodactyl\Traits\Controllers\JavascriptInjection; use Pterodactyl\Traits\Controllers\JavascriptInjection;
use Pterodactyl\Services\Helpers\SoftwareVersionService; use Pterodactyl\Services\Helpers\SoftwareVersionService;
@ -26,7 +25,6 @@ class NodeViewController extends Controller
public function __construct( public function __construct(
private AllocationRepository $allocationRepository, private AllocationRepository $allocationRepository,
private LocationRepository $locationRepository, private LocationRepository $locationRepository,
private NodeRepository $repository,
private ServerRepository $serverRepository, private ServerRepository $serverRepository,
private SoftwareVersionService $versionService, private SoftwareVersionService $versionService,
private ViewFactory $view private ViewFactory $view
@ -38,11 +36,37 @@ class NodeViewController extends Controller
*/ */
public function index(Request $request, Node $node): View public function index(Request $request, Node $node): View
{ {
$node = $this->repository->loadLocationAndServerCount($node); $node->load('location')->loadCount('servers');
$stats = Node::query()
->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();
$usageStats = 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;
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 $this->view->make('admin.nodes.view.index', [ return $this->view->make('admin.nodes.view.index', [
'node' => $node, 'node' => $node,
'stats' => $this->repository->getUsageStats($node), 'stats' => $usageStats,
'version' => $this->versionService, 'version' => $this->versionService,
]); ]);
} }
@ -71,7 +95,15 @@ class NodeViewController extends Controller
*/ */
public function allocations(Request $request, Node $node): View public function allocations(Request $request, Node $node): View
{ {
$node = $this->repository->loadNodeAllocations($node); $node->setRelation(
'allocations',
$node->allocations()
->orderByRaw('server_id IS NOT NULL DESC, server_id IS NULL')
->orderByRaw('INET_ATON(ip) ASC')
->orderBy('port')
->with('server:id,name')
->paginate(50)
);
$this->plainInject(['node' => Collection::wrap($node)->only(['id'])]); $this->plainInject(['node' => Collection::wrap($node)->only(['id'])]);

View file

@ -18,7 +18,6 @@ use Pterodactyl\Services\Nodes\NodeDeletionService;
use Pterodactyl\Services\Allocations\AssignmentService; use Pterodactyl\Services\Allocations\AssignmentService;
use Pterodactyl\Services\Helpers\SoftwareVersionService; use Pterodactyl\Services\Helpers\SoftwareVersionService;
use Pterodactyl\Http\Requests\Admin\Node\NodeFormRequest; use Pterodactyl\Http\Requests\Admin\Node\NodeFormRequest;
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
use Pterodactyl\Http\Requests\Admin\Node\AllocationFormRequest; use Pterodactyl\Http\Requests\Admin\Node\AllocationFormRequest;
use Pterodactyl\Services\Allocations\AllocationDeletionService; use Pterodactyl\Services\Allocations\AllocationDeletionService;
@ -40,7 +39,6 @@ class NodesController extends Controller
protected NodeCreationService $creationService, protected NodeCreationService $creationService,
protected NodeDeletionService $deletionService, protected NodeDeletionService $deletionService,
protected LocationRepositoryInterface $locationRepository, protected LocationRepositoryInterface $locationRepository,
protected NodeRepositoryInterface $repository,
protected ServerRepositoryInterface $serverRepository, protected ServerRepositoryInterface $serverRepository,
protected NodeUpdateService $updateService, protected NodeUpdateService $updateService,
protected SoftwareVersionService $versionService, protected SoftwareVersionService $versionService,

View file

@ -11,7 +11,6 @@ use Prologue\Alerts\AlertsMessageBag;
use Illuminate\View\Factory as ViewFactory; use Illuminate\View\Factory as ViewFactory;
use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Repositories\Eloquent\NestRepository; use Pterodactyl\Repositories\Eloquent\NestRepository;
use Pterodactyl\Repositories\Eloquent\NodeRepository;
use Pterodactyl\Http\Requests\Admin\ServerFormRequest; use Pterodactyl\Http\Requests\Admin\ServerFormRequest;
use Pterodactyl\Services\Servers\ServerCreationService; use Pterodactyl\Services\Servers\ServerCreationService;
@ -23,7 +22,6 @@ class CreateServerController extends Controller
public function __construct( public function __construct(
private AlertsMessageBag $alert, private AlertsMessageBag $alert,
private NestRepository $nestRepository, private NestRepository $nestRepository,
private NodeRepository $nodeRepository,
private ServerCreationService $creationService, private ServerCreationService $creationService,
private ViewFactory $view private ViewFactory $view
) { ) {
@ -46,7 +44,7 @@ class CreateServerController extends Controller
$nests = $this->nestRepository->getWithEggs(); $nests = $this->nestRepository->getWithEggs();
JavaScript::put([ JavaScript::put([
'nodeData' => $this->nodeRepository->getNodesForServerCreation(), 'nodeData' => Node::getForServerCreation(),
'nests' => $nests->map(function ($item) { 'nests' => $nests->map(function ($item) {
return array_merge($item->toArray(), [ return array_merge($item->toArray(), [
'eggs' => $item->eggs->keyBy('id')->toArray(), 'eggs' => $item->eggs->keyBy('id')->toArray(),

View file

@ -3,13 +3,13 @@
namespace Pterodactyl\Http\Controllers\Admin\Servers; namespace Pterodactyl\Http\Controllers\Admin\Servers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Pterodactyl\Models\Node;
use Pterodactyl\Models\Server; use Pterodactyl\Models\Server;
use Prologue\Alerts\AlertsMessageBag; use Prologue\Alerts\AlertsMessageBag;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Pterodactyl\Models\ServerTransfer; use Pterodactyl\Models\ServerTransfer;
use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\Servers\TransferService; use Pterodactyl\Services\Servers\TransferService;
use Pterodactyl\Repositories\Eloquent\NodeRepository;
use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository; use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository;
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface; use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
@ -21,7 +21,6 @@ class ServerTransferController extends Controller
public function __construct( public function __construct(
private AlertsMessageBag $alert, private AlertsMessageBag $alert,
private AllocationRepositoryInterface $allocationRepository, private AllocationRepositoryInterface $allocationRepository,
private NodeRepository $nodeRepository,
private TransferService $transferService, private TransferService $transferService,
private DaemonConfigurationRepository $daemonConfigurationRepository private DaemonConfigurationRepository $daemonConfigurationRepository
) { ) {
@ -45,7 +44,13 @@ class ServerTransferController extends Controller
$additional_allocations = array_map('intval', $validatedData['allocation_additional'] ?? []); $additional_allocations = array_map('intval', $validatedData['allocation_additional'] ?? []);
// Check if the node is viable for the transfer. // Check if the node is viable for the transfer.
$node = $this->nodeRepository->getNodeWithResourceUsage($node_id); $node = Node::query()
->select(['nodes.id', 'nodes.fqdn', 'nodes.scheme', 'nodes.daemon_token', 'nodes.daemonListen', 'nodes.memory', 'nodes.disk', 'nodes.memory_overallocate', 'nodes.disk_overallocate'])
->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk')
->leftJoin('servers', 'servers.node_id', '=', 'nodes.id')
->where('nodes.id', $node_id)
->first();
if ($node->isViable($server->memory, $server->disk)) { if ($node->isViable($server->memory, $server->disk)) {
// Check if the selected daemon is online. // Check if the selected daemon is online.
$this->daemonConfigurationRepository->setNode($node)->getSystemInformation(); $this->daemonConfigurationRepository->setNode($node)->getSystemInformation();

View file

@ -6,13 +6,13 @@ use JavaScript;
use Illuminate\View\View; use Illuminate\View\View;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Pterodactyl\Models\Nest; use Pterodactyl\Models\Nest;
use Pterodactyl\Models\Node;
use Pterodactyl\Models\Server; use Pterodactyl\Models\Server;
use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\DisplayException;
use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\Servers\EnvironmentService; use Pterodactyl\Services\Servers\EnvironmentService;
use Illuminate\Contracts\View\Factory as ViewFactory; use Illuminate\Contracts\View\Factory as ViewFactory;
use Pterodactyl\Repositories\Eloquent\NestRepository; use Pterodactyl\Repositories\Eloquent\NestRepository;
use Pterodactyl\Repositories\Eloquent\NodeRepository;
use Pterodactyl\Repositories\Eloquent\MountRepository; use Pterodactyl\Repositories\Eloquent\MountRepository;
use Pterodactyl\Repositories\Eloquent\ServerRepository; use Pterodactyl\Repositories\Eloquent\ServerRepository;
use Pterodactyl\Traits\Controllers\JavascriptInjection; use Pterodactyl\Traits\Controllers\JavascriptInjection;
@ -31,7 +31,6 @@ class ServerViewController extends Controller
private LocationRepository $locationRepository, private LocationRepository $locationRepository,
private MountRepository $mountRepository, private MountRepository $mountRepository,
private NestRepository $nestRepository, private NestRepository $nestRepository,
private NodeRepository $nodeRepository,
private ServerRepository $repository, private ServerRepository $repository,
private EnvironmentService $environmentService, private EnvironmentService $environmentService,
private ViewFactory $view private ViewFactory $view
@ -128,14 +127,14 @@ class ServerViewController extends Controller
} }
// Check if the panel doesn't have at least 2 nodes configured. // Check if the panel doesn't have at least 2 nodes configured.
$nodes = $this->nodeRepository->all(); $nodeCount = Node::query()->count();
$canTransfer = false; $canTransfer = false;
if (count($nodes) >= 2) { if ($nodeCount >= 2) {
$canTransfer = true; $canTransfer = true;
} }
JavaScript::put([ JavaScript::put([
'nodeData' => $this->nodeRepository->getNodesForServerCreation(), 'nodeData' => Node::getForServerCreation(),
]); ]);
return $this->view->make('admin.servers.view.manage', [ return $this->view->make('admin.servers.view.manage', [

View file

@ -4,10 +4,9 @@ namespace Pterodactyl\Http\Middleware\Api\Daemon;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Pterodactyl\Models\Node;
use Illuminate\Contracts\Encryption\Encrypter; use Illuminate\Contracts\Encryption\Encrypter;
use Pterodactyl\Repositories\Eloquent\NodeRepository;
use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\HttpException;
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
@ -23,14 +22,14 @@ class DaemonAuthenticate
/** /**
* DaemonAuthenticate constructor. * DaemonAuthenticate constructor.
*/ */
public function __construct(private Encrypter $encrypter, private NodeRepository $repository) public function __construct(private Encrypter $encrypter)
{ {
} }
/** /**
* Check if a request from the daemon can be properly attributed back to a single node instance. * Check if a request from the daemon can be properly attributed back to a single node instance.
* *
* @throws \Symfony\Component\HttpKernel\Exception\HttpException * @throws HttpException
*/ */
public function handle(Request $request, Closure $next): mixed public function handle(Request $request, Closure $next): mixed
{ {
@ -43,24 +42,18 @@ class DaemonAuthenticate
} }
$parts = explode('.', $bearer); $parts = explode('.', $bearer);
// Ensure that all of the correct parts are provided in the header. // Ensure that all the correct parts are provided in the header.
if (count($parts) !== 2 || empty($parts[0]) || empty($parts[1])) { if (count($parts) !== 2 || empty($parts[0]) || empty($parts[1])) {
throw new BadRequestHttpException('The Authorization header provided was not in a valid format.'); throw new BadRequestHttpException('The Authorization header provided was not in a valid format.');
} }
try { /** @var Node $node */
/** @var \Pterodactyl\Models\Node $node */ $node = Node::query()->where('daemon_token_id', $parts[0])->firstOrFail();
$node = $this->repository->findFirstWhere([
'daemon_token_id' => $parts[0],
]);
if (hash_equals((string) $this->encrypter->decrypt($node->daemon_token), $parts[1])) { if (hash_equals((string) $this->encrypter->decrypt($node->daemon_token), $parts[1])) {
$request->attributes->set('node', $node); $request->attributes->set('node', $node);
return $next($request); return $next($request);
}
} catch (RecordNotFoundException $exception) {
// Do nothing, we don't want to expose a node not existing at all.
} }
throw new AccessDeniedHttpException('You are not authorized to access this resource.'); throw new AccessDeniedHttpException('You are not authorized to access this resource.');

View file

@ -225,4 +225,26 @@ class Node extends Model
return ($this->sum_memory + $memory) <= $memoryLimit && ($this->sum_disk + $disk) <= $diskLimit; return ($this->sum_memory + $memory) <= $memoryLimit && ($this->sum_disk + $disk) <= $diskLimit;
} }
public static function getForServerCreation()
{
return self::with('allocations')->get()->map(function (Node $item) {
$filtered = $item->getRelation('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

@ -5,7 +5,6 @@ namespace Pterodactyl\Providers;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use Pterodactyl\Repositories\Eloquent\EggRepository; use Pterodactyl\Repositories\Eloquent\EggRepository;
use Pterodactyl\Repositories\Eloquent\NestRepository; use Pterodactyl\Repositories\Eloquent\NestRepository;
use Pterodactyl\Repositories\Eloquent\NodeRepository;
use Pterodactyl\Repositories\Eloquent\TaskRepository; use Pterodactyl\Repositories\Eloquent\TaskRepository;
use Pterodactyl\Repositories\Eloquent\UserRepository; use Pterodactyl\Repositories\Eloquent\UserRepository;
use Pterodactyl\Repositories\Eloquent\ApiKeyRepository; use Pterodactyl\Repositories\Eloquent\ApiKeyRepository;
@ -20,7 +19,6 @@ use Pterodactyl\Repositories\Eloquent\AllocationRepository;
use Pterodactyl\Contracts\Repository\EggRepositoryInterface; use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
use Pterodactyl\Repositories\Eloquent\EggVariableRepository; use Pterodactyl\Repositories\Eloquent\EggVariableRepository;
use Pterodactyl\Contracts\Repository\NestRepositoryInterface; use Pterodactyl\Contracts\Repository\NestRepositoryInterface;
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
use Pterodactyl\Contracts\Repository\TaskRepositoryInterface; use Pterodactyl\Contracts\Repository\TaskRepositoryInterface;
use Pterodactyl\Contracts\Repository\UserRepositoryInterface; use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository; use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository;
@ -54,7 +52,6 @@ class RepositoryServiceProvider extends ServiceProvider
$this->app->bind(EggVariableRepositoryInterface::class, EggVariableRepository::class); $this->app->bind(EggVariableRepositoryInterface::class, EggVariableRepository::class);
$this->app->bind(LocationRepositoryInterface::class, LocationRepository::class); $this->app->bind(LocationRepositoryInterface::class, LocationRepository::class);
$this->app->bind(NestRepositoryInterface::class, NestRepository::class); $this->app->bind(NestRepositoryInterface::class, NestRepository::class);
$this->app->bind(NodeRepositoryInterface::class, NodeRepository::class);
$this->app->bind(ScheduleRepositoryInterface::class, ScheduleRepository::class); $this->app->bind(ScheduleRepositoryInterface::class, ScheduleRepository::class);
$this->app->bind(ServerRepositoryInterface::class, ServerRepository::class); $this->app->bind(ServerRepositoryInterface::class, ServerRepository::class);
$this->app->bind(ServerVariableRepositoryInterface::class, ServerVariableRepository::class); $this->app->bind(ServerVariableRepositoryInterface::class, ServerVariableRepository::class);

View file

@ -3,10 +3,8 @@
namespace Pterodactyl\Repositories\Eloquent; namespace Pterodactyl\Repositories\Eloquent;
use Pterodactyl\Models\Node; use Pterodactyl\Models\Node;
use Illuminate\Support\Collection;
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
class NodeRepository extends EloquentRepository implements NodeRepositoryInterface class NodeRepository
{ {
/** /**
* Return the model backing this repository. * Return the model backing this repository.
@ -15,138 +13,4 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa
{ {
return Node::class; return Node::class;
} }
/**
* Return the usage stats for a single node.
*/
public function getUsageStats(Node $node): array
{
$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 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;
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 the usage stats for a single node.
*/
public function getUsageStatsRaw(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();
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 [
$key => [
'value' => $value,
'max' => $maxUsage,
],
];
})->toArray();
}
/**
* Return a single node with location and server information.
*/
public function loadLocationAndServerCount(Node $node, bool $refresh = false): Node
{
if (!$node->relationLoaded('location') || $refresh) {
$node->load('location');
}
// This is quite ugly and can probably be improved down the road.
// And by probably, I mean it should.
if (is_null($node->servers_count) || $refresh) {
$node->load('servers');
$node->setRelation('servers_count', count($node->getRelation('servers')));
unset($node->servers);
}
return $node;
}
/**
* Attach a paginated set of allocations to a node mode including
* any servers that are also attached to those allocations.
*/
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')
->with('server:id,name')
->paginate(50)
);
return $node;
}
/**
* Return a collection of nodes for all locations to use in server creation UI.
*/
public function getNodesForServerCreation(): Collection
{
return $this->getBuilder()->with('allocations')->get()->map(function (Node $item) {
$filtered = $item->getRelation('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();
}
/**
* Returns a node with the given id with the Node's resource usage.
*/
public function getNodeWithResourceUsage(int $node_id): Node
{
$instance = $this->getBuilder()
->select(['nodes.id', 'nodes.fqdn', 'nodes.scheme', 'nodes.daemon_token', 'nodes.daemonListen', 'nodes.memory', 'nodes.disk', 'nodes.memory_overallocate', 'nodes.disk_overallocate'])
->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk')
->leftJoin('servers', 'servers.node_id', '=', 'nodes.id')
->where('nodes.id', $node_id);
return $instance->first();
}
} }

View file

@ -2,39 +2,26 @@
namespace Pterodactyl\Services\Locations; namespace Pterodactyl\Services\Locations;
use Webmozart\Assert\Assert;
use Pterodactyl\Models\Location; use Pterodactyl\Models\Location;
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
use Pterodactyl\Contracts\Repository\LocationRepositoryInterface;
use Pterodactyl\Exceptions\Service\Location\HasActiveNodesException; use Pterodactyl\Exceptions\Service\Location\HasActiveNodesException;
class LocationDeletionService class LocationDeletionService
{ {
/**
* LocationDeletionService constructor.
*/
public function __construct(
protected LocationRepositoryInterface $repository,
protected NodeRepositoryInterface $nodeRepository
) {
}
/** /**
* Delete an existing location. * Delete an existing location.
* *
* @throws \Pterodactyl\Exceptions\Service\Location\HasActiveNodesException * @throws HasActiveNodesException
*/ */
public function handle(Location|int $location): ?int public function handle(Location|int $location): ?int
{ {
$location = ($location instanceof Location) ? $location->id : $location; /** @var Location $location */
$location = ($location instanceof Location) ? $location : Location::query()->findOrFail($location);
Assert::integerish($location, 'First argument passed to handle must be numeric or an instance of ' . Location::class . ', received %s.'); $count = $location->nodes()->count();
$count = $this->nodeRepository->findCountWhere([['location_id', '=', $location]]);
if ($count > 0) { if ($count > 0) {
throw new HasActiveNodesException(trans('exceptions.locations.has_nodes')); throw new HasActiveNodesException(trans('exceptions.locations.has_nodes'));
} }
return $this->repository->delete($location); return $location->delete();
} }
} }

View file

@ -6,21 +6,19 @@ use Ramsey\Uuid\Uuid;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Pterodactyl\Models\Node; use Pterodactyl\Models\Node;
use Illuminate\Contracts\Encryption\Encrypter; use Illuminate\Contracts\Encryption\Encrypter;
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
class NodeCreationService class NodeCreationService
{ {
/** /**
* NodeCreationService constructor. * NodeCreationService constructor.
*/ */
public function __construct(private Encrypter $encrypter, protected NodeRepositoryInterface $repository) public function __construct(private Encrypter $encrypter)
{ {
} }
/** /**
* Create a new node on the panel. * Create a new node on the panel.
* *
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
*/ */
public function handle(array $data): Node public function handle(array $data): Node
{ {
@ -28,6 +26,9 @@ class NodeCreationService
$data['daemon_token'] = $this->encrypter->encrypt(Str::random(Node::DAEMON_TOKEN_LENGTH)); $data['daemon_token'] = $this->encrypter->encrypt(Str::random(Node::DAEMON_TOKEN_LENGTH));
$data['daemon_token_id'] = Str::random(Node::DAEMON_TOKEN_ID_LENGTH); $data['daemon_token_id'] = Str::random(Node::DAEMON_TOKEN_ID_LENGTH);
return $this->repository->create($data, true, true); /** @var Node $node */
$node = Node::query()->create($data);
return $node;
} }
} }

View file

@ -4,38 +4,33 @@ namespace Pterodactyl\Services\Nodes;
use Pterodactyl\Models\Node; use Pterodactyl\Models\Node;
use Illuminate\Contracts\Translation\Translator; use Illuminate\Contracts\Translation\Translator;
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
use Pterodactyl\Exceptions\Service\HasActiveServersException; use Pterodactyl\Exceptions\Service\HasActiveServersException;
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
class NodeDeletionService class NodeDeletionService
{ {
/** /**
* NodeDeletionService constructor. * NodeDeletionService constructor.
*/ */
public function __construct( public function __construct(protected Translator $translator)
protected NodeRepositoryInterface $repository, {
protected ServerRepositoryInterface $serverRepository,
protected Translator $translator
) {
} }
/** /**
* Delete a node from the panel if no servers are attached to it. * Delete a node from the panel if no servers are attached to it.
* *
* @throws \Pterodactyl\Exceptions\Service\HasActiveServersException * @throws HasActiveServersException
*/ */
public function handle(int|Node $node): int public function handle(int|Node $node): int
{ {
if ($node instanceof Node) { if (is_int($node)) {
$node = $node->id; $node = Node::query()->findOrFail($node);
} }
$servers = $this->serverRepository->setColumns('id')->findCountWhere([['node_id', '=', $node]]); $servers = $node->servers()->count();
if ($servers > 0) { if ($servers > 0) {
throw new HasActiveServersException($this->translator->get('exceptions.node.servers_attached')); throw new HasActiveServersException($this->translator->get('exceptions.node.servers_attached'));
} }
return $this->repository->delete($node); return $node->delete();
} }
} }

View file

@ -7,7 +7,6 @@ use Pterodactyl\Models\Node;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionInterface;
use Illuminate\Contracts\Encryption\Encrypter; use Illuminate\Contracts\Encryption\Encrypter;
use Pterodactyl\Repositories\Eloquent\NodeRepository;
use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository; use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException; use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
use Pterodactyl\Exceptions\Service\Node\ConfigurationNotPersistedException; use Pterodactyl\Exceptions\Service\Node\ConfigurationNotPersistedException;
@ -20,8 +19,7 @@ class NodeUpdateService
public function __construct( public function __construct(
private ConnectionInterface $connection, private ConnectionInterface $connection,
private DaemonConfigurationRepository $configurationRepository, private DaemonConfigurationRepository $configurationRepository,
private Encrypter $encrypter, private Encrypter $encrypter
private NodeRepository $repository
) { ) {
} }
@ -39,7 +37,7 @@ class NodeUpdateService
[$updated, $exception] = $this->connection->transaction(function () use ($data, $node) { [$updated, $exception] = $this->connection->transaction(function () use ($data, $node) {
/** @var \Pterodactyl\Models\Node $updated */ /** @var \Pterodactyl\Models\Node $updated */
$updated = $this->repository->withFreshModel()->update($node->id, $data, true, true); $updated = (clone $node)->update($data);
try { try {
// If we're changing the FQDN for the node, use the newly provided FQDN for the connection // If we're changing the FQDN for the node, use the newly provided FQDN for the connection