Compare commits
4 commits
develop
...
replace-no
Author | SHA1 | Date | |
---|---|---|---|
|
fda8aa07f4 | ||
|
2f5804b2c9 | ||
|
1f93291ddb | ||
|
aeb7590a6d |
15 changed files with 122 additions and 272 deletions
|
@ -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;
|
||||
}
|
|
@ -9,7 +9,6 @@ use Illuminate\Support\Collection;
|
|||
use Pterodactyl\Models\Allocation;
|
||||
use Pterodactyl\Http\Controllers\Controller;
|
||||
use Illuminate\Contracts\View\Factory as ViewFactory;
|
||||
use Pterodactyl\Repositories\Eloquent\NodeRepository;
|
||||
use Pterodactyl\Repositories\Eloquent\ServerRepository;
|
||||
use Pterodactyl\Traits\Controllers\JavascriptInjection;
|
||||
use Pterodactyl\Services\Helpers\SoftwareVersionService;
|
||||
|
@ -20,13 +19,15 @@ class NodeViewController extends Controller
|
|||
{
|
||||
use JavascriptInjection;
|
||||
|
||||
public const THRESHOLD_PERCENTAGE_LOW = 75;
|
||||
public const THRESHOLD_PERCENTAGE_MEDIUM = 90;
|
||||
|
||||
/**
|
||||
* NodeViewController constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private AllocationRepository $allocationRepository,
|
||||
private LocationRepository $locationRepository,
|
||||
private NodeRepository $repository,
|
||||
private ServerRepository $serverRepository,
|
||||
private SoftwareVersionService $versionService,
|
||||
private ViewFactory $view
|
||||
|
@ -38,11 +39,37 @@ class NodeViewController extends Controller
|
|||
*/
|
||||
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', [
|
||||
'node' => $node,
|
||||
'stats' => $this->repository->getUsageStats($node),
|
||||
'stats' => $usageStats,
|
||||
'version' => $this->versionService,
|
||||
]);
|
||||
}
|
||||
|
@ -71,7 +98,15 @@ class NodeViewController extends Controller
|
|||
*/
|
||||
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'])]);
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ use Pterodactyl\Services\Nodes\NodeDeletionService;
|
|||
use Pterodactyl\Services\Allocations\AssignmentService;
|
||||
use Pterodactyl\Services\Helpers\SoftwareVersionService;
|
||||
use Pterodactyl\Http\Requests\Admin\Node\NodeFormRequest;
|
||||
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
|
||||
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
|
||||
use Pterodactyl\Http\Requests\Admin\Node\AllocationFormRequest;
|
||||
use Pterodactyl\Services\Allocations\AllocationDeletionService;
|
||||
|
@ -40,7 +39,6 @@ class NodesController extends Controller
|
|||
protected NodeCreationService $creationService,
|
||||
protected NodeDeletionService $deletionService,
|
||||
protected LocationRepositoryInterface $locationRepository,
|
||||
protected NodeRepositoryInterface $repository,
|
||||
protected ServerRepositoryInterface $serverRepository,
|
||||
protected NodeUpdateService $updateService,
|
||||
protected SoftwareVersionService $versionService,
|
||||
|
|
|
@ -11,7 +11,6 @@ use Prologue\Alerts\AlertsMessageBag;
|
|||
use Illuminate\View\Factory as ViewFactory;
|
||||
use Pterodactyl\Http\Controllers\Controller;
|
||||
use Pterodactyl\Repositories\Eloquent\NestRepository;
|
||||
use Pterodactyl\Repositories\Eloquent\NodeRepository;
|
||||
use Pterodactyl\Http\Requests\Admin\ServerFormRequest;
|
||||
use Pterodactyl\Services\Servers\ServerCreationService;
|
||||
|
||||
|
@ -23,7 +22,6 @@ class CreateServerController extends Controller
|
|||
public function __construct(
|
||||
private AlertsMessageBag $alert,
|
||||
private NestRepository $nestRepository,
|
||||
private NodeRepository $nodeRepository,
|
||||
private ServerCreationService $creationService,
|
||||
private ViewFactory $view
|
||||
) {
|
||||
|
@ -46,7 +44,7 @@ class CreateServerController extends Controller
|
|||
$nests = $this->nestRepository->getWithEggs();
|
||||
|
||||
JavaScript::put([
|
||||
'nodeData' => $this->nodeRepository->getNodesForServerCreation(),
|
||||
'nodeData' => Node::getForServerCreation(),
|
||||
'nests' => $nests->map(function ($item) {
|
||||
return array_merge($item->toArray(), [
|
||||
'eggs' => $item->eggs->keyBy('id')->toArray(),
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
namespace Pterodactyl\Http\Controllers\Admin\Servers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Pterodactyl\Models\Node;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Prologue\Alerts\AlertsMessageBag;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Pterodactyl\Models\ServerTransfer;
|
||||
use Pterodactyl\Http\Controllers\Controller;
|
||||
use Pterodactyl\Services\Servers\TransferService;
|
||||
use Pterodactyl\Repositories\Eloquent\NodeRepository;
|
||||
use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository;
|
||||
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
|
||||
|
||||
|
@ -21,7 +21,6 @@ class ServerTransferController extends Controller
|
|||
public function __construct(
|
||||
private AlertsMessageBag $alert,
|
||||
private AllocationRepositoryInterface $allocationRepository,
|
||||
private NodeRepository $nodeRepository,
|
||||
private TransferService $transferService,
|
||||
private DaemonConfigurationRepository $daemonConfigurationRepository
|
||||
) {
|
||||
|
@ -45,7 +44,13 @@ class ServerTransferController extends Controller
|
|||
$additional_allocations = array_map('intval', $validatedData['allocation_additional'] ?? []);
|
||||
|
||||
// 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)) {
|
||||
// Check if the selected daemon is online.
|
||||
$this->daemonConfigurationRepository->setNode($node)->getSystemInformation();
|
||||
|
|
|
@ -6,13 +6,13 @@ use JavaScript;
|
|||
use Illuminate\View\View;
|
||||
use Illuminate\Http\Request;
|
||||
use Pterodactyl\Models\Nest;
|
||||
use Pterodactyl\Models\Node;
|
||||
use Pterodactyl\Models\Server;
|
||||
use Pterodactyl\Exceptions\DisplayException;
|
||||
use Pterodactyl\Http\Controllers\Controller;
|
||||
use Pterodactyl\Services\Servers\EnvironmentService;
|
||||
use Illuminate\Contracts\View\Factory as ViewFactory;
|
||||
use Pterodactyl\Repositories\Eloquent\NestRepository;
|
||||
use Pterodactyl\Repositories\Eloquent\NodeRepository;
|
||||
use Pterodactyl\Repositories\Eloquent\MountRepository;
|
||||
use Pterodactyl\Repositories\Eloquent\ServerRepository;
|
||||
use Pterodactyl\Traits\Controllers\JavascriptInjection;
|
||||
|
@ -31,7 +31,6 @@ class ServerViewController extends Controller
|
|||
private LocationRepository $locationRepository,
|
||||
private MountRepository $mountRepository,
|
||||
private NestRepository $nestRepository,
|
||||
private NodeRepository $nodeRepository,
|
||||
private ServerRepository $repository,
|
||||
private EnvironmentService $environmentService,
|
||||
private ViewFactory $view
|
||||
|
@ -128,14 +127,14 @@ class ServerViewController extends Controller
|
|||
}
|
||||
|
||||
// Check if the panel doesn't have at least 2 nodes configured.
|
||||
$nodes = $this->nodeRepository->all();
|
||||
$nodeCount = Node::query()->count();
|
||||
$canTransfer = false;
|
||||
if (count($nodes) >= 2) {
|
||||
if ($nodeCount >= 2) {
|
||||
$canTransfer = true;
|
||||
}
|
||||
|
||||
JavaScript::put([
|
||||
'nodeData' => $this->nodeRepository->getNodesForServerCreation(),
|
||||
'nodeData' => Node::getForServerCreation(),
|
||||
]);
|
||||
|
||||
return $this->view->make('admin.servers.view.manage', [
|
||||
|
|
|
@ -4,10 +4,9 @@ namespace Pterodactyl\Http\Middleware\Api\Daemon;
|
|||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Pterodactyl\Models\Node;
|
||||
use Illuminate\Contracts\Encryption\Encrypter;
|
||||
use Pterodactyl\Repositories\Eloquent\NodeRepository;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
|
||||
|
@ -23,14 +22,14 @@ class DaemonAuthenticate
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
|
||||
* @throws HttpException
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): mixed
|
||||
{
|
||||
|
@ -43,24 +42,18 @@ class DaemonAuthenticate
|
|||
}
|
||||
|
||||
$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])) {
|
||||
throw new BadRequestHttpException('The Authorization header provided was not in a valid format.');
|
||||
}
|
||||
|
||||
try {
|
||||
/** @var \Pterodactyl\Models\Node $node */
|
||||
$node = $this->repository->findFirstWhere([
|
||||
'daemon_token_id' => $parts[0],
|
||||
]);
|
||||
/** @var Node $node */
|
||||
$node = Node::query()->where('daemon_token_id', $parts[0])->firstOrFail();
|
||||
|
||||
if (hash_equals((string) $this->encrypter->decrypt($node->daemon_token), $parts[1])) {
|
||||
$request->attributes->set('node', $node);
|
||||
if (hash_equals((string) $this->encrypter->decrypt($node->daemon_token), $parts[1])) {
|
||||
$request->attributes->set('node', $node);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
} catch (RecordNotFoundException $exception) {
|
||||
// Do nothing, we don't want to expose a node not existing at all.
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
throw new AccessDeniedHttpException('You are not authorized to access this resource.');
|
||||
|
|
|
@ -225,4 +225,26 @@ class Node extends Model
|
|||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ namespace Pterodactyl\Providers;
|
|||
use Illuminate\Support\ServiceProvider;
|
||||
use Pterodactyl\Repositories\Eloquent\EggRepository;
|
||||
use Pterodactyl\Repositories\Eloquent\NestRepository;
|
||||
use Pterodactyl\Repositories\Eloquent\NodeRepository;
|
||||
use Pterodactyl\Repositories\Eloquent\TaskRepository;
|
||||
use Pterodactyl\Repositories\Eloquent\UserRepository;
|
||||
use Pterodactyl\Repositories\Eloquent\ApiKeyRepository;
|
||||
|
@ -20,7 +19,6 @@ use Pterodactyl\Repositories\Eloquent\AllocationRepository;
|
|||
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
|
||||
use Pterodactyl\Repositories\Eloquent\EggVariableRepository;
|
||||
use Pterodactyl\Contracts\Repository\NestRepositoryInterface;
|
||||
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
|
||||
use Pterodactyl\Contracts\Repository\TaskRepositoryInterface;
|
||||
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
|
||||
use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository;
|
||||
|
@ -54,7 +52,6 @@ class RepositoryServiceProvider extends ServiceProvider
|
|||
$this->app->bind(EggVariableRepositoryInterface::class, EggVariableRepository::class);
|
||||
$this->app->bind(LocationRepositoryInterface::class, LocationRepository::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(ServerRepositoryInterface::class, ServerRepository::class);
|
||||
$this->app->bind(ServerVariableRepositoryInterface::class, ServerVariableRepository::class);
|
||||
|
|
|
@ -3,10 +3,8 @@
|
|||
namespace Pterodactyl\Repositories\Eloquent;
|
||||
|
||||
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.
|
||||
|
@ -15,138 +13,4 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa
|
|||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,39 +2,26 @@
|
|||
|
||||
namespace Pterodactyl\Services\Locations;
|
||||
|
||||
use Webmozart\Assert\Assert;
|
||||
use Pterodactyl\Models\Location;
|
||||
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
|
||||
use Pterodactyl\Contracts\Repository\LocationRepositoryInterface;
|
||||
use Pterodactyl\Exceptions\Service\Location\HasActiveNodesException;
|
||||
|
||||
class LocationDeletionService
|
||||
{
|
||||
/**
|
||||
* LocationDeletionService constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected LocationRepositoryInterface $repository,
|
||||
protected NodeRepositoryInterface $nodeRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an existing location.
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Service\Location\HasActiveNodesException
|
||||
* @throws HasActiveNodesException
|
||||
*/
|
||||
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 = $this->nodeRepository->findCountWhere([['location_id', '=', $location]]);
|
||||
$count = $location->nodes()->count();
|
||||
if ($count > 0) {
|
||||
throw new HasActiveNodesException(trans('exceptions.locations.has_nodes'));
|
||||
}
|
||||
|
||||
return $this->repository->delete($location);
|
||||
return $location->delete();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,21 +6,19 @@ use Ramsey\Uuid\Uuid;
|
|||
use Illuminate\Support\Str;
|
||||
use Pterodactyl\Models\Node;
|
||||
use Illuminate\Contracts\Encryption\Encrypter;
|
||||
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
|
||||
|
||||
class NodeCreationService
|
||||
{
|
||||
/**
|
||||
* NodeCreationService constructor.
|
||||
*/
|
||||
public function __construct(private Encrypter $encrypter, protected NodeRepositoryInterface $repository)
|
||||
public function __construct(private Encrypter $encrypter)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new node on the panel.
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||
*/
|
||||
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_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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,38 +4,33 @@ namespace Pterodactyl\Services\Nodes;
|
|||
|
||||
use Pterodactyl\Models\Node;
|
||||
use Illuminate\Contracts\Translation\Translator;
|
||||
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
|
||||
use Pterodactyl\Exceptions\Service\HasActiveServersException;
|
||||
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
|
||||
|
||||
class NodeDeletionService
|
||||
{
|
||||
/**
|
||||
* NodeDeletionService constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
protected NodeRepositoryInterface $repository,
|
||||
protected ServerRepositoryInterface $serverRepository,
|
||||
protected Translator $translator
|
||||
) {
|
||||
public function __construct(protected Translator $translator)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
if ($node instanceof Node) {
|
||||
$node = $node->id;
|
||||
if (is_int($node)) {
|
||||
$node = Node::query()->findOrFail($node);
|
||||
}
|
||||
|
||||
$servers = $this->serverRepository->setColumns('id')->findCountWhere([['node_id', '=', $node]]);
|
||||
$servers = $node->servers()->count();
|
||||
if ($servers > 0) {
|
||||
throw new HasActiveServersException($this->translator->get('exceptions.node.servers_attached'));
|
||||
}
|
||||
|
||||
return $this->repository->delete($node);
|
||||
return $node->delete();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ use Pterodactyl\Models\Node;
|
|||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Illuminate\Contracts\Encryption\Encrypter;
|
||||
use Pterodactyl\Repositories\Eloquent\NodeRepository;
|
||||
use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository;
|
||||
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
|
||||
use Pterodactyl\Exceptions\Service\Node\ConfigurationNotPersistedException;
|
||||
|
@ -20,8 +19,7 @@ class NodeUpdateService
|
|||
public function __construct(
|
||||
private ConnectionInterface $connection,
|
||||
private DaemonConfigurationRepository $configurationRepository,
|
||||
private Encrypter $encrypter,
|
||||
private NodeRepository $repository
|
||||
private Encrypter $encrypter
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -39,7 +37,7 @@ class NodeUpdateService
|
|||
|
||||
[$updated, $exception] = $this->connection->transaction(function () use ($data, $node) {
|
||||
/** @var \Pterodactyl\Models\Node $updated */
|
||||
$updated = $this->repository->withFreshModel()->update($node->id, $data, true, true);
|
||||
$updated = (clone $node)->update($data);
|
||||
|
||||
try {
|
||||
// If we're changing the FQDN for the node, use the newly provided FQDN for the connection
|
||||
|
|
|
@ -5,10 +5,10 @@ namespace Pterodactyl\Tests\Unit\Http\Middleware\Api\Daemon;
|
|||
use Mockery as m;
|
||||
use Mockery\MockInterface;
|
||||
use Pterodactyl\Models\Node;
|
||||
use Pterodactyl\Models\Location;
|
||||
use Illuminate\Contracts\Encryption\Encrypter;
|
||||
use Pterodactyl\Repositories\Eloquent\NodeRepository;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
|
||||
use Pterodactyl\Http\Middleware\Api\Daemon\DaemonAuthenticate;
|
||||
use Pterodactyl\Tests\Unit\Http\Middleware\MiddlewareTestCase;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
|
@ -18,8 +18,6 @@ class DaemonAuthenticateTest extends MiddlewareTestCase
|
|||
{
|
||||
private MockInterface $encrypter;
|
||||
|
||||
private MockInterface $repository;
|
||||
|
||||
/**
|
||||
* Setup tests.
|
||||
*/
|
||||
|
@ -28,7 +26,6 @@ class DaemonAuthenticateTest extends MiddlewareTestCase
|
|||
parent::setUp();
|
||||
|
||||
$this->encrypter = m::mock(Encrypter::class);
|
||||
$this->repository = m::mock(NodeRepository::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -83,16 +80,16 @@ class DaemonAuthenticateTest extends MiddlewareTestCase
|
|||
*/
|
||||
public function testResponseShouldFailIfTokenIsNotValid()
|
||||
{
|
||||
$node = Node::factory()
|
||||
->for(Location::factory())
|
||||
->create();
|
||||
|
||||
$this->expectException(AccessDeniedHttpException::class);
|
||||
|
||||
/** @var \Pterodactyl\Models\Node $model */
|
||||
$model = Node::factory()->make();
|
||||
|
||||
$this->request->expects('route->getName')->withNoArgs()->andReturn('random.route');
|
||||
$this->request->expects('bearerToken')->withNoArgs()->andReturn($model->daemon_token_id . '.random_string_123');
|
||||
$this->request->expects('bearerToken')->withNoArgs()->andReturn($node->daemon_token_id . '.random_string_123');
|
||||
|
||||
$this->repository->expects('findFirstWhere')->with(['daemon_token_id' => $model->daemon_token_id])->andReturn($model);
|
||||
$this->encrypter->expects('decrypt')->with($model->daemon_token)->andReturns(decrypt($model->daemon_token));
|
||||
$this->encrypter->expects('decrypt')->with($node->daemon_token)->andReturns(decrypt($node->daemon_token));
|
||||
|
||||
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
|
||||
}
|
||||
|
@ -103,13 +100,11 @@ class DaemonAuthenticateTest extends MiddlewareTestCase
|
|||
*/
|
||||
public function testResponseShouldFailIfNodeIsNotFound()
|
||||
{
|
||||
$this->expectException(AccessDeniedHttpException::class);
|
||||
$this->expectException(ModelNotFoundException::class);
|
||||
|
||||
$this->request->expects('route->getName')->withNoArgs()->andReturn('random.route');
|
||||
$this->request->expects('bearerToken')->withNoArgs()->andReturn('abcd1234.random_string_123');
|
||||
|
||||
$this->repository->expects('findFirstWhere')->with(['daemon_token_id' => 'abcd1234'])->andThrow(RecordNotFoundException::class);
|
||||
|
||||
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
|
||||
}
|
||||
|
||||
|
@ -118,18 +113,19 @@ class DaemonAuthenticateTest extends MiddlewareTestCase
|
|||
*/
|
||||
public function testSuccessfulMiddlewareProcess()
|
||||
{
|
||||
/** @var \Pterodactyl\Models\Node $model */
|
||||
$model = Node::factory()->make();
|
||||
$node = Node::factory()
|
||||
->for(Location::factory())
|
||||
->create();
|
||||
$node->daemon_token = encrypt('the_same');
|
||||
$node->save();
|
||||
|
||||
$this->request->expects('route->getName')->withNoArgs()->andReturn('random.route');
|
||||
$this->request->expects('bearerToken')->withNoArgs()->andReturn($model->daemon_token_id . '.' . decrypt($model->daemon_token));
|
||||
|
||||
$this->repository->expects('findFirstWhere')->with(['daemon_token_id' => $model->daemon_token_id])->andReturn($model);
|
||||
$this->encrypter->expects('decrypt')->with($model->daemon_token)->andReturns(decrypt($model->daemon_token));
|
||||
$this->request->expects('bearerToken')->withNoArgs()->andReturn($node->daemon_token_id . '.the_same');
|
||||
$this->encrypter->expects('decrypt')->with($node->daemon_token)->andReturns(decrypt($node->daemon_token));
|
||||
|
||||
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
|
||||
$this->assertRequestHasAttribute('node');
|
||||
$this->assertRequestAttributeEquals($model, 'node');
|
||||
$this->assertRequestAttributeEquals($node->fresh(), 'node');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -156,6 +152,6 @@ class DaemonAuthenticateTest extends MiddlewareTestCase
|
|||
*/
|
||||
private function getMiddleware(): DaemonAuthenticate
|
||||
{
|
||||
return new DaemonAuthenticate($this->encrypter, $this->repository);
|
||||
return new DaemonAuthenticate($this->encrypter);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue