mirror of
				https://github.com/pelican-dev/panel.git
				synced 2025-11-04 15:36:52 +01:00 
			
		
		
		
	Merge branch 'develop' into feature/cross-env
This commit is contained in:
		
						commit
						0a53e75fd1
					
				
							
								
								
									
										1
									
								
								.php_cs
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								.php_cs
									
									
									
									
									
								
							@ -33,6 +33,7 @@ return PhpCsFixer\Config::create()
 | 
				
			|||||||
        'new_with_braces' => false,
 | 
					        'new_with_braces' => false,
 | 
				
			||||||
        'no_alias_functions' => true,
 | 
					        'no_alias_functions' => true,
 | 
				
			||||||
        'no_multiline_whitespace_before_semicolons' => true,
 | 
					        'no_multiline_whitespace_before_semicolons' => true,
 | 
				
			||||||
 | 
					        'no_superfluous_phpdoc_tags' => false,
 | 
				
			||||||
        'no_unreachable_default_argument_value' => true,
 | 
					        'no_unreachable_default_argument_value' => true,
 | 
				
			||||||
        'no_useless_return' => true,
 | 
					        'no_useless_return' => true,
 | 
				
			||||||
        'not_operator_with_successor_space' => true,
 | 
					        'not_operator_with_successor_space' => true,
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										177
									
								
								app/Http/Controllers/Admin/Servers/ServerTransferController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								app/Http/Controllers/Admin/Servers/ServerTransferController.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,177 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pterodactyl\Http\Controllers\Admin\Servers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Illuminate\Http\Request;
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Server;
 | 
				
			||||||
 | 
					use Prologue\Alerts\AlertsMessageBag;
 | 
				
			||||||
 | 
					use Pterodactyl\Models\ServerTransfer;
 | 
				
			||||||
 | 
					use Pterodactyl\Http\Controllers\Controller;
 | 
				
			||||||
 | 
					use Pterodactyl\Services\Servers\TransferService;
 | 
				
			||||||
 | 
					use Pterodactyl\Services\Servers\SuspensionService;
 | 
				
			||||||
 | 
					use Pterodactyl\Repositories\Eloquent\NodeRepository;
 | 
				
			||||||
 | 
					use Pterodactyl\Repositories\Eloquent\ServerRepository;
 | 
				
			||||||
 | 
					use Pterodactyl\Repositories\Eloquent\LocationRepository;
 | 
				
			||||||
 | 
					use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository;
 | 
				
			||||||
 | 
					use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ServerTransferController extends Controller
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Prologue\Alerts\AlertsMessageBag
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $alert;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $allocationRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Repositories\Eloquent\ServerRepository
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $repository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Repositories\Eloquent\LocationRepository
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $locationRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Repositories\Eloquent\NodeRepository
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $nodeRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Services\Servers\SuspensionService
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $suspensionService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Services\Servers\TransferService
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $transferService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Repositories\Wings\DaemonConfigurationRepository
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $daemonConfigurationRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * ServerTransferController constructor.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Prologue\Alerts\AlertsMessageBag $alert
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Repositories\Eloquent\LocationRepository $locationRepository
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Repositories\Eloquent\NodeRepository $nodeRepository
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Services\Servers\SuspensionService $suspensionService
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Services\Servers\TransferService $transferService
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Repositories\Wings\DaemonConfigurationRepository $daemonConfigurationRepository
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        AlertsMessageBag $alert,
 | 
				
			||||||
 | 
					        AllocationRepositoryInterface $allocationRepository,
 | 
				
			||||||
 | 
					        ServerRepository $repository,
 | 
				
			||||||
 | 
					        LocationRepository $locationRepository,
 | 
				
			||||||
 | 
					        NodeRepository $nodeRepository,
 | 
				
			||||||
 | 
					        SuspensionService $suspensionService,
 | 
				
			||||||
 | 
					        TransferService $transferService,
 | 
				
			||||||
 | 
					        DaemonConfigurationRepository $daemonConfigurationRepository
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        $this->alert = $alert;
 | 
				
			||||||
 | 
					        $this->allocationRepository = $allocationRepository;
 | 
				
			||||||
 | 
					        $this->repository = $repository;
 | 
				
			||||||
 | 
					        $this->locationRepository = $locationRepository;
 | 
				
			||||||
 | 
					        $this->nodeRepository = $nodeRepository;
 | 
				
			||||||
 | 
					        $this->suspensionService = $suspensionService;
 | 
				
			||||||
 | 
					        $this->transferService = $transferService;
 | 
				
			||||||
 | 
					        $this->daemonConfigurationRepository = $daemonConfigurationRepository;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Starts a transfer of a server to a new node.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Illuminate\Http\Request $request
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Models\Server $server
 | 
				
			||||||
 | 
					     * @return \Illuminate\Http\RedirectResponse
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @throws \Throwable
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function transfer(Request $request, Server $server)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $validatedData = $request->validate([
 | 
				
			||||||
 | 
					            'node_id' => 'required|exists:nodes,id',
 | 
				
			||||||
 | 
					            'allocation_id' => 'required|bail|unique:servers|exists:allocations,id',
 | 
				
			||||||
 | 
					            'allocation_additional' => 'nullable',
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $node_id = $validatedData['node_id'];
 | 
				
			||||||
 | 
					        $allocation_id = intval($validatedData['allocation_id']);
 | 
				
			||||||
 | 
					        $additional_allocations = array_map('intval', $validatedData['allocation_additional'] ?? []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Check if the node is viable for the transfer.
 | 
				
			||||||
 | 
					        $node = $this->nodeRepository->getNodeWithResourceUsage($node_id);
 | 
				
			||||||
 | 
					        if ($node->isViable($server->memory, $server->disk)) {
 | 
				
			||||||
 | 
					            // Check if the selected daemon is online.
 | 
				
			||||||
 | 
					            $this->daemonConfigurationRepository->setNode($node)->getSystemInformation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Suspend the server and request an archive to be created.
 | 
				
			||||||
 | 
					            $this->suspensionService->toggle($server, 'suspend');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Create a new ServerTransfer entry.
 | 
				
			||||||
 | 
					            $transfer = new ServerTransfer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $transfer->server_id = $server->id;
 | 
				
			||||||
 | 
					            $transfer->old_node = $server->node_id;
 | 
				
			||||||
 | 
					            $transfer->new_node = $node_id;
 | 
				
			||||||
 | 
					            $transfer->old_allocation = $server->allocation_id;
 | 
				
			||||||
 | 
					            $transfer->new_allocation = $allocation_id;
 | 
				
			||||||
 | 
					            $transfer->old_additional_allocations = json_encode($server->allocations->where('id', '!=', $server->allocation_id)->pluck('id'));
 | 
				
			||||||
 | 
					            $transfer->new_additional_allocations = json_encode($additional_allocations);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $transfer->save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Add the allocations to the server so they cannot be automatically assigned while the transfer is in progress.
 | 
				
			||||||
 | 
					            $this->assignAllocationsToServer($server, $node_id, $allocation_id, $additional_allocations);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Request an archive from the server's current daemon. (this also checks if the daemon is online)
 | 
				
			||||||
 | 
					            $this->transferService->requestArchive($server);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $this->alert->success(trans('admin/server.alerts.transfer_started'))->flash();
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            $this->alert->danger(trans('admin/server.alerts.transfer_not_viable'))->flash();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return redirect()->route('admin.servers.view.manage', $server->id);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Assigns the specified allocations to the specified server.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param Server $server
 | 
				
			||||||
 | 
					     * @param int $node_id
 | 
				
			||||||
 | 
					     * @param int $allocation_id
 | 
				
			||||||
 | 
					     * @param array $additional_allocations
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private function assignAllocationsToServer(Server $server, int $node_id, int $allocation_id, array $additional_allocations)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $allocations = $additional_allocations;
 | 
				
			||||||
 | 
					        array_push($allocations, $allocation_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $unassigned = $this->allocationRepository->getUnassignedAllocationIds($node_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $updateIds = [];
 | 
				
			||||||
 | 
					        foreach ($allocations as $allocation) {
 | 
				
			||||||
 | 
					            if (! in_array($allocation, $unassigned)) {
 | 
				
			||||||
 | 
					                continue;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $updateIds[] = $allocation;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (! empty($updateIds)) {
 | 
				
			||||||
 | 
					            $this->allocationRepository->updateWhereIn('id', $updateIds, ['server_id' => $server->id]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace Pterodactyl\Http\Controllers\Admin\Servers;
 | 
					namespace Pterodactyl\Http\Controllers\Admin\Servers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use JavaScript;
 | 
				
			||||||
use Illuminate\Http\Request;
 | 
					use Illuminate\Http\Request;
 | 
				
			||||||
use Pterodactyl\Models\Nest;
 | 
					use Pterodactyl\Models\Nest;
 | 
				
			||||||
use Pterodactyl\Models\Server;
 | 
					use Pterodactyl\Models\Server;
 | 
				
			||||||
@ -9,8 +10,10 @@ use Illuminate\Contracts\View\Factory;
 | 
				
			|||||||
use Pterodactyl\Exceptions\DisplayException;
 | 
					use Pterodactyl\Exceptions\DisplayException;
 | 
				
			||||||
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\Repositories\Eloquent\ServerRepository;
 | 
					use Pterodactyl\Repositories\Eloquent\ServerRepository;
 | 
				
			||||||
use Pterodactyl\Traits\Controllers\JavascriptInjection;
 | 
					use Pterodactyl\Traits\Controllers\JavascriptInjection;
 | 
				
			||||||
 | 
					use Pterodactyl\Repositories\Eloquent\LocationRepository;
 | 
				
			||||||
use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository;
 | 
					use Pterodactyl\Repositories\Eloquent\DatabaseHostRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ServerViewController extends Controller
 | 
					class ServerViewController extends Controller
 | 
				
			||||||
@ -37,17 +40,31 @@ class ServerViewController extends Controller
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    private $nestRepository;
 | 
					    private $nestRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Repositories\Eloquent\LocationRepository
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $locationRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Repositories\Eloquent\NodeRepository
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $nodeRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * ServerViewController constructor.
 | 
					     * ServerViewController constructor.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param \Pterodactyl\Repositories\Eloquent\DatabaseHostRepository $databaseHostRepository
 | 
					     * @param \Pterodactyl\Repositories\Eloquent\DatabaseHostRepository $databaseHostRepository
 | 
				
			||||||
     * @param \Pterodactyl\Repositories\Eloquent\NestRepository $nestRepository
 | 
					     * @param \Pterodactyl\Repositories\Eloquent\NestRepository $nestRepository
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Repositories\Eloquent\LocationRepository $locationRepository
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Repositories\Eloquent\NodeRepository $nodeRepository
 | 
				
			||||||
     * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository
 | 
					     * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository
 | 
				
			||||||
     * @param \Illuminate\Contracts\View\Factory $view
 | 
					     * @param \Illuminate\Contracts\View\Factory $view
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function __construct(
 | 
					    public function __construct(
 | 
				
			||||||
        DatabaseHostRepository $databaseHostRepository,
 | 
					        DatabaseHostRepository $databaseHostRepository,
 | 
				
			||||||
        NestRepository $nestRepository,
 | 
					        NestRepository $nestRepository,
 | 
				
			||||||
 | 
					        LocationRepository $locationRepository,
 | 
				
			||||||
 | 
					        NodeRepository $nodeRepository,
 | 
				
			||||||
        ServerRepository $repository,
 | 
					        ServerRepository $repository,
 | 
				
			||||||
        Factory $view
 | 
					        Factory $view
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
@ -55,6 +72,8 @@ class ServerViewController extends Controller
 | 
				
			|||||||
        $this->databaseHostRepository = $databaseHostRepository;
 | 
					        $this->databaseHostRepository = $databaseHostRepository;
 | 
				
			||||||
        $this->repository = $repository;
 | 
					        $this->repository = $repository;
 | 
				
			||||||
        $this->nestRepository = $nestRepository;
 | 
					        $this->nestRepository = $nestRepository;
 | 
				
			||||||
 | 
					        $this->nodeRepository = $nodeRepository;
 | 
				
			||||||
 | 
					        $this->locationRepository = $locationRepository;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -150,6 +169,7 @@ class ServerViewController extends Controller
 | 
				
			|||||||
     * @return \Illuminate\Contracts\View\View
 | 
					     * @return \Illuminate\Contracts\View\View
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @throws \Pterodactyl\Exceptions\DisplayException
 | 
					     * @throws \Pterodactyl\Exceptions\DisplayException
 | 
				
			||||||
 | 
					     * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function manage(Request $request, Server $server)
 | 
					    public function manage(Request $request, Server $server)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@ -159,7 +179,22 @@ class ServerViewController extends Controller
 | 
				
			|||||||
            );
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $this->view->make('admin.servers.view.manage', compact('server'));
 | 
					        // Check if the panel doesn't have at least 2 nodes configured.
 | 
				
			||||||
 | 
					        $nodes = $this->nodeRepository->all();
 | 
				
			||||||
 | 
					        $canTransfer = false;
 | 
				
			||||||
 | 
					        if (count($nodes) >= 2) {
 | 
				
			||||||
 | 
					            $canTransfer = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Javascript::put([
 | 
				
			||||||
 | 
					            'nodeData' => $this->nodeRepository->getNodesForServerCreation(),
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $this->view->make('admin.servers.view.manage', [
 | 
				
			||||||
 | 
					            'server' => $server,
 | 
				
			||||||
 | 
					            'locations' => $this->locationRepository->all(),
 | 
				
			||||||
 | 
					            'canTransfer' => $canTransfer,
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 | 
				
			|||||||
@ -262,7 +262,7 @@ class ServersController extends Controller
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->buildModificationService->handle($server, $request->only([
 | 
					        $this->buildModificationService->handle($server, $request->only([
 | 
				
			||||||
            'allocation_id', 'add_allocations', 'remove_allocations',
 | 
					            'allocation_id', 'add_allocations', 'remove_allocations',
 | 
				
			||||||
            'memory', 'swap', 'io', 'cpu', 'disk',
 | 
					            'memory', 'swap', 'io', 'cpu', 'threads', 'disk',
 | 
				
			||||||
            'database_limit', 'allocation_limit', 'oom_disabled',
 | 
					            'database_limit', 'allocation_limit', 'oom_disabled',
 | 
				
			||||||
        ]));
 | 
					        ]));
 | 
				
			||||||
        $this->alert->success(trans('admin/server.alerts.build_updated'))->flash();
 | 
					        $this->alert->success(trans('admin/server.alerts.build_updated'))->flash();
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,6 @@
 | 
				
			|||||||
namespace Pterodactyl\Http\Controllers\Api\Client;
 | 
					namespace Pterodactyl\Http\Controllers\Api\Client;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use Pterodactyl\Models\User;
 | 
					use Pterodactyl\Models\User;
 | 
				
			||||||
use Illuminate\Support\Collection;
 | 
					 | 
				
			||||||
use Pterodactyl\Models\Permission;
 | 
					use Pterodactyl\Models\Permission;
 | 
				
			||||||
use Pterodactyl\Repositories\Eloquent\ServerRepository;
 | 
					use Pterodactyl\Repositories\Eloquent\ServerRepository;
 | 
				
			||||||
use Pterodactyl\Transformers\Api\Client\ServerTransformer;
 | 
					use Pterodactyl\Transformers\Api\Client\ServerTransformer;
 | 
				
			||||||
@ -72,16 +71,10 @@ class ClientController extends ClientApiController
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    public function permissions()
 | 
					    public function permissions()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $permissions = Permission::permissions()->map(function ($values, $key) {
 | 
					 | 
				
			||||||
            return Collection::make($values)->map(function ($permission) use ($key) {
 | 
					 | 
				
			||||||
                return $key . '.' . $permission;
 | 
					 | 
				
			||||||
            })->values()->toArray();
 | 
					 | 
				
			||||||
        })->flatten();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return [
 | 
					        return [
 | 
				
			||||||
            'object' => 'system_permissions',
 | 
					            'object' => 'system_permissions',
 | 
				
			||||||
            'attributes' => [
 | 
					            'attributes' => [
 | 
				
			||||||
                'permissions' => $permissions,
 | 
					                'permissions' => Permission::permissions(),
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										77
									
								
								app/Http/Controllers/Api/Client/Servers/BackupController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								app/Http/Controllers/Api/Client/Servers/BackupController.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,77 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Server;
 | 
				
			||||||
 | 
					use Pterodactyl\Services\Backups\InitiateBackupService;
 | 
				
			||||||
 | 
					use Pterodactyl\Transformers\Api\Client\BackupTransformer;
 | 
				
			||||||
 | 
					use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
 | 
				
			||||||
 | 
					use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\GetBackupsRequest;
 | 
				
			||||||
 | 
					use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\StoreBackupRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BackupController extends ClientApiController
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Services\Backups\InitiateBackupService
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $initiateBackupService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * BackupController constructor.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Services\Backups\InitiateBackupService $initiateBackupService
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function __construct(InitiateBackupService $initiateBackupService)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        parent::__construct();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->initiateBackupService = $initiateBackupService;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Returns all of the backups for a given server instance in a paginated
 | 
				
			||||||
 | 
					     * result set.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Backups\GetBackupsRequest $request
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Models\Server $server
 | 
				
			||||||
 | 
					     * @return array
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function index(GetBackupsRequest $request, Server $server)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->fractal->collection($server->backups()->paginate(20))
 | 
				
			||||||
 | 
					            ->transformWith($this->getTransformer(BackupTransformer::class))
 | 
				
			||||||
 | 
					            ->toArray();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Starts the backup process for a server.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Backups\StoreBackupRequest $request
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Models\Server $server
 | 
				
			||||||
 | 
					     * @return array
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @throws \Exception
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function store(StoreBackupRequest $request, Server $server)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $backup = $this->initiateBackupService
 | 
				
			||||||
 | 
					            ->setIgnoredFiles($request->input('ignored'))
 | 
				
			||||||
 | 
					            ->handle($server, $request->input('name'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $this->fractal->item($backup)
 | 
				
			||||||
 | 
					            ->transformWith($this->getTransformer(BackupTransformer::class))
 | 
				
			||||||
 | 
					            ->toArray();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function view()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function update()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function delete()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,80 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Lcobucci\JWT\Builder;
 | 
				
			||||||
 | 
					use Carbon\CarbonImmutable;
 | 
				
			||||||
 | 
					use Illuminate\Support\Str;
 | 
				
			||||||
 | 
					use Lcobucci\JWT\Signer\Key;
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Backup;
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Server;
 | 
				
			||||||
 | 
					use Lcobucci\JWT\Signer\Hmac\Sha256;
 | 
				
			||||||
 | 
					use Illuminate\Http\RedirectResponse;
 | 
				
			||||||
 | 
					use Illuminate\Contracts\Routing\ResponseFactory;
 | 
				
			||||||
 | 
					use Pterodactyl\Repositories\Wings\DaemonBackupRepository;
 | 
				
			||||||
 | 
					use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
 | 
				
			||||||
 | 
					use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\DownloadBackupRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DownloadBackupController extends ClientApiController
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Repositories\Wings\DaemonBackupRepository
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $daemonBackupRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Illuminate\Contracts\Routing\ResponseFactory
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $responseFactory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * DownloadBackupController constructor.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Repositories\Wings\DaemonBackupRepository $daemonBackupRepository
 | 
				
			||||||
 | 
					     * @param \Illuminate\Contracts\Routing\ResponseFactory $responseFactory
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        DaemonBackupRepository $daemonBackupRepository,
 | 
				
			||||||
 | 
					        ResponseFactory $responseFactory
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        parent::__construct();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->daemonBackupRepository = $daemonBackupRepository;
 | 
				
			||||||
 | 
					        $this->responseFactory = $responseFactory;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Download the backup for a given server instance. For daemon local files, the file
 | 
				
			||||||
 | 
					     * will be streamed back through the Panel. For AWS S3 files, a signed URL will be generated
 | 
				
			||||||
 | 
					     * which the user is redirected to.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Backups\DownloadBackupRequest $request
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Models\Server $server
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Models\Backup $backup
 | 
				
			||||||
 | 
					     * @return \Illuminate\Http\RedirectResponse
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function __invoke(DownloadBackupRequest $request, Server $server, Backup $backup)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $signer = new Sha256;
 | 
				
			||||||
 | 
					        $now = CarbonImmutable::now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $token = (new Builder)->issuedBy(config('app.url'))
 | 
				
			||||||
 | 
					            ->permittedFor($server->node->getConnectionAddress())
 | 
				
			||||||
 | 
					            ->identifiedBy(hash('sha256', $request->user()->id . $server->uuid), true)
 | 
				
			||||||
 | 
					            ->issuedAt($now->getTimestamp())
 | 
				
			||||||
 | 
					            ->canOnlyBeUsedAfter($now->subMinutes(5)->getTimestamp())
 | 
				
			||||||
 | 
					            ->expiresAt($now->addMinutes(15)->getTimestamp())
 | 
				
			||||||
 | 
					            ->withClaim('unique_id', Str::random(16))
 | 
				
			||||||
 | 
					            ->withClaim('backup_uuid', $backup->uuid)
 | 
				
			||||||
 | 
					            ->withClaim('server_uuid', $server->uuid)
 | 
				
			||||||
 | 
					            ->getToken($signer, new Key($server->node->daemonSecret));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $location = sprintf(
 | 
				
			||||||
 | 
					            '%s/download/backup?token=%s',
 | 
				
			||||||
 | 
					            $server->node->getConnectionAddress(),
 | 
				
			||||||
 | 
					            $token->__toString()
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return RedirectResponse::create($location);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -4,9 +4,12 @@ namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use Illuminate\Http\Response;
 | 
					use Illuminate\Http\Response;
 | 
				
			||||||
use Pterodactyl\Models\Server;
 | 
					use Pterodactyl\Models\Server;
 | 
				
			||||||
 | 
					use Illuminate\Http\JsonResponse;
 | 
				
			||||||
use Pterodactyl\Repositories\Eloquent\ServerRepository;
 | 
					use Pterodactyl\Repositories\Eloquent\ServerRepository;
 | 
				
			||||||
 | 
					use Pterodactyl\Services\Servers\ReinstallServerService;
 | 
				
			||||||
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
 | 
					use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
 | 
				
			||||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Settings\RenameServerRequest;
 | 
					use Pterodactyl\Http\Requests\Api\Client\Servers\Settings\RenameServerRequest;
 | 
				
			||||||
 | 
					use Pterodactyl\Http\Requests\Api\Client\Servers\Settings\ReinstallServerRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SettingsController extends ClientApiController
 | 
					class SettingsController extends ClientApiController
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@ -15,16 +18,25 @@ class SettingsController extends ClientApiController
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    private $repository;
 | 
					    private $repository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Services\Servers\ReinstallServerService
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $reinstallServerService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * SettingsController constructor.
 | 
					     * SettingsController constructor.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository
 | 
					     * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Services\Servers\ReinstallServerService $reinstallServerService
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function __construct(ServerRepository $repository)
 | 
					    public function __construct(
 | 
				
			||||||
    {
 | 
					        ServerRepository $repository,
 | 
				
			||||||
 | 
					        ReinstallServerService $reinstallServerService
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
        parent::__construct();
 | 
					        parent::__construct();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->repository = $repository;
 | 
					        $this->repository = $repository;
 | 
				
			||||||
 | 
					        $this->reinstallServerService = $reinstallServerService;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -32,7 +44,7 @@ class SettingsController extends ClientApiController
 | 
				
			|||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Settings\RenameServerRequest $request
 | 
					     * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Settings\RenameServerRequest $request
 | 
				
			||||||
     * @param \Pterodactyl\Models\Server $server
 | 
					     * @param \Pterodactyl\Models\Server $server
 | 
				
			||||||
     * @return \Illuminate\Http\Response
 | 
					     * @return \Illuminate\Http\JsonResponse
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @throws \Pterodactyl\Exceptions\Model\DataValidationException
 | 
					     * @throws \Pterodactyl\Exceptions\Model\DataValidationException
 | 
				
			||||||
     * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
 | 
					     * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
 | 
				
			||||||
@ -43,6 +55,22 @@ class SettingsController extends ClientApiController
 | 
				
			|||||||
            'name' => $request->input('name'),
 | 
					            'name' => $request->input('name'),
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Response::create('', Response::HTTP_NO_CONTENT);
 | 
					        return JsonResponse::create([], Response::HTTP_NO_CONTENT);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Reinstalls the server on the daemon.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Settings\ReinstallServerRequest $request
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Models\Server $server
 | 
				
			||||||
 | 
					     * @return \Illuminate\Http\JsonResponse
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @throws \Throwable
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function reinstall(ReinstallServerRequest $request, Server $server)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->reinstallServerService->reinstall($server);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return JsonResponse::create([], Response::HTTP_ACCEPTED);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,11 +2,17 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
 | 
					namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Illuminate\Http\Request;
 | 
				
			||||||
use Pterodactyl\Models\Server;
 | 
					use Pterodactyl\Models\Server;
 | 
				
			||||||
 | 
					use Illuminate\Http\JsonResponse;
 | 
				
			||||||
use Pterodactyl\Repositories\Eloquent\SubuserRepository;
 | 
					use Pterodactyl\Repositories\Eloquent\SubuserRepository;
 | 
				
			||||||
 | 
					use Pterodactyl\Services\Subusers\SubuserCreationService;
 | 
				
			||||||
use Pterodactyl\Transformers\Api\Client\SubuserTransformer;
 | 
					use Pterodactyl\Transformers\Api\Client\SubuserTransformer;
 | 
				
			||||||
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
 | 
					use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
 | 
				
			||||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\GetSubuserRequest;
 | 
					use Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\GetSubuserRequest;
 | 
				
			||||||
 | 
					use Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\StoreSubuserRequest;
 | 
				
			||||||
 | 
					use Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\DeleteSubuserRequest;
 | 
				
			||||||
 | 
					use Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\UpdateSubuserRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SubuserController extends ClientApiController
 | 
					class SubuserController extends ClientApiController
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@ -15,16 +21,25 @@ class SubuserController extends ClientApiController
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    private $repository;
 | 
					    private $repository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Services\Subusers\SubuserCreationService
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $creationService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * SubuserController constructor.
 | 
					     * SubuserController constructor.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param \Pterodactyl\Repositories\Eloquent\SubuserRepository $repository
 | 
					     * @param \Pterodactyl\Repositories\Eloquent\SubuserRepository $repository
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Services\Subusers\SubuserCreationService $creationService
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function __construct(SubuserRepository $repository)
 | 
					    public function __construct(
 | 
				
			||||||
    {
 | 
					        SubuserRepository $repository,
 | 
				
			||||||
 | 
					        SubuserCreationService $creationService
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
        parent::__construct();
 | 
					        parent::__construct();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->repository = $repository;
 | 
					        $this->repository = $repository;
 | 
				
			||||||
 | 
					        $this->creationService = $creationService;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -36,10 +51,78 @@ class SubuserController extends ClientApiController
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    public function index(GetSubuserRequest $request, Server $server)
 | 
					    public function index(GetSubuserRequest $request, Server $server)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $server->subusers->load('user');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return $this->fractal->collection($server->subusers)
 | 
					        return $this->fractal->collection($server->subusers)
 | 
				
			||||||
            ->transformWith($this->getTransformer(SubuserTransformer::class))
 | 
					            ->transformWith($this->getTransformer(SubuserTransformer::class))
 | 
				
			||||||
            ->toArray();
 | 
					            ->toArray();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Create a new subuser for the given server.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\StoreSubuserRequest $request
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Models\Server $server
 | 
				
			||||||
 | 
					     * @return array
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @throws \Pterodactyl\Exceptions\Model\DataValidationException
 | 
				
			||||||
 | 
					     * @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException
 | 
				
			||||||
 | 
					     * @throws \Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException
 | 
				
			||||||
 | 
					     * @throws \Throwable
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function store(StoreSubuserRequest $request, Server $server)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $response = $this->creationService->handle(
 | 
				
			||||||
 | 
					            $server, $request->input('email'), $this->getDefaultPermissions($request)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $this->fractal->item($response)
 | 
				
			||||||
 | 
					            ->transformWith($this->getTransformer(SubuserTransformer::class))
 | 
				
			||||||
 | 
					            ->toArray();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Update a given subuser in the system for the server.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\UpdateSubuserRequest $request
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Models\Server $server
 | 
				
			||||||
 | 
					     * @return array
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @throws \Pterodactyl\Exceptions\Model\DataValidationException
 | 
				
			||||||
 | 
					     * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function update(UpdateSubuserRequest $request, Server $server): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $subuser = $request->endpointSubuser();
 | 
				
			||||||
 | 
					        $this->repository->update($subuser->id, [
 | 
				
			||||||
 | 
					            'permissions' => $this->getDefaultPermissions($request),
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $this->fractal->item($subuser->refresh())
 | 
				
			||||||
 | 
					            ->transformWith($this->getTransformer(SubuserTransformer::class))
 | 
				
			||||||
 | 
					            ->toArray();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Removes a subusers from a server's assignment.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Http\Requests\Api\Client\Servers\Subusers\DeleteSubuserRequest $request
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Models\Server $server
 | 
				
			||||||
 | 
					     * @return \Illuminate\Http\JsonResponse
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function delete(DeleteSubuserRequest $request, Server $server)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->repository->delete($request->endpointSubuser()->id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return JsonResponse::create([], JsonResponse::HTTP_NO_CONTENT);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Returns the default permissions for all subusers to ensure none are ever removed wrongly.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Illuminate\Http\Request $request
 | 
				
			||||||
 | 
					     * @return array
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected function getDefaultPermissions(Request $request): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return array_unique(array_merge($request->input('permissions') ?? [], ['websocket.*']));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,70 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pterodactyl\Http\Controllers\Api\Remote\Servers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Carbon\Carbon;
 | 
				
			||||||
 | 
					use Illuminate\Http\Request;
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Server;
 | 
				
			||||||
 | 
					use Illuminate\Http\JsonResponse;
 | 
				
			||||||
 | 
					use Pterodactyl\Http\Controllers\Controller;
 | 
				
			||||||
 | 
					use Pterodactyl\Repositories\Eloquent\BackupRepository;
 | 
				
			||||||
 | 
					use Pterodactyl\Repositories\Eloquent\ServerRepository;
 | 
				
			||||||
 | 
					use Pterodactyl\Http\Requests\Api\Remote\ReportBackupCompleteRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ServerBackupController extends Controller
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Repositories\Eloquent\BackupRepository
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $repository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Repositories\Eloquent\ServerRepository
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $serverRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * ServerBackupController constructor.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Repositories\Eloquent\BackupRepository $repository
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $serverRepository
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function __construct(BackupRepository $repository, ServerRepository $serverRepository)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->repository = $repository;
 | 
				
			||||||
 | 
					        $this->serverRepository = $serverRepository;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Updates a server backup's state in the database depending on wether or not
 | 
				
			||||||
 | 
					     * it was successful.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Http\Requests\Api\Remote\ReportBackupCompleteRequest $request
 | 
				
			||||||
 | 
					     * @param string $uuid
 | 
				
			||||||
 | 
					     * @param string $backup
 | 
				
			||||||
 | 
					     * @return \Illuminate\Http\JsonResponse
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function __invoke(ReportBackupCompleteRequest $request, string $uuid, string $backup)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $server = $this->serverRepository->getByUuid($uuid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $where = [
 | 
				
			||||||
 | 
					            ['uuid', '=', $backup],
 | 
				
			||||||
 | 
					            ['server_id', '=', $server->id],
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($request->input('successful')) {
 | 
				
			||||||
 | 
					            $this->repository->updateWhere($where, [
 | 
				
			||||||
 | 
					                'sha256_hash' => $request->input('sha256_hash'),
 | 
				
			||||||
 | 
					                'bytes' => $request->input('file_size'),
 | 
				
			||||||
 | 
					                'completed_at' => Carbon::now(),
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            $this->repository->deleteWhere($where);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return JsonResponse::create([], JsonResponse::HTTP_NO_CONTENT);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,238 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pterodactyl\Http\Controllers\Api\Remote\Servers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Cake\Chronos\Chronos;
 | 
				
			||||||
 | 
					use Lcobucci\JWT\Builder;
 | 
				
			||||||
 | 
					use Illuminate\Http\Request;
 | 
				
			||||||
 | 
					use Lcobucci\JWT\Signer\Key;
 | 
				
			||||||
 | 
					use Psr\Log\LoggerInterface;
 | 
				
			||||||
 | 
					use Illuminate\Http\Response;
 | 
				
			||||||
 | 
					use Illuminate\Http\JsonResponse;
 | 
				
			||||||
 | 
					use Lcobucci\JWT\Signer\Hmac\Sha256;
 | 
				
			||||||
 | 
					use Illuminate\Database\ConnectionInterface;
 | 
				
			||||||
 | 
					use Pterodactyl\Http\Controllers\Controller;
 | 
				
			||||||
 | 
					use Pterodactyl\Services\Servers\SuspensionService;
 | 
				
			||||||
 | 
					use Pterodactyl\Repositories\Eloquent\NodeRepository;
 | 
				
			||||||
 | 
					use Pterodactyl\Repositories\Eloquent\ServerRepository;
 | 
				
			||||||
 | 
					use Pterodactyl\Repositories\Wings\DaemonServerRepository;
 | 
				
			||||||
 | 
					use Pterodactyl\Repositories\Wings\DaemonTransferRepository;
 | 
				
			||||||
 | 
					use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
 | 
				
			||||||
 | 
					use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
 | 
				
			||||||
 | 
					use Pterodactyl\Services\Servers\ServerConfigurationStructureService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ServerTransferController extends Controller
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Illuminate\Database\ConnectionInterface
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $connection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Repositories\Eloquent\ServerRepository
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $repository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $allocationRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Repositories\Eloquent\NodeRepository
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $nodeRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $daemonServerRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Repositories\Wings\DaemonTransferRepository
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $daemonTransferRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Services\Servers\ServerConfigurationStructureService
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $configurationStructureService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Services\Servers\SuspensionService
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $suspensionService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Psr\Log\LoggerInterface
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $writer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * ServerTransferController constructor.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Illuminate\Database\ConnectionInterface $connection
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Repositories\Eloquent\ServerRepository $repository
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Contracts\Repository\AllocationRepositoryInterface $allocationRepository
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Repositories\Eloquent\NodeRepository $nodeRepository
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonServerRepository
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Repositories\Wings\DaemonTransferRepository $daemonTransferRepository
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Services\Servers\ServerConfigurationStructureService $configurationStructureService
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Services\Servers\SuspensionService $suspensionService
 | 
				
			||||||
 | 
					     * @param \Psr\Log\LoggerInterface $writer
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        ConnectionInterface $connection,
 | 
				
			||||||
 | 
					        ServerRepository $repository,
 | 
				
			||||||
 | 
					        AllocationRepositoryInterface $allocationRepository,
 | 
				
			||||||
 | 
					        NodeRepository $nodeRepository,
 | 
				
			||||||
 | 
					        DaemonServerRepository $daemonServerRepository,
 | 
				
			||||||
 | 
					        DaemonTransferRepository $daemonTransferRepository,
 | 
				
			||||||
 | 
					        ServerConfigurationStructureService $configurationStructureService,
 | 
				
			||||||
 | 
					        SuspensionService $suspensionService,
 | 
				
			||||||
 | 
					        LoggerInterface $writer
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        $this->connection = $connection;
 | 
				
			||||||
 | 
					        $this->repository = $repository;
 | 
				
			||||||
 | 
					        $this->allocationRepository = $allocationRepository;
 | 
				
			||||||
 | 
					        $this->nodeRepository = $nodeRepository;
 | 
				
			||||||
 | 
					        $this->daemonServerRepository = $daemonServerRepository;
 | 
				
			||||||
 | 
					        $this->daemonTransferRepository = $daemonTransferRepository;
 | 
				
			||||||
 | 
					        $this->configurationStructureService = $configurationStructureService;
 | 
				
			||||||
 | 
					        $this->suspensionService = $suspensionService;
 | 
				
			||||||
 | 
					        $this->writer = $writer;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The daemon notifies us about the archive status.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Illuminate\Http\Request $request
 | 
				
			||||||
 | 
					     * @param string $uuid
 | 
				
			||||||
 | 
					     * @return \Illuminate\Http\JsonResponse
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
 | 
				
			||||||
 | 
					     * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
 | 
				
			||||||
 | 
					     * @throws \Throwable
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function archive(Request $request, string $uuid)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $server = $this->repository->getByUuid($uuid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Unsuspend the server and don't continue the transfer.
 | 
				
			||||||
 | 
					        if (! $request->input('successful')) {
 | 
				
			||||||
 | 
					            $this->suspensionService->toggle($server, 'unsuspend');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return JsonResponse::create([], Response::HTTP_NO_CONTENT);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $server->node_id = $server->transfer->new_node;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $data = $this->configurationStructureService->handle($server);
 | 
				
			||||||
 | 
					        $data['suspended'] = false;
 | 
				
			||||||
 | 
					        $data['service']['skip_scripts'] = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $allocations = $server->getAllocationMappings();
 | 
				
			||||||
 | 
					        $data['allocations']['default']['ip'] = array_key_first($allocations);
 | 
				
			||||||
 | 
					        $data['allocations']['default']['port'] = $allocations[$data['allocations']['default']['ip']][0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $now = Chronos::now();
 | 
				
			||||||
 | 
					        $signer = new Sha256;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $token = (new Builder)->issuedBy(config('app.url'))
 | 
				
			||||||
 | 
					            ->permittedFor($server->node->getConnectionAddress())
 | 
				
			||||||
 | 
					            ->identifiedBy(hash('sha256', $server->uuid), true)
 | 
				
			||||||
 | 
					            ->issuedAt($now->getTimestamp())
 | 
				
			||||||
 | 
					            ->canOnlyBeUsedAfter($now->getTimestamp())
 | 
				
			||||||
 | 
					            ->expiresAt($now->addMinutes(15)->getTimestamp())
 | 
				
			||||||
 | 
					            ->relatedTo($server->uuid, true)
 | 
				
			||||||
 | 
					            ->getToken($signer, new Key($server->node->daemonSecret));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // On the daemon transfer repository, make sure to set the node after the server
 | 
				
			||||||
 | 
					        // because setServer() tells the repository to use the server's node and not the one
 | 
				
			||||||
 | 
					        // we want to specify.
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            $this->daemonTransferRepository
 | 
				
			||||||
 | 
					                ->setServer($server)
 | 
				
			||||||
 | 
					                ->setNode($this->nodeRepository->find($server->transfer->new_node))
 | 
				
			||||||
 | 
					                ->notify($server, $data, $server->node, $token->__toString());
 | 
				
			||||||
 | 
					        } catch (DaemonConnectionException $exception) {
 | 
				
			||||||
 | 
					            throw $exception;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return JsonResponse::create([], Response::HTTP_NO_CONTENT);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The daemon notifies us about a transfer failure.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Illuminate\Http\Request $request
 | 
				
			||||||
 | 
					     * @param string $uuid
 | 
				
			||||||
 | 
					     * @return \Illuminate\Http\JsonResponse
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @throws \Throwable
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function failure(string $uuid)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $server = $this->repository->getByUuid($uuid);
 | 
				
			||||||
 | 
					        $transfer = $server->transfer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $allocationIds = json_decode($transfer->new_additional_allocations);
 | 
				
			||||||
 | 
					        array_push($allocationIds, $transfer->new_allocation);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Remove the new allocations.
 | 
				
			||||||
 | 
					        $this->allocationRepository->updateWhereIn('id', $allocationIds, ['server_id' => null]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Unsuspend the server.
 | 
				
			||||||
 | 
					        $this->suspensionService->toggle($server, 'unsuspend');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return JsonResponse::create([], Response::HTTP_NO_CONTENT);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The daemon notifies us about a transfer success.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param string $uuid
 | 
				
			||||||
 | 
					     * @return \Illuminate\Http\JsonResponse
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @throws \Throwable
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function success(string $uuid)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $server = $this->repository->getByUuid($uuid);
 | 
				
			||||||
 | 
					        $transfer = $server->transfer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $allocationIds = json_decode($transfer->old_additional_allocations);
 | 
				
			||||||
 | 
					        array_push($allocationIds, $transfer->old_allocation);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Begin a transaction.
 | 
				
			||||||
 | 
					        $this->connection->beginTransaction();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Remove the old allocations.
 | 
				
			||||||
 | 
					        $this->allocationRepository->updateWhereIn('id', $allocationIds, ['server_id' => null]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Update the server's allocation_id and node_id.
 | 
				
			||||||
 | 
					        $server->allocation_id = $transfer->new_allocation;
 | 
				
			||||||
 | 
					        $server->node_id = $transfer->new_node;
 | 
				
			||||||
 | 
					        $server->save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Mark the transfer as successful.
 | 
				
			||||||
 | 
					        $transfer->successful = true;
 | 
				
			||||||
 | 
					        $transfer->save();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Commit the transaction.
 | 
				
			||||||
 | 
					        $this->connection->commit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Delete the server from the old node
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            $this->daemonServerRepository->setServer($server)->delete();
 | 
				
			||||||
 | 
					        } catch (DaemonConnectionException $exception) {
 | 
				
			||||||
 | 
					            $this->writer->warning($exception);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Unsuspend the server
 | 
				
			||||||
 | 
					        $server->load('node');
 | 
				
			||||||
 | 
					        $this->suspensionService->toggle($server, $this->suspensionService::ACTION_UNSUSPEND);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return JsonResponse::create([], Response::HTTP_NO_CONTENT);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -42,6 +42,16 @@ class AuthenticateServerAccess
 | 
				
			|||||||
            throw new NotFoundHttpException(trans('exceptions.api.resource_not_found'));
 | 
					            throw new NotFoundHttpException(trans('exceptions.api.resource_not_found'));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // At the very least, ensure that the user trying to make this request is the
 | 
				
			||||||
 | 
					        // server owner, a subuser, or a root admin. We'll leave it up to the controllers
 | 
				
			||||||
 | 
					        // to authenticate more detailed permissions if needed.
 | 
				
			||||||
 | 
					        if ($request->user()->id !== $server->owner_id && ! $request->user()->root_admin) {
 | 
				
			||||||
 | 
					            // Check for subuser status.
 | 
				
			||||||
 | 
					            if (! $server->subusers->contains('user_id', $request->user()->id)) {
 | 
				
			||||||
 | 
					                throw new NotFoundHttpException(trans('exceptions.api.resource_not_found'));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ($server->suspended) {
 | 
					        if ($server->suspended) {
 | 
				
			||||||
            throw new AccessDeniedHttpException('Cannot access a server that is marked as being suspended.');
 | 
					            throw new AccessDeniedHttpException('Cannot access a server that is marked as being suspended.');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@
 | 
				
			|||||||
namespace Pterodactyl\Http\Middleware\Api\Client;
 | 
					namespace Pterodactyl\Http\Middleware\Api\Client;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use Closure;
 | 
					use Closure;
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Backup;
 | 
				
			||||||
use Illuminate\Container\Container;
 | 
					use Illuminate\Container\Container;
 | 
				
			||||||
use Pterodactyl\Contracts\Extensions\HashidsInterface;
 | 
					use Pterodactyl\Contracts\Extensions\HashidsInterface;
 | 
				
			||||||
use Pterodactyl\Http\Middleware\Api\ApiSubstituteBindings;
 | 
					use Pterodactyl\Http\Middleware\Api\ApiSubstituteBindings;
 | 
				
			||||||
@ -55,6 +56,10 @@ class SubstituteClientApiBindings extends ApiSubstituteBindings
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->router->model('backup', Backup::class, function ($value) {
 | 
				
			||||||
 | 
					            return Backup::query()->where('uuid', $value)->firstOrFail();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return parent::handle($request, $next);
 | 
					        return parent::handle($request, $next);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -49,6 +49,7 @@ class StoreServerRequest extends ApplicationApiRequest
 | 
				
			|||||||
            'limits.swap' => $rules['swap'],
 | 
					            'limits.swap' => $rules['swap'],
 | 
				
			||||||
            'limits.disk' => $rules['disk'],
 | 
					            'limits.disk' => $rules['disk'],
 | 
				
			||||||
            'limits.io' => $rules['io'],
 | 
					            'limits.io' => $rules['io'],
 | 
				
			||||||
 | 
					            'limits.threads' => $rules['threads'],
 | 
				
			||||||
            'limits.cpu' => $rules['cpu'],
 | 
					            'limits.cpu' => $rules['cpu'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Application Resource Limits
 | 
					            // Application Resource Limits
 | 
				
			||||||
@ -96,6 +97,7 @@ class StoreServerRequest extends ApplicationApiRequest
 | 
				
			|||||||
            'disk' => array_get($data, 'limits.disk'),
 | 
					            'disk' => array_get($data, 'limits.disk'),
 | 
				
			||||||
            'io' => array_get($data, 'limits.io'),
 | 
					            'io' => array_get($data, 'limits.io'),
 | 
				
			||||||
            'cpu' => array_get($data, 'limits.cpu'),
 | 
					            'cpu' => array_get($data, 'limits.cpu'),
 | 
				
			||||||
 | 
					            'threads' => array_get($data, 'limits.threads'),
 | 
				
			||||||
            'skip_scripts' => array_get($data, 'skip_scripts', false),
 | 
					            'skip_scripts' => array_get($data, 'skip_scripts', false),
 | 
				
			||||||
            'allocation_id' => array_get($data, 'allocation.default'),
 | 
					            'allocation_id' => array_get($data, 'allocation.default'),
 | 
				
			||||||
            'allocation_additional' => array_get($data, 'allocation.additional'),
 | 
					            'allocation_additional' => array_get($data, 'allocation.additional'),
 | 
				
			||||||
 | 
				
			|||||||
@ -25,6 +25,7 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest
 | 
				
			|||||||
            'limits.swap' => $this->requiredToOptional('swap', $rules['swap'], true),
 | 
					            'limits.swap' => $this->requiredToOptional('swap', $rules['swap'], true),
 | 
				
			||||||
            'limits.io' => $this->requiredToOptional('io', $rules['io'], true),
 | 
					            'limits.io' => $this->requiredToOptional('io', $rules['io'], true),
 | 
				
			||||||
            'limits.cpu' => $this->requiredToOptional('cpu', $rules['cpu'], true),
 | 
					            'limits.cpu' => $this->requiredToOptional('cpu', $rules['cpu'], true),
 | 
				
			||||||
 | 
					            'limits.threads' => $this->requiredToOptional('threads', $rules['threads'], true),
 | 
				
			||||||
            'limits.disk' => $this->requiredToOptional('disk', $rules['disk'], true),
 | 
					            'limits.disk' => $this->requiredToOptional('disk', $rules['disk'], true),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Legacy rules to maintain backwards compatable API support without requiring
 | 
					            // Legacy rules to maintain backwards compatable API support without requiring
 | 
				
			||||||
@ -35,6 +36,7 @@ class UpdateServerBuildConfigurationRequest extends ServerWriteRequest
 | 
				
			|||||||
            'swap' => $this->requiredToOptional('swap', $rules['swap']),
 | 
					            'swap' => $this->requiredToOptional('swap', $rules['swap']),
 | 
				
			||||||
            'io' => $this->requiredToOptional('io', $rules['io']),
 | 
					            'io' => $this->requiredToOptional('io', $rules['io']),
 | 
				
			||||||
            'cpu' => $this->requiredToOptional('cpu', $rules['cpu']),
 | 
					            'cpu' => $this->requiredToOptional('cpu', $rules['cpu']),
 | 
				
			||||||
 | 
					            'threads' => $this->requiredToOptional('threads', $rules['threads']),
 | 
				
			||||||
            'disk' => $this->requiredToOptional('disk', $rules['disk']),
 | 
					            'disk' => $this->requiredToOptional('disk', $rules['disk']),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            'add_allocations' => 'bail|array',
 | 
					            'add_allocations' => 'bail|array',
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pterodactyl\Http\Requests\Api\Client\Servers\Backups;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Backup;
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Server;
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Permission;
 | 
				
			||||||
 | 
					use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DownloadBackupRequest extends ClientApiRequest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @return string
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function permission()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return Permission::ACTION_BACKUP_DOWNLOAD;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Ensure that this backup belongs to the server that is also present in the
 | 
				
			||||||
 | 
					     * request.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return bool
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function resourceExists(): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /** @var \Pterodactyl\Models\Server|mixed $server */
 | 
				
			||||||
 | 
					        $server = $this->route()->parameter('server');
 | 
				
			||||||
 | 
					        /** @var \Pterodactyl\Models\Backup|mixed $backup */
 | 
				
			||||||
 | 
					        $backup = $this->route()->parameter('backup');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ($server instanceof Server && $backup instanceof Backup) {
 | 
				
			||||||
 | 
					            if ($server->exists && $backup->exists && $server->id === $backup->server_id) {
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pterodactyl\Http\Requests\Api\Client\Servers\Backups;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Permission;
 | 
				
			||||||
 | 
					use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GetBackupsRequest extends ClientApiRequest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @return string
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function permission()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return Permission::ACTION_BACKUP_READ;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pterodactyl\Http\Requests\Api\Client\Servers\Backups;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Permission;
 | 
				
			||||||
 | 
					use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class StoreBackupRequest extends ClientApiRequest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @return string
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function permission()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return Permission::ACTION_BACKUP_CREATE;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @return array
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function rules(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'name' => 'nullable|string|max:255',
 | 
				
			||||||
 | 
					            'ignore' => 'nullable|string',
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -18,11 +18,10 @@ class SendPowerRequest extends ClientApiRequest
 | 
				
			|||||||
            case 'start':
 | 
					            case 'start':
 | 
				
			||||||
                return Permission::ACTION_CONTROL_START;
 | 
					                return Permission::ACTION_CONTROL_START;
 | 
				
			||||||
            case 'stop':
 | 
					            case 'stop':
 | 
				
			||||||
 | 
					            case 'kill':
 | 
				
			||||||
                return Permission::ACTION_CONTROL_STOP;
 | 
					                return Permission::ACTION_CONTROL_STOP;
 | 
				
			||||||
            case 'restart':
 | 
					            case 'restart':
 | 
				
			||||||
                return Permission::ACTION_CONTROL_RESTART;
 | 
					                return Permission::ACTION_CONTROL_RESTART;
 | 
				
			||||||
            case 'kill':
 | 
					 | 
				
			||||||
                return Permission::ACTION_CONTROL_KILL;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return '__invalid';
 | 
					        return '__invalid';
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pterodactyl\Http\Requests\Api\Client\Servers\Settings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Permission;
 | 
				
			||||||
 | 
					use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ReinstallServerRequest extends ClientApiRequest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @return string
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function permission()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return Permission::ACTION_SETTINGS_REINSTALL;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pterodactyl\Http\Requests\Api\Client\Servers\Subusers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Permission;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DeleteSubuserRequest extends SubuserRequest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @return string
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function permission()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return Permission::ACTION_USER_DELETE;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -3,9 +3,8 @@
 | 
				
			|||||||
namespace Pterodactyl\Http\Requests\Api\Client\Servers\Subusers;
 | 
					namespace Pterodactyl\Http\Requests\Api\Client\Servers\Subusers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use Pterodactyl\Models\Permission;
 | 
					use Pterodactyl\Models\Permission;
 | 
				
			||||||
use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class GetSubuserRequest extends ClientApiRequest
 | 
					class GetSubuserRequest extends SubuserRequest
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Confirm that a user is able to view subusers for the specified server.
 | 
					     * Confirm that a user is able to view subusers for the specified server.
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pterodactyl\Http\Requests\Api\Client\Servers\Subusers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Permission;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class StoreSubuserRequest extends SubuserRequest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @return string
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function permission()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return Permission::ACTION_USER_CREATE;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @return array
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function rules(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'email' => 'required|email',
 | 
				
			||||||
 | 
					            'permissions' => 'required|array',
 | 
				
			||||||
 | 
					            'permissions.*' => 'string',
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										131
									
								
								app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								app/Http/Requests/Api/Client/Servers/Subusers/SubuserRequest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,131 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pterodactyl\Http\Requests\Api\Client\Servers\Subusers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Illuminate\Http\Request;
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Server;
 | 
				
			||||||
 | 
					use Pterodactyl\Exceptions\Http\HttpForbiddenException;
 | 
				
			||||||
 | 
					use Pterodactyl\Repositories\Eloquent\SubuserRepository;
 | 
				
			||||||
 | 
					use Pterodactyl\Http\Requests\Api\Client\ClientApiRequest;
 | 
				
			||||||
 | 
					use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
 | 
				
			||||||
 | 
					use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					abstract class SubuserRequest extends ClientApiRequest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Models\Subuser|null
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected $model;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Authorize the request and ensure that a user is not trying to modify themselves.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return bool
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function authorize(): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (! parent::authorize()) {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // If there is a subuser present in the URL, validate that it is not the same as the
 | 
				
			||||||
 | 
					        // current request user. You're not allowed to modify yourself.
 | 
				
			||||||
 | 
					        if ($this->route()->hasParameter('subuser')) {
 | 
				
			||||||
 | 
					            if ($this->endpointSubuser()->user_id === $this->user()->id) {
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // If this is a POST request, validate that the user can even assign the permissions they
 | 
				
			||||||
 | 
					        // have selected to assign.
 | 
				
			||||||
 | 
					        if ($this->method() === Request::METHOD_POST && $this->has('permissions')) {
 | 
				
			||||||
 | 
					            $this->validatePermissionsCanBeAssigned(
 | 
				
			||||||
 | 
					                $this->input('permissions') ?? []
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Validates that the permissions we are trying to assign can actually be assigned
 | 
				
			||||||
 | 
					     * by the user making the request.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param array $permissions
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected function validatePermissionsCanBeAssigned(array $permissions)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $user = $this->user();
 | 
				
			||||||
 | 
					        /** @var \Pterodactyl\Models\Server $server */
 | 
				
			||||||
 | 
					        $server = $this->route()->parameter('server');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // If we are a root admin or the server owner, no need to perform these checks.
 | 
				
			||||||
 | 
					        if ($user->root_admin || $user->id === $server->owner_id) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Otherwise, get the current subuser's permission set, and ensure that the
 | 
				
			||||||
 | 
					        // permissions they are trying to assign are not _more_ than the ones they
 | 
				
			||||||
 | 
					        // already have.
 | 
				
			||||||
 | 
					        if (count(array_diff($permissions, $this->currentUserPermissions())) > 0) {
 | 
				
			||||||
 | 
					            throw new HttpForbiddenException(
 | 
				
			||||||
 | 
					                'Cannot assign permissions to a subuser that your account does not actively possess.'
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Returns the currently authenticated user's permissions.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return array
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function currentUserPermissions(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /** @var \Pterodactyl\Repositories\Eloquent\SubuserRepository $repository */
 | 
				
			||||||
 | 
					        $repository = $this->container->make(SubuserRepository::class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /* @var \Pterodactyl\Models\Subuser $model */
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            $model = $repository->findFirstWhere([
 | 
				
			||||||
 | 
					                ['server_id', $this->route()->parameter('server')->id],
 | 
				
			||||||
 | 
					                ['user_id' => $this->user()->id],
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					        } catch (RecordNotFoundException $exception) {
 | 
				
			||||||
 | 
					            return [];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $model->permissions;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Return the subuser model for the given request which can then be validated. If
 | 
				
			||||||
 | 
					     * required request parameters are missing a 404 error will be returned, otherwise
 | 
				
			||||||
 | 
					     * a model exception will be returned if the model is not found.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * This returns the subuser based on the endpoint being hit, not the actual subuser
 | 
				
			||||||
 | 
					     * for the account making the request.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return \Pterodactyl\Models\Subuser
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
 | 
				
			||||||
 | 
					     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function endpointSubuser()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        /** @var \Pterodactyl\Repositories\Eloquent\SubuserRepository $repository */
 | 
				
			||||||
 | 
					        $repository = $this->container->make(SubuserRepository::class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $parameters = $this->route()->parameters();
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					            ! isset($parameters['server'], $parameters['server'])
 | 
				
			||||||
 | 
					            || ! is_string($parameters['subuser'])
 | 
				
			||||||
 | 
					            || ! $parameters['server'] instanceof Server
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            throw new NotFoundHttpException;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $this->model ?: $this->model = $repository->getUserForServer(
 | 
				
			||||||
 | 
					            $parameters['server']->id, $parameters['subuser']
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pterodactyl\Http\Requests\Api\Client\Servers\Subusers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Permission;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UpdateSubuserRequest extends SubuserRequest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @return string
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function permission()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return Permission::ACTION_USER_UPDATE;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @return array
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function rules(): array
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'permissions' => 'required|array',
 | 
				
			||||||
 | 
					            'permissions.*' => 'string',
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										20
									
								
								app/Http/Requests/Api/Remote/ReportBackupCompleteRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								app/Http/Requests/Api/Remote/ReportBackupCompleteRequest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pterodactyl\Http\Requests\Api\Remote;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Illuminate\Foundation\Http\FormRequest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ReportBackupCompleteRequest extends FormRequest
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @return string[]
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function rules()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'successful' => 'boolean',
 | 
				
			||||||
 | 
					            'sha256_hash' => 'string|required_if:successful,true',
 | 
				
			||||||
 | 
					            'file_size' => 'numeric|required_if:successful,true',
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -18,7 +18,7 @@ namespace Pterodactyl\Models;
 | 
				
			|||||||
 * @property \Pterodactyl\Models\Server|null $server
 | 
					 * @property \Pterodactyl\Models\Server|null $server
 | 
				
			||||||
 * @property \Pterodactyl\Models\Node $node
 | 
					 * @property \Pterodactyl\Models\Node $node
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class Allocation extends Validable
 | 
					class Allocation extends Model
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The resource name for this model when it is transformed into an
 | 
					     * The resource name for this model when it is transformed into an
 | 
				
			||||||
@ -75,7 +75,7 @@ class Allocation extends Validable
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Accessor to automatically provide the IP alias if defined.
 | 
					     * Accessor to automatically provide the IP alias if defined.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param null|string $value
 | 
					     * @param string|null $value
 | 
				
			||||||
     * @return string
 | 
					     * @return string
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function getAliasAttribute($value)
 | 
					    public function getAliasAttribute($value)
 | 
				
			||||||
@ -86,7 +86,7 @@ class Allocation extends Validable
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Accessor to quickly determine if this allocation has an alias.
 | 
					     * Accessor to quickly determine if this allocation has an alias.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param null|string $value
 | 
					     * @param string|null $value
 | 
				
			||||||
     * @return bool
 | 
					     * @return bool
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function getHasAliasAttribute($value)
 | 
					    public function getHasAliasAttribute($value)
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,7 @@ use Pterodactyl\Services\Acl\Api\AdminAcl;
 | 
				
			|||||||
 * @property \Carbon\Carbon $created_at
 | 
					 * @property \Carbon\Carbon $created_at
 | 
				
			||||||
 * @property \Carbon\Carbon $updated_at
 | 
					 * @property \Carbon\Carbon $updated_at
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class ApiKey extends Validable
 | 
					class ApiKey extends Model
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    const RESOURCE_NAME = 'api_key';
 | 
					    const RESOURCE_NAME = 'api_key';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -53,7 +53,7 @@ class ApiKey extends Validable
 | 
				
			|||||||
     * @var array
 | 
					     * @var array
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected $casts = [
 | 
					    protected $casts = [
 | 
				
			||||||
        'allowed_ips' => 'json',
 | 
					        'allowed_ips' => 'array',
 | 
				
			||||||
        'user_id' => 'int',
 | 
					        'user_id' => 'int',
 | 
				
			||||||
        'r_' . AdminAcl::RESOURCE_USERS => 'int',
 | 
					        'r_' . AdminAcl::RESOURCE_USERS => 'int',
 | 
				
			||||||
        'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'int',
 | 
					        'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'int',
 | 
				
			||||||
@ -99,7 +99,8 @@ class ApiKey extends Validable
 | 
				
			|||||||
        'identifier' => 'required|string|size:16|unique:api_keys,identifier',
 | 
					        'identifier' => 'required|string|size:16|unique:api_keys,identifier',
 | 
				
			||||||
        'token' => 'required|string',
 | 
					        'token' => 'required|string',
 | 
				
			||||||
        'memo' => 'required|nullable|string|max:500',
 | 
					        'memo' => 'required|nullable|string|max:500',
 | 
				
			||||||
        'allowed_ips' => 'nullable|json',
 | 
					        'allowed_ips' => 'nullable|array',
 | 
				
			||||||
 | 
					        'allowed_ips.*' => 'string',
 | 
				
			||||||
        'last_used_at' => 'nullable|date',
 | 
					        'last_used_at' => 'nullable|date',
 | 
				
			||||||
        'r_' . AdminAcl::RESOURCE_USERS => 'integer|min:0|max:3',
 | 
					        'r_' . AdminAcl::RESOURCE_USERS => 'integer|min:0|max:3',
 | 
				
			||||||
        'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'integer|min:0|max:3',
 | 
					        'r_' . AdminAcl::RESOURCE_ALLOCATIONS => 'integer|min:0|max:3',
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										85
									
								
								app/Models/Backup.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								app/Models/Backup.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,85 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pterodactyl\Models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\SoftDeletes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @property int $id
 | 
				
			||||||
 | 
					 * @property int $server_id
 | 
				
			||||||
 | 
					 * @property int $uuid
 | 
				
			||||||
 | 
					 * @property string $name
 | 
				
			||||||
 | 
					 * @property string $ignored_files
 | 
				
			||||||
 | 
					 * @property string $disk
 | 
				
			||||||
 | 
					 * @property string|null $sha256_hash
 | 
				
			||||||
 | 
					 * @property int $bytes
 | 
				
			||||||
 | 
					 * @property \Carbon\CarbonImmutable|null $completed_at
 | 
				
			||||||
 | 
					 * @property \Carbon\CarbonImmutable $created_at
 | 
				
			||||||
 | 
					 * @property \Carbon\CarbonImmutable $updated_at
 | 
				
			||||||
 | 
					 * @property \Carbon\CarbonImmutable|null $deleted_at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @property \Pterodactyl\Models\Server $server
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class Backup extends Model
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    use SoftDeletes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const RESOURCE_NAME = 'backup';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const DISK_LOCAL = 'local';
 | 
				
			||||||
 | 
					    const DISK_AWS_S3 = 's3';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var string
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected $table = 'backups';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var bool
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected $immutableDates = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var array
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected $casts = [
 | 
				
			||||||
 | 
					        'id' => 'int',
 | 
				
			||||||
 | 
					        'bytes' => 'int',
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var array
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected $dates = [
 | 
				
			||||||
 | 
					        'completed_at',
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var array
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected $attributes = [
 | 
				
			||||||
 | 
					        'sha256_hash' => null,
 | 
				
			||||||
 | 
					        'bytes' => 0,
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var array
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static $validationRules = [
 | 
				
			||||||
 | 
					        'server_id' => 'bail|required|numeric|exists:servers,id',
 | 
				
			||||||
 | 
					        'uuid' => 'required|uuid',
 | 
				
			||||||
 | 
					        'name' => 'required|string',
 | 
				
			||||||
 | 
					        'ignored_files' => 'string',
 | 
				
			||||||
 | 
					        'disk' => 'required|string',
 | 
				
			||||||
 | 
					        'sha256_hash' => 'nullable|string',
 | 
				
			||||||
 | 
					        'bytes' => 'numeric',
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function server()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->belongsTo(Server::class);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -4,7 +4,7 @@ namespace Pterodactyl\Models;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use Znck\Eloquent\Traits\BelongsToThrough;
 | 
					use Znck\Eloquent\Traits\BelongsToThrough;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DaemonKey extends Validable
 | 
					class DaemonKey extends Model
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    use BelongsToThrough;
 | 
					    use BelongsToThrough;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace Pterodactyl\Models;
 | 
					namespace Pterodactyl\Models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Database extends Validable
 | 
					class Database extends Model
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The resource name for this model when it is transformed into an
 | 
					     * The resource name for this model when it is transformed into an
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace Pterodactyl\Models;
 | 
					namespace Pterodactyl\Models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DatabaseHost extends Validable
 | 
					class DatabaseHost extends Model
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The resource name for this model when it is transformed into an
 | 
					     * The resource name for this model when it is transformed into an
 | 
				
			||||||
 | 
				
			|||||||
@ -39,7 +39,7 @@ namespace Pterodactyl\Models;
 | 
				
			|||||||
 * @property \Pterodactyl\Models\Egg|null $scriptFrom
 | 
					 * @property \Pterodactyl\Models\Egg|null $scriptFrom
 | 
				
			||||||
 * @property \Pterodactyl\Models\Egg|null $configFrom
 | 
					 * @property \Pterodactyl\Models\Egg|null $configFrom
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class Egg extends Validable
 | 
					class Egg extends Model
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The resource name for this model when it is transformed into an
 | 
					     * The resource name for this model when it is transformed into an
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace Pterodactyl\Models;
 | 
					namespace Pterodactyl\Models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class EggVariable extends Validable
 | 
					class EggVariable extends Model
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The resource name for this model when it is transformed into an
 | 
					     * The resource name for this model when it is transformed into an
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace Pterodactyl\Models;
 | 
					namespace Pterodactyl\Models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Location extends Validable
 | 
					class Location extends Model
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The resource name for this model when it is transformed into an
 | 
					     * The resource name for this model when it is transformed into an
 | 
				
			||||||
 | 
				
			|||||||
@ -5,11 +5,18 @@ namespace Pterodactyl\Models;
 | 
				
			|||||||
use Illuminate\Support\Str;
 | 
					use Illuminate\Support\Str;
 | 
				
			||||||
use Illuminate\Validation\Rule;
 | 
					use Illuminate\Validation\Rule;
 | 
				
			||||||
use Illuminate\Container\Container;
 | 
					use Illuminate\Container\Container;
 | 
				
			||||||
use Illuminate\Database\Eloquent\Model;
 | 
					 | 
				
			||||||
use Illuminate\Contracts\Validation\Factory;
 | 
					use Illuminate\Contracts\Validation\Factory;
 | 
				
			||||||
 | 
					use Illuminate\Database\Eloquent\Model as IlluminateModel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
abstract class Validable extends Model
 | 
					abstract class Model extends IlluminateModel
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Set to true to return immutable Carbon date instances from the model.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @var bool
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected $immutableDates = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Determines if the model should undergo data validation before it is saved
 | 
					     * Determines if the model should undergo data validation before it is saved
 | 
				
			||||||
     * to the database.
 | 
					     * to the database.
 | 
				
			||||||
@ -47,7 +54,7 @@ abstract class Validable extends Model
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        static::$validatorFactory = Container::getInstance()->make(Factory::class);
 | 
					        static::$validatorFactory = Container::getInstance()->make(Factory::class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        static::saving(function (Validable $model) {
 | 
					        static::saving(function (Model $model) {
 | 
				
			||||||
            return $model->validate();
 | 
					            return $model->validate();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -140,7 +147,27 @@ abstract class Validable extends Model
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $this->getValidator()->setData(
 | 
					        return $this->getValidator()->setData(
 | 
				
			||||||
            $this->getAttributes()
 | 
					            // Trying to do self::toArray() here will leave out keys based on the whitelist/blacklist
 | 
				
			||||||
 | 
					            // for that model. Doing this will return all of the attributes in a format that can
 | 
				
			||||||
 | 
					            // properly be validated.
 | 
				
			||||||
 | 
					            $this->addCastAttributesToArray(
 | 
				
			||||||
 | 
					                $this->getAttributes(), $this->getMutatedAttributes()
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
        )->passes();
 | 
					        )->passes();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Return a timestamp as DateTime object.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param mixed $value
 | 
				
			||||||
 | 
					     * @return \Illuminate\Support\Carbon|\Carbon\CarbonImmutable
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected function asDateTime($value)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (! $this->immutableDates) {
 | 
				
			||||||
 | 
					            return parent::asDateTime($value);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return parent::asDateTime($value)->toImmutable();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -15,7 +15,7 @@ namespace Pterodactyl\Models;
 | 
				
			|||||||
 * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Egg[] $eggs
 | 
					 * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Egg[] $eggs
 | 
				
			||||||
 * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Pack[] $packs
 | 
					 * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Pack[] $packs
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class Nest extends Validable
 | 
					class Nest extends Model
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The resource name for this model when it is transformed into an
 | 
					     * The resource name for this model when it is transformed into an
 | 
				
			||||||
 | 
				
			|||||||
@ -32,9 +32,10 @@ use Pterodactyl\Models\Traits\Searchable;
 | 
				
			|||||||
 * @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers
 | 
					 * @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers
 | 
				
			||||||
 * @property \Pterodactyl\Models\Allocation[]|\Illuminate\Database\Eloquent\Collection $allocations
 | 
					 * @property \Pterodactyl\Models\Allocation[]|\Illuminate\Database\Eloquent\Collection $allocations
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class Node extends Validable
 | 
					class Node extends Model
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    use Notifiable, Searchable;
 | 
					    use Notifiable;
 | 
				
			||||||
 | 
					    use Searchable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The resource name for this model when it is transformed into an
 | 
					     * The resource name for this model when it is transformed into an
 | 
				
			||||||
@ -170,6 +171,7 @@ class Node extends Validable
 | 
				
			|||||||
            ],
 | 
					            ],
 | 
				
			||||||
            'system' => [
 | 
					            'system' => [
 | 
				
			||||||
                'data' => $this->daemonBase,
 | 
					                'data' => $this->daemonBase,
 | 
				
			||||||
 | 
					                'archive_directory' => $this->daemonBase . '/.archives',
 | 
				
			||||||
                'username' => 'pterodactyl',
 | 
					                'username' => 'pterodactyl',
 | 
				
			||||||
                'timezone_path' => '/etc/timezone',
 | 
					                'timezone_path' => '/etc/timezone',
 | 
				
			||||||
                'set_permissions_on_boot' => true,
 | 
					                'set_permissions_on_boot' => true,
 | 
				
			||||||
@ -235,4 +237,19 @@ class Node extends Validable
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->hasMany(Allocation::class);
 | 
					        return $this->hasMany(Allocation::class);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Returns a boolean if the node is viable for an additional server to be placed on it.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param int $memory
 | 
				
			||||||
 | 
					     * @param int $disk
 | 
				
			||||||
 | 
					     * @return bool
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function isViable(int $memory, int $disk): bool
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $memoryLimit = $this->memory * (1 + ($this->memory_overallocate / 100));
 | 
				
			||||||
 | 
					        $diskLimit = $this->disk * (1 + ($this->disk_overallocate / 100));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return ($this->sum_memory + $memory) <= $memoryLimit && ($this->sum_disk + $disk) <= $diskLimit;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -20,7 +20,7 @@ use Pterodactyl\Models\Traits\Searchable;
 | 
				
			|||||||
 * @property \Pterodactyl\Models\Egg|null $egg
 | 
					 * @property \Pterodactyl\Models\Egg|null $egg
 | 
				
			||||||
 * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Server[] $servers
 | 
					 * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Server[] $servers
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class Pack extends Validable
 | 
					class Pack extends Model
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    use Searchable;
 | 
					    use Searchable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,7 @@ namespace Pterodactyl\Models;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use Illuminate\Support\Collection;
 | 
					use Illuminate\Support\Collection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Permission extends Validable
 | 
					class Permission extends Model
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The resource name for this model when it is transformed into an
 | 
					     * The resource name for this model when it is transformed into an
 | 
				
			||||||
@ -20,7 +20,6 @@ class Permission extends Validable
 | 
				
			|||||||
    const ACTION_CONTROL_START = 'control.start';
 | 
					    const ACTION_CONTROL_START = 'control.start';
 | 
				
			||||||
    const ACTION_CONTROL_STOP = 'control.stop';
 | 
					    const ACTION_CONTROL_STOP = 'control.stop';
 | 
				
			||||||
    const ACTION_CONTROL_RESTART = 'control.restart';
 | 
					    const ACTION_CONTROL_RESTART = 'control.restart';
 | 
				
			||||||
    const ACTION_CONTROL_KILL = 'control.kill';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const ACTION_DATABASE_READ = 'database.read';
 | 
					    const ACTION_DATABASE_READ = 'database.read';
 | 
				
			||||||
    const ACTION_DATABASE_CREATE = 'database.create';
 | 
					    const ACTION_DATABASE_CREATE = 'database.create';
 | 
				
			||||||
@ -38,6 +37,12 @@ class Permission extends Validable
 | 
				
			|||||||
    const ACTION_USER_UPDATE = 'user.update';
 | 
					    const ACTION_USER_UPDATE = 'user.update';
 | 
				
			||||||
    const ACTION_USER_DELETE = 'user.delete';
 | 
					    const ACTION_USER_DELETE = 'user.delete';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const ACTION_BACKUP_READ = 'backup.read';
 | 
				
			||||||
 | 
					    const ACTION_BACKUP_CREATE = 'backup.create';
 | 
				
			||||||
 | 
					    const ACTION_BACKUP_UPDATE = 'backup.update';
 | 
				
			||||||
 | 
					    const ACTION_BACKUP_DELETE = 'backup.delete';
 | 
				
			||||||
 | 
					    const ACTION_BACKUP_DOWNLOAD = 'backup.download';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const ACTION_ALLOCATION_READ = 'allocation.read';
 | 
					    const ACTION_ALLOCATION_READ = 'allocation.read';
 | 
				
			||||||
    const ACTION_ALLOCIATION_UPDATE = 'allocation.update';
 | 
					    const ACTION_ALLOCIATION_UPDATE = 'allocation.update';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -98,105 +103,100 @@ class Permission extends Validable
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    protected static $permissions = [
 | 
					    protected static $permissions = [
 | 
				
			||||||
        'websocket' => [
 | 
					        'websocket' => [
 | 
				
			||||||
            // Allows the user to connect to the server websocket, this will give them
 | 
					            'description' => 'Allows the user to connect to the server websocket, giving them access to view console output and realtime server stats.',
 | 
				
			||||||
            // access to view the console output as well as realtime server stats (CPU
 | 
					            'keys' => [
 | 
				
			||||||
            // and Memory usage).
 | 
					                '*' => 'Gives user full read access to the websocket.',
 | 
				
			||||||
            '*',
 | 
					            ],
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        'control' => [
 | 
					        'control' => [
 | 
				
			||||||
            // Allows the user to send data to the server console process. A user with this
 | 
					            'description' => 'Permissions that control a user\'s ability to control the power state of a server, or send commands.',
 | 
				
			||||||
            // permission will not be able to stop the server directly by issuing the specified
 | 
					            'keys' => [
 | 
				
			||||||
            // stop command for the Egg, however depending on plugins and server configuration
 | 
					                'console' => 'Allows a user to send commands to the server instance via the console.',
 | 
				
			||||||
            // they may still be able to control the server power state.
 | 
					                'start' => 'Allows a user to start the server if it is stopped.',
 | 
				
			||||||
            'console', // power.send-command
 | 
					                'stop' => 'Allows a user to stop a server if it is running.',
 | 
				
			||||||
 | 
					                'restart' => 'Allows a user to perform a server restart. This allows them to start the server if it is offline, but not put the server in a completely stopped state.',
 | 
				
			||||||
            // Allows the user to start/stop/restart/kill the server process.
 | 
					            ],
 | 
				
			||||||
            'start', // power.power-start
 | 
					 | 
				
			||||||
            'stop', // power.power-stop
 | 
					 | 
				
			||||||
            'restart', // power.power-restart
 | 
					 | 
				
			||||||
            'kill', // power.power-kill
 | 
					 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        'user' => [
 | 
					        'user' => [
 | 
				
			||||||
            // Allows a user to create a new user assigned to the server. They will not be able
 | 
					            'description' => 'Permissions that allow a user to manage other subusers on a server. They will never be able to edit their own account, or assign permissions they do not have themselves.',
 | 
				
			||||||
            // to assign any permissions they do not already have on their account as well.
 | 
					            'keys' => [
 | 
				
			||||||
            'create', // subuser.create-subuser
 | 
					                'create' => 'Allows a user to create new subusers for the server.',
 | 
				
			||||||
            'read', // subuser.list-subusers, subuser.view-subuser
 | 
					                'read' => 'Allows the user to view subusers and their permissions for the server.',
 | 
				
			||||||
            'update', // subuser.edit-subuser
 | 
					                'update' => 'Allows a user to modify other subusers.',
 | 
				
			||||||
            'delete', // subuser.delete-subuser
 | 
					                'delete' => 'Allows a user to delete a subuser from the server.',
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        'file' => [
 | 
					        'file' => [
 | 
				
			||||||
            // Allows a user to create additional files and folders either via the Panel,
 | 
					            'description' => 'Permissions that control a user\'s ability to modify the filesystem for this server.',
 | 
				
			||||||
            // or via a direct upload.
 | 
					            'keys' => [
 | 
				
			||||||
            'create', // files.create-files, files.upload-files, files.copy-files, files.move-files
 | 
					                'create' => 'Allows a user to create additional files and folders via the Panel or direct upload.',
 | 
				
			||||||
 | 
					                'read' => 'Allows a user to view the contents of a directory and read the contents of a file. Users with this permission can also download files.',
 | 
				
			||||||
 | 
					                'update' => 'Allows a user to update the contents of an existing file or directory.',
 | 
				
			||||||
 | 
					                'delete' => 'Allows a user to delete files or directories.',
 | 
				
			||||||
 | 
					                'archive' => 'Allows a user to archive the contents of a directory as well as decompress existing archives on the system.',
 | 
				
			||||||
 | 
					                'sftp' => 'Allows a user to connect to SFTP and manage server files using the other assigned file permissions.',
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Allows a user to view the contents of a directory as well as read the contents
 | 
					        'backup' => [
 | 
				
			||||||
            // of a given file. A user with this permission will be able to download files
 | 
					            'description' => 'Permissions that control a user\'s ability to generate and manage server backups.',
 | 
				
			||||||
            // as well.
 | 
					            'keys' => [
 | 
				
			||||||
            'read', // files.list-files, files.download-files
 | 
					                'create' => 'Allows a user to create new backups for this server.',
 | 
				
			||||||
 | 
					                'read' => 'Allows a user to view all backups that exist for this server.',
 | 
				
			||||||
            // Allows a user to update the contents of an existing file or directory.
 | 
					                'update' => '',
 | 
				
			||||||
            'update', // files.edit-files, files.save-files
 | 
					                'delete' => 'Allows a user to remove backups from the system.',
 | 
				
			||||||
 | 
					                'download' => 'Allows a user to download backups.',
 | 
				
			||||||
            // Allows a user to delete a file or directory.
 | 
					            ],
 | 
				
			||||||
            'delete', // files.delete-files
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Allows a user to archive the contents of a directory as well as decompress existing
 | 
					 | 
				
			||||||
            // archives on the system.
 | 
					 | 
				
			||||||
            'archive', // files.compress-files, files.decompress-files
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Allows the user to connect and manage server files using their account
 | 
					 | 
				
			||||||
            // credentials and a SFTP client.
 | 
					 | 
				
			||||||
            'sftp', // files.access-sftp
 | 
					 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Controls permissions for editing or viewing a server's allocations.
 | 
					        // Controls permissions for editing or viewing a server's allocations.
 | 
				
			||||||
        'allocation' => [
 | 
					        'allocation' => [
 | 
				
			||||||
            'read', // server.view-allocations
 | 
					            'description' => 'Permissions that control a user\'s ability to modify the port allocations for this server.',
 | 
				
			||||||
            'update', // server.edit-allocation
 | 
					            'keys' => [
 | 
				
			||||||
 | 
					                'read' => 'Allows a user to view the allocations assigned to this server.',
 | 
				
			||||||
 | 
					                'update' => 'Allows a user to modify the allocations assigned to this server.',
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Controls permissions for editing or viewing a server's startup parameters.
 | 
					        // Controls permissions for editing or viewing a server's startup parameters.
 | 
				
			||||||
        'startup' => [
 | 
					        'startup' => [
 | 
				
			||||||
            'read', // server.view-startup
 | 
					            'description' => 'Permissions that control a user\'s ability to view this server\'s startup parameters.',
 | 
				
			||||||
            'update', // server.edit-startup
 | 
					            'keys' => [
 | 
				
			||||||
 | 
					                'read' => '',
 | 
				
			||||||
 | 
					                'update' => '',
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        'database' => [
 | 
					        'database' => [
 | 
				
			||||||
            // Allows a user to create a new database for a server.
 | 
					            'description' => 'Permissions that control a user\'s access to the database management for this server.',
 | 
				
			||||||
            'create', // database.create-database
 | 
					            'keys' => [
 | 
				
			||||||
 | 
					                'create' => 'Allows a user to create a new database for this server.',
 | 
				
			||||||
            // Allows a user to view the databases associated with the server. If they do not also
 | 
					                'read' => 'Allows a user to view the database associated with this server.',
 | 
				
			||||||
            // have the view_password permission they will only be able to see the connection address
 | 
					                'update' => 'Allows a user to rotate the password on a database instance. If the user does not have the view_password permission they will not see the updated password.',
 | 
				
			||||||
            // and the name of the user.
 | 
					                'delete' => 'Allows a user to remove a database instance from this server.',
 | 
				
			||||||
            'read', // database.view-databases
 | 
					                'view_password' => 'Allows a user to view the password associated with a database instance for this server.',
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
            // Allows a user to rotate the password on a database instance. If the user does not
 | 
					 | 
				
			||||||
            // alow have the view_password permission they will not be able to see the updated password
 | 
					 | 
				
			||||||
            // anywhere, but it will still be rotated.
 | 
					 | 
				
			||||||
            'update', // database.reset-db-password
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Allows a user to delete a database instance.
 | 
					 | 
				
			||||||
            'delete', // database.delete-database
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Allows a user to view the password associated with a database instance for the
 | 
					 | 
				
			||||||
            // server. Note that a user without this permission may still be able to access these
 | 
					 | 
				
			||||||
            // credentials by viewing files or the console.
 | 
					 | 
				
			||||||
            'view_password', // database.reset-db-password
 | 
					 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        'schedule' => [
 | 
					        'schedule' => [
 | 
				
			||||||
            'create', // task.create-schedule
 | 
					            'description' => 'Permissions that control a user\'s access to the schedule management for this server.',
 | 
				
			||||||
            'read', // task.view-schedule, task.list-schedules
 | 
					            'keys' => [
 | 
				
			||||||
            'update', // task.edit-schedule, task.queue-schedule, task.toggle-schedule
 | 
					                'create' => '', // task.create-schedule
 | 
				
			||||||
            'delete', // task.delete-schedule
 | 
					                'read' => '', // task.view-schedule, task.list-schedules
 | 
				
			||||||
 | 
					                'update' => '', // task.edit-schedule, task.queue-schedule, task.toggle-schedule
 | 
				
			||||||
 | 
					                'delete' => '', // task.delete-schedule
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        'settings' => [
 | 
					        'settings' => [
 | 
				
			||||||
            'rename',
 | 
					            'description' => 'Permissions that control a user\'s access to the settings for this server.',
 | 
				
			||||||
            'reinstall',
 | 
					            'keys' => [
 | 
				
			||||||
 | 
					                'rename' => '',
 | 
				
			||||||
 | 
					                'reinstall' => '',
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -25,7 +25,7 @@ use Pterodactyl\Contracts\Extensions\HashidsInterface;
 | 
				
			|||||||
 * @property \Pterodactyl\Models\Server $server
 | 
					 * @property \Pterodactyl\Models\Server $server
 | 
				
			||||||
 * @property \Pterodactyl\Models\Task[]|\Illuminate\Support\Collection $tasks
 | 
					 * @property \Pterodactyl\Models\Task[]|\Illuminate\Support\Collection $tasks
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class Schedule extends Validable
 | 
					class Schedule extends Model
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The resource name for this model when it is transformed into an
 | 
					     * The resource name for this model when it is transformed into an
 | 
				
			||||||
 | 
				
			|||||||
@ -23,6 +23,7 @@ use Znck\Eloquent\Traits\BelongsToThrough;
 | 
				
			|||||||
 * @property int $disk
 | 
					 * @property int $disk
 | 
				
			||||||
 * @property int $io
 | 
					 * @property int $io
 | 
				
			||||||
 * @property int $cpu
 | 
					 * @property int $cpu
 | 
				
			||||||
 | 
					 * @property string $threads
 | 
				
			||||||
 * @property bool $oom_disabled
 | 
					 * @property bool $oom_disabled
 | 
				
			||||||
 * @property int $allocation_id
 | 
					 * @property int $allocation_id
 | 
				
			||||||
 * @property int $nest_id
 | 
					 * @property int $nest_id
 | 
				
			||||||
@ -50,10 +51,14 @@ use Znck\Eloquent\Traits\BelongsToThrough;
 | 
				
			|||||||
 * @property \Pterodactyl\Models\Location $location
 | 
					 * @property \Pterodactyl\Models\Location $location
 | 
				
			||||||
 * @property \Pterodactyl\Models\DaemonKey $key
 | 
					 * @property \Pterodactyl\Models\DaemonKey $key
 | 
				
			||||||
 * @property \Pterodactyl\Models\DaemonKey[]|\Illuminate\Database\Eloquent\Collection $keys
 | 
					 * @property \Pterodactyl\Models\DaemonKey[]|\Illuminate\Database\Eloquent\Collection $keys
 | 
				
			||||||
 | 
					 * @property \Pterodactyl\Models\ServerTransfer $transfer
 | 
				
			||||||
 | 
					 * @property \Pterodactyl\Models\Backup[]|\Illuminate\Database\Eloquent\Collection $backups
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class Server extends Validable
 | 
					class Server extends Model
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    use BelongsToThrough, Notifiable, Searchable;
 | 
					    use BelongsToThrough;
 | 
				
			||||||
 | 
					    use Notifiable;
 | 
				
			||||||
 | 
					    use Searchable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The resource name for this model when it is transformed into an
 | 
					     * The resource name for this model when it is transformed into an
 | 
				
			||||||
@ -61,6 +66,10 @@ class Server extends Validable
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    const RESOURCE_NAME = 'server';
 | 
					    const RESOURCE_NAME = 'server';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const STATUS_INSTALLING = 0;
 | 
				
			||||||
 | 
					    const STATUS_INSTALLED = 1;
 | 
				
			||||||
 | 
					    const STATUS_INSTALL_FAILED = 2;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The table associated with the model.
 | 
					     * The table associated with the model.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@ -105,6 +114,7 @@ class Server extends Validable
 | 
				
			|||||||
        'swap' => 'required|numeric|min:-1',
 | 
					        'swap' => 'required|numeric|min:-1',
 | 
				
			||||||
        'io' => 'required|numeric|between:10,1000',
 | 
					        'io' => 'required|numeric|between:10,1000',
 | 
				
			||||||
        'cpu' => 'required|numeric|min:0',
 | 
					        'cpu' => 'required|numeric|min:0',
 | 
				
			||||||
 | 
					        'threads' => 'nullable|regex:/^[0-9-,]+$/',
 | 
				
			||||||
        'oom_disabled' => 'sometimes|boolean',
 | 
					        'oom_disabled' => 'sometimes|boolean',
 | 
				
			||||||
        'disk' => 'required|numeric|min:0',
 | 
					        'disk' => 'required|numeric|min:0',
 | 
				
			||||||
        'allocation_id' => 'required|bail|unique:servers|exists:allocations,id',
 | 
					        'allocation_id' => 'required|bail|unique:servers|exists:allocations,id',
 | 
				
			||||||
@ -177,7 +187,7 @@ class Server extends Validable
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    public function getAllocationMappings(): array
 | 
					    public function getAllocationMappings(): array
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->allocations->groupBy('ip')->map(function ($item) {
 | 
					        return $this->allocations->where('node_id', $this->node_id)->groupBy('ip')->map(function ($item) {
 | 
				
			||||||
            return $item->pluck('port');
 | 
					            return $item->pluck('port');
 | 
				
			||||||
        })->toArray();
 | 
					        })->toArray();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -331,4 +341,22 @@ class Server extends Validable
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->hasMany(DaemonKey::class);
 | 
					        return $this->hasMany(DaemonKey::class);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Returns the associated server transfer.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return \Illuminate\Database\Eloquent\Relations\HasOne
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function transfer()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->hasOne(ServerTransfer::class)->orderByDesc('id');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @return \Illuminate\Database\Eloquent\Relations\HasMany
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function backups()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->hasMany(Backup::class);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										81
									
								
								app/Models/ServerTransfer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								app/Models/ServerTransfer.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pterodactyl\Models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @property int $id
 | 
				
			||||||
 | 
					 * @property int $server_id
 | 
				
			||||||
 | 
					 * @property int $old_node
 | 
				
			||||||
 | 
					 * @property int $new_node
 | 
				
			||||||
 | 
					 * @property int $old_allocation
 | 
				
			||||||
 | 
					 * @property int $new_allocation
 | 
				
			||||||
 | 
					 * @property string $old_additional_allocations
 | 
				
			||||||
 | 
					 * @property string $new_additional_allocations
 | 
				
			||||||
 | 
					 * @property bool $successful
 | 
				
			||||||
 | 
					 * @property \Carbon\Carbon $created_at
 | 
				
			||||||
 | 
					 * @property \Carbon\Carbon $updated_at
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @property \Pterodactyl\Models\Server $server
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class ServerTransfer extends Validable
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The resource name for this model when it is transformed into an
 | 
				
			||||||
 | 
					     * API representation using fractal.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    const RESOURCE_NAME = 'server_transfer';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * The table associated with the model.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @var string
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected $table = 'server_transfers';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Fields that are not mass assignable.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @var array
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected $guarded = ['id', 'created_at', 'updated_at'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Cast values to correct type.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @var array
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected $casts = [
 | 
				
			||||||
 | 
					        'server_id' => 'int',
 | 
				
			||||||
 | 
					        'old_node' => 'int',
 | 
				
			||||||
 | 
					        'new_node' => 'int',
 | 
				
			||||||
 | 
					        'old_allocation' => 'int',
 | 
				
			||||||
 | 
					        'new_allocation' => 'int',
 | 
				
			||||||
 | 
					        'old_additional_allocations' => 'string',
 | 
				
			||||||
 | 
					        'new_additional_allocations' => 'string',
 | 
				
			||||||
 | 
					        'successful' => 'bool',
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var array
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static $validationRules = [
 | 
				
			||||||
 | 
					        'server_id' => 'required|numeric|exists:servers,id',
 | 
				
			||||||
 | 
					        'old_node' => 'required|numeric',
 | 
				
			||||||
 | 
					        'new_node' => 'required|numeric',
 | 
				
			||||||
 | 
					        'old_allocation' => 'required|numeric',
 | 
				
			||||||
 | 
					        'new_allocation' => 'required|numeric',
 | 
				
			||||||
 | 
					        'old_additional_allocations' => 'nullable',
 | 
				
			||||||
 | 
					        'new_additional_allocations' => 'nullable',
 | 
				
			||||||
 | 
					        'successful' => 'sometimes|boolean',
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Gets the server associated with a server transfer.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function server()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->belongsTo(Server::class);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -2,7 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace Pterodactyl\Models;
 | 
					namespace Pterodactyl\Models;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Setting extends Validable
 | 
					class Setting extends Model
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The table associated with the model.
 | 
					     * The table associated with the model.
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,7 @@ use Illuminate\Notifications\Notifiable;
 | 
				
			|||||||
 * @property \Pterodactyl\Models\User $user
 | 
					 * @property \Pterodactyl\Models\User $user
 | 
				
			||||||
 * @property \Pterodactyl\Models\Server $server
 | 
					 * @property \Pterodactyl\Models\Server $server
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class Subuser extends Validable
 | 
					class Subuser extends Model
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    use Notifiable;
 | 
					    use Notifiable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -22,7 +22,7 @@ use Pterodactyl\Contracts\Extensions\HashidsInterface;
 | 
				
			|||||||
 * @property \Pterodactyl\Models\Schedule $schedule
 | 
					 * @property \Pterodactyl\Models\Schedule $schedule
 | 
				
			||||||
 * @property \Pterodactyl\Models\Server $server
 | 
					 * @property \Pterodactyl\Models\Server $server
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class Task extends Validable
 | 
					class Task extends Model
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    use BelongsToThrough;
 | 
					    use BelongsToThrough;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -37,17 +37,20 @@ use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification;
 | 
				
			|||||||
 *
 | 
					 *
 | 
				
			||||||
 * @property string $name
 | 
					 * @property string $name
 | 
				
			||||||
 * @property \Pterodactyl\Models\ApiKey[]|\Illuminate\Database\Eloquent\Collection $apiKeys
 | 
					 * @property \Pterodactyl\Models\ApiKey[]|\Illuminate\Database\Eloquent\Collection $apiKeys
 | 
				
			||||||
 * @property \Pterodactyl\Models\Permission[]|\Illuminate\Database\Eloquent\Collection $permissions
 | 
					 | 
				
			||||||
 * @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers
 | 
					 * @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers
 | 
				
			||||||
 * @property \Pterodactyl\Models\Subuser[]|\Illuminate\Database\Eloquent\Collection $subuserOf
 | 
					 | 
				
			||||||
 * @property \Pterodactyl\Models\DaemonKey[]|\Illuminate\Database\Eloquent\Collection $keys
 | 
					 * @property \Pterodactyl\Models\DaemonKey[]|\Illuminate\Database\Eloquent\Collection $keys
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class User extends Validable implements
 | 
					class User extends Model implements
 | 
				
			||||||
    AuthenticatableContract,
 | 
					    AuthenticatableContract,
 | 
				
			||||||
    AuthorizableContract,
 | 
					    AuthorizableContract,
 | 
				
			||||||
    CanResetPasswordContract
 | 
					    CanResetPasswordContract
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    use Authenticatable, Authorizable, AvailableLanguages, CanResetPassword, Notifiable, Searchable;
 | 
					    use Authenticatable;
 | 
				
			||||||
 | 
					    use Authorizable;
 | 
				
			||||||
 | 
					    use AvailableLanguages;
 | 
				
			||||||
 | 
					    use CanResetPassword;
 | 
				
			||||||
 | 
					    use Notifiable;
 | 
				
			||||||
 | 
					    use Searchable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const USER_LEVEL_USER = 0;
 | 
					    const USER_LEVEL_USER = 0;
 | 
				
			||||||
    const USER_LEVEL_ADMIN = 1;
 | 
					    const USER_LEVEL_ADMIN = 1;
 | 
				
			||||||
@ -220,16 +223,6 @@ class User extends Validable implements
 | 
				
			|||||||
        return trim($this->name_first . ' ' . $this->name_last);
 | 
					        return trim($this->name_first . ' ' . $this->name_last);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns all permissions that a user has.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return \Illuminate\Database\Eloquent\Relations\HasManyThrough
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function permissions()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        return $this->hasManyThrough(Permission::class, Subuser::class);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Returns all servers that a user owns.
 | 
					     * Returns all servers that a user owns.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@ -240,16 +233,6 @@ class User extends Validable implements
 | 
				
			|||||||
        return $this->hasMany(Server::class, 'owner_id');
 | 
					        return $this->hasMany(Server::class, 'owner_id');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Return all servers that user is listed as a subuser of directly.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function subuserOf()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        return $this->hasMany(Subuser::class);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Return all of the daemon keys that a user belongs to.
 | 
					     * Return all of the daemon keys that a user belongs to.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
 | 
				
			|||||||
@ -1,21 +1,29 @@
 | 
				
			|||||||
<?php
 | 
					<?php
 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Pterodactyl - Panel
 | 
					 | 
				
			||||||
 * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * This software is licensed under the terms of the MIT license.
 | 
					 | 
				
			||||||
 * https://opensource.org/licenses/MIT
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Pterodactyl\Policies;
 | 
					namespace Pterodactyl\Policies;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use Cache;
 | 
					use Carbon\Carbon;
 | 
				
			||||||
use Carbon;
 | 
					 | 
				
			||||||
use Pterodactyl\Models\User;
 | 
					use Pterodactyl\Models\User;
 | 
				
			||||||
use Pterodactyl\Models\Server;
 | 
					use Pterodactyl\Models\Server;
 | 
				
			||||||
 | 
					use Illuminate\Contracts\Cache\Repository as CacheRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ServerPolicy
 | 
					class ServerPolicy
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Illuminate\Contracts\Cache\Repository
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $cache;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * ServerPolicy constructor.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Illuminate\Contracts\Cache\Repository $cache
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function __construct(CacheRepository $cache)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->cache = $cache;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Checks if the user has the given permission on/for the server.
 | 
					     * Checks if the user has the given permission on/for the server.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@ -26,13 +34,16 @@ class ServerPolicy
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    protected function checkPermission(User $user, Server $server, $permission)
 | 
					    protected function checkPermission(User $user, Server $server, $permission)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $permissions = Cache::remember('ServerPolicy.' . $user->uuid . $server->uuid, Carbon::now()->addSeconds(5), function () use ($user, $server) {
 | 
					        $key = sprintf('ServerPolicy.%s.%s', $user->uuid, $server->uuid);
 | 
				
			||||||
            return $user->permissions()->server($server)->get()->transform(function ($item) {
 | 
					
 | 
				
			||||||
                return $item->permission;
 | 
					        $permissions = $this->cache->remember($key, Carbon::now()->addSeconds(5), function () use ($user, $server) {
 | 
				
			||||||
            })->values();
 | 
					            /** @var \Pterodactyl\Models\Subuser|null $subuser */
 | 
				
			||||||
 | 
					            $subuser = $server->subusers()->where('user_id', $user->id)->first();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return $subuser ? $subuser->permissions : [];
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $permissions->search($permission, true) !== false;
 | 
					        return in_array($permission, $permissions);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										16
									
								
								app/Repositories/Eloquent/BackupRepository.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								app/Repositories/Eloquent/BackupRepository.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pterodactyl\Repositories\Eloquent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Backup;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BackupRepository extends EloquentRepository
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @return string
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function model()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return Backup::class;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -177,6 +177,18 @@ abstract class EloquentRepository extends Repository implements RepositoryInterf
 | 
				
			|||||||
        return ($this->withFresh) ? $instance->fresh() : $saved;
 | 
					        return ($this->withFresh) ? $instance->fresh() : $saved;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Update a model using the attributes passed.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param array|\Closure $attributes
 | 
				
			||||||
 | 
					     * @param array $values
 | 
				
			||||||
 | 
					     * @return int
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function updateWhere($attributes, array $values)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->getBuilder()->where($attributes)->update($values);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Perform a mass update where matching records are updated using whereIn.
 | 
					     * Perform a mass update where matching records are updated using whereIn.
 | 
				
			||||||
     * This does not perform any model data validation.
 | 
					     * This does not perform any model data validation.
 | 
				
			||||||
 | 
				
			|||||||
@ -174,6 +174,23 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa
 | 
				
			|||||||
        })->values();
 | 
					        })->values();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Returns a node with the given id with the Node's resource usage.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param int $node_id
 | 
				
			||||||
 | 
					     * @return Node
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function getNodeWithResourceUsage(int $node_id): Node
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $instance = $this->getBuilder()
 | 
				
			||||||
 | 
					            ->select(['nodes.id', 'nodes.fqdn', 'nodes.scheme', 'nodes.daemonSecret', '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();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Return the IDs of all nodes that exist in the provided locations and have the space
 | 
					     * Return the IDs of all nodes that exist in the provided locations and have the space
 | 
				
			||||||
     * available to support the additional disk and memory provided.
 | 
					     * available to support the additional disk and memory provided.
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,6 @@
 | 
				
			|||||||
namespace Pterodactyl\Repositories\Eloquent;
 | 
					namespace Pterodactyl\Repositories\Eloquent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use Pterodactyl\Models\Subuser;
 | 
					use Pterodactyl\Models\Subuser;
 | 
				
			||||||
use Illuminate\Support\Collection;
 | 
					 | 
				
			||||||
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
 | 
					use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
 | 
				
			||||||
use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
 | 
					use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -20,19 +19,27 @@ class SubuserRepository extends EloquentRepository implements SubuserRepositoryI
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Returns the subusers for the given server instance with the associated user
 | 
					     * Returns a subuser model for the given user and server combination. If no record
 | 
				
			||||||
     * and permission relationships pre-loaded.
 | 
					     * exists an exception will be thrown.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param int $server
 | 
					     * @param int $server
 | 
				
			||||||
     * @return \Illuminate\Support\Collection
 | 
					     * @param string $uuid
 | 
				
			||||||
 | 
					     * @return \Pterodactyl\Models\Subuser
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function getSubusersForServer(int $server): Collection
 | 
					    public function getUserForServer(int $server, string $uuid): Subuser
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->getBuilder()
 | 
					        /** @var \Pterodactyl\Models\Subuser $model */
 | 
				
			||||||
            ->with('user', 'permissions')
 | 
					        $model = $this->getBuilder()
 | 
				
			||||||
            ->where('server_id', $server)
 | 
					            ->with('server', 'user')
 | 
				
			||||||
            ->get()
 | 
					            ->select('subusers.*')
 | 
				
			||||||
            ->toBase();
 | 
					            ->join('users', 'users.id', '=', 'subusers.user_id')
 | 
				
			||||||
 | 
					            ->where('subusers.server_id', $server)
 | 
				
			||||||
 | 
					            ->where('users.uuid', $uuid)
 | 
				
			||||||
 | 
					            ->firstOrFail();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $model;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 | 
				
			|||||||
@ -29,7 +29,7 @@ class UserRepository extends EloquentRepository implements UserRepositoryInterfa
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    public function getAllUsersWithCounts(): LengthAwarePaginator
 | 
					    public function getAllUsersWithCounts(): LengthAwarePaginator
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->getBuilder()->withCount('servers', 'subuserOf')
 | 
					        return $this->getBuilder()->withCount('servers')
 | 
				
			||||||
            ->search($this->getSearchTerm())
 | 
					            ->search($this->getSearchTerm())
 | 
				
			||||||
            ->paginate(50, $this->getColumns());
 | 
					            ->paginate(50, $this->getColumns());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										63
									
								
								app/Repositories/Wings/DaemonBackupRepository.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								app/Repositories/Wings/DaemonBackupRepository.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,63 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pterodactyl\Repositories\Wings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Webmozart\Assert\Assert;
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Backup;
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Server;
 | 
				
			||||||
 | 
					use Psr\Http\Message\ResponseInterface;
 | 
				
			||||||
 | 
					use GuzzleHttp\Exception\TransferException;
 | 
				
			||||||
 | 
					use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DaemonBackupRepository extends DaemonRepository
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Tells the remote Daemon to begin generating a backup for the server.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Models\Backup $backup
 | 
				
			||||||
 | 
					     * @return \Psr\Http\Message\ResponseInterface
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function backup(Backup $backup): ResponseInterface
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Assert::isInstanceOf($this->server, Server::class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            return $this->getHttpClient()->post(
 | 
				
			||||||
 | 
					                sprintf('/api/servers/%s/backup', $this->server->uuid),
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    'json' => [
 | 
				
			||||||
 | 
					                        'uuid' => $backup->uuid,
 | 
				
			||||||
 | 
					                        'ignored_files' => explode(PHP_EOL, $backup->ignored_files),
 | 
				
			||||||
 | 
					                    ],
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        } catch (TransferException $exception) {
 | 
				
			||||||
 | 
					            throw new DaemonConnectionException($exception);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Returns a stream of a backup's contents from the Wings instance so that we
 | 
				
			||||||
 | 
					     * do not need to send the user directly to the Daemon.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param string $backup
 | 
				
			||||||
 | 
					     * @return \Psr\Http\Message\ResponseInterface
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function getBackup(string $backup): ResponseInterface
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Assert::isInstanceOf($this->server, Server::class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            return $this->getHttpClient()->get(
 | 
				
			||||||
 | 
					                sprintf('/api/servers/%s/backup/%s', $this->server->uuid, $backup),
 | 
				
			||||||
 | 
					                ['stream' => true]
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        } catch (TransferException $exception) {
 | 
				
			||||||
 | 
					            throw new DaemonConnectionException($exception);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -2,7 +2,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace Pterodactyl\Repositories\Wings;
 | 
					namespace Pterodactyl\Repositories\Wings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use BadMethodCallException;
 | 
					 | 
				
			||||||
use Webmozart\Assert\Assert;
 | 
					use Webmozart\Assert\Assert;
 | 
				
			||||||
use Pterodactyl\Models\Server;
 | 
					use Pterodactyl\Models\Server;
 | 
				
			||||||
use GuzzleHttp\Exception\TransferException;
 | 
					use GuzzleHttp\Exception\TransferException;
 | 
				
			||||||
@ -13,7 +12,6 @@ class DaemonServerRepository extends DaemonRepository
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Returns details about a server from the Daemon instance.
 | 
					     * Returns details about a server from the Daemon instance.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @return array
 | 
					 | 
				
			||||||
     * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
 | 
					     * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function getDetails(): array
 | 
					    public function getDetails(): array
 | 
				
			||||||
@ -89,10 +87,20 @@ class DaemonServerRepository extends DaemonRepository
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Reinstall a server on the daemon.
 | 
					     * Reinstall a server on the daemon.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function reinstall(): void
 | 
					    public function reinstall(): void
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        throw new BadMethodCallException('Method is not implemented.');
 | 
					        Assert::isInstanceOf($this->server, Server::class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            $this->getHttpClient()->post(sprintf(
 | 
				
			||||||
 | 
					                '/api/servers/%s/reinstall', $this->server->uuid
 | 
				
			||||||
 | 
					            ));
 | 
				
			||||||
 | 
					        } catch (TransferException $exception) {
 | 
				
			||||||
 | 
					            throw new DaemonConnectionException($exception);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -116,4 +124,24 @@ class DaemonServerRepository extends DaemonRepository
 | 
				
			|||||||
            throw new DaemonConnectionException($exception);
 | 
					            throw new DaemonConnectionException($exception);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Requests the daemon to create a full archive of the server.
 | 
				
			||||||
 | 
					     * Once the daemon is finished they will send a POST request to
 | 
				
			||||||
 | 
					     * "/api/remote/servers/{uuid}/archive" with a boolean.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @throws DaemonConnectionException
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function requestArchive(): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Assert::isInstanceOf($this->server, Server::class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            $this->getHttpClient()->post(sprintf(
 | 
				
			||||||
 | 
					                '/api/servers/%s/archive', $this->server->uuid
 | 
				
			||||||
 | 
					            ));
 | 
				
			||||||
 | 
					        } catch (TransferException $exception) {
 | 
				
			||||||
 | 
					            throw new DaemonConnectionException($exception);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										35
									
								
								app/Repositories/Wings/DaemonTransferRepository.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								app/Repositories/Wings/DaemonTransferRepository.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pterodactyl\Repositories\Wings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Node;
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Server;
 | 
				
			||||||
 | 
					use GuzzleHttp\Exception\TransferException;
 | 
				
			||||||
 | 
					use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DaemonTransferRepository extends DaemonRepository
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @param Server $server
 | 
				
			||||||
 | 
					     * @param array $data
 | 
				
			||||||
 | 
					     * @param Node $node
 | 
				
			||||||
 | 
					     * @param string $token
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @throws DaemonConnectionException
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function notify(Server $server, array $data, Node $node, string $token): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            $this->getHttpClient()->post('/api/transfer', [
 | 
				
			||||||
 | 
					                'json' => [
 | 
				
			||||||
 | 
					                    'server_id' => $server->uuid,
 | 
				
			||||||
 | 
					                    'url' => $node->getConnectionAddress() . sprintf('/api/servers/%s/archive', $server->uuid),
 | 
				
			||||||
 | 
					                    'token' => 'Bearer ' . $token,
 | 
				
			||||||
 | 
					                    'server' => $data,
 | 
				
			||||||
 | 
					                ],
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					        } catch (TransferException $exception) {
 | 
				
			||||||
 | 
					            throw new DaemonConnectionException($exception);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										91
									
								
								app/Services/Backups/InitiateBackupService.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								app/Services/Backups/InitiateBackupService.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,91 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pterodactyl\Services\Backups;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Ramsey\Uuid\Uuid;
 | 
				
			||||||
 | 
					use Carbon\CarbonImmutable;
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Backup;
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Server;
 | 
				
			||||||
 | 
					use Illuminate\Database\ConnectionInterface;
 | 
				
			||||||
 | 
					use Pterodactyl\Repositories\Eloquent\BackupRepository;
 | 
				
			||||||
 | 
					use Pterodactyl\Repositories\Wings\DaemonBackupRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class InitiateBackupService
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var string|null
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $ignoredFiles;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Repositories\Eloquent\BackupRepository
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $repository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Illuminate\Database\ConnectionInterface
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $connection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Repositories\Wings\DaemonBackupRepository
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $daemonBackupRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * InitiateBackupService constructor.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Repositories\Eloquent\BackupRepository $repository
 | 
				
			||||||
 | 
					     * @param \Illuminate\Database\ConnectionInterface $connection
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Repositories\Wings\DaemonBackupRepository $daemonBackupRepository
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        BackupRepository $repository,
 | 
				
			||||||
 | 
					        ConnectionInterface $connection,
 | 
				
			||||||
 | 
					        DaemonBackupRepository $daemonBackupRepository
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        $this->repository = $repository;
 | 
				
			||||||
 | 
					        $this->connection = $connection;
 | 
				
			||||||
 | 
					        $this->daemonBackupRepository = $daemonBackupRepository;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Sets the files to be ignored by this backup.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param string|null $ignored
 | 
				
			||||||
 | 
					     * @return $this
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function setIgnoredFiles(?string $ignored)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->ignoredFiles = $ignored;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Initiates the backup process for a server on the daemon.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Models\Server $server
 | 
				
			||||||
 | 
					     * @param string|null $name
 | 
				
			||||||
 | 
					     * @return \Pterodactyl\Models\Backup
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @throws \Throwable
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function handle(Server $server, string $name = null): Backup
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return $this->connection->transaction(function () use ($server, $name) {
 | 
				
			||||||
 | 
					            /** @var \Pterodactyl\Models\Backup $backup */
 | 
				
			||||||
 | 
					            $backup = $this->repository->create([
 | 
				
			||||||
 | 
					                'server_id' => $server->id,
 | 
				
			||||||
 | 
					                'uuid' => Uuid::uuid4()->toString(),
 | 
				
			||||||
 | 
					                'name' => trim($name) ?: sprintf('Backup at %s', CarbonImmutable::now()->toDateTimeString()),
 | 
				
			||||||
 | 
					                'ignored_files' => $this->ignoredFiles ?? '',
 | 
				
			||||||
 | 
					                'disk' => 'local',
 | 
				
			||||||
 | 
					            ], true, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $this->daemonBackupRepository->setServer($server)->backup($backup);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return $backup;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -98,6 +98,7 @@ class BuildModificationService
 | 
				
			|||||||
            'swap' => array_get($data, 'swap'),
 | 
					            'swap' => array_get($data, 'swap'),
 | 
				
			||||||
            'io' => array_get($data, 'io'),
 | 
					            'io' => array_get($data, 'io'),
 | 
				
			||||||
            'cpu' => array_get($data, 'cpu'),
 | 
					            'cpu' => array_get($data, 'cpu'),
 | 
				
			||||||
 | 
					            'threads' => array_get($data, 'threads'),
 | 
				
			||||||
            'disk' => array_get($data, 'disk'),
 | 
					            'disk' => array_get($data, 'disk'),
 | 
				
			||||||
            'allocation_id' => array_get($data, 'allocation_id'),
 | 
					            'allocation_id' => array_get($data, 'allocation_id'),
 | 
				
			||||||
            'database_limit' => array_get($data, 'database_limit'),
 | 
					            'database_limit' => array_get($data, 'database_limit'),
 | 
				
			||||||
 | 
				
			|||||||
@ -3,11 +3,9 @@
 | 
				
			|||||||
namespace Pterodactyl\Services\Servers;
 | 
					namespace Pterodactyl\Services\Servers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use Pterodactyl\Models\Server;
 | 
					use Pterodactyl\Models\Server;
 | 
				
			||||||
use GuzzleHttp\Exception\RequestException;
 | 
					 | 
				
			||||||
use Illuminate\Database\ConnectionInterface;
 | 
					use Illuminate\Database\ConnectionInterface;
 | 
				
			||||||
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
 | 
					use Pterodactyl\Repositories\Wings\DaemonServerRepository;
 | 
				
			||||||
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
 | 
					use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
 | 
				
			||||||
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ReinstallServerService
 | 
					class ReinstallServerService
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@ -44,28 +42,23 @@ class ReinstallServerService
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @param int|\Pterodactyl\Models\Server $server
 | 
					     * Reinstall a server on the remote daemon.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @throws \Pterodactyl\Exceptions\DisplayException
 | 
					     * @param \Pterodactyl\Models\Server $server
 | 
				
			||||||
     * @throws \Pterodactyl\Exceptions\Model\DataValidationException
 | 
					     * @return \Pterodactyl\Models\Server
 | 
				
			||||||
     * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
 | 
					     *
 | 
				
			||||||
 | 
					     * @throws \Throwable
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function reinstall($server)
 | 
					    public function reinstall(Server $server)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if (! $server instanceof Server) {
 | 
					        $this->database->transaction(function () use ($server) {
 | 
				
			||||||
            $server = $this->repository->find($server);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $this->database->beginTransaction();
 | 
					 | 
				
			||||||
            $this->repository->withoutFreshModel()->update($server->id, [
 | 
					            $this->repository->withoutFreshModel()->update($server->id, [
 | 
				
			||||||
            'installed' => 0,
 | 
					                'installed' => Server::STATUS_INSTALLING,
 | 
				
			||||||
        ], true, true);
 | 
					            ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            $this->daemonServerRepository->setServer($server)->reinstall();
 | 
					            $this->daemonServerRepository->setServer($server)->reinstall();
 | 
				
			||||||
            $this->database->commit();
 | 
					        });
 | 
				
			||||||
        } catch (RequestException $exception) {
 | 
					
 | 
				
			||||||
            throw new DaemonConnectionException($exception);
 | 
					        return $server->refresh();
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -81,6 +81,7 @@ class ServerConfigurationStructureService
 | 
				
			|||||||
                'swap' => $server->swap,
 | 
					                'swap' => $server->swap,
 | 
				
			||||||
                'io_weight' => $server->io,
 | 
					                'io_weight' => $server->io,
 | 
				
			||||||
                'cpu_limit' => $server->cpu,
 | 
					                'cpu_limit' => $server->cpu,
 | 
				
			||||||
 | 
					                'threads' => $server->threads,
 | 
				
			||||||
                'disk_space' => $server->disk,
 | 
					                'disk_space' => $server->disk,
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            'service' => [
 | 
					            'service' => [
 | 
				
			||||||
@ -130,6 +131,7 @@ class ServerConfigurationStructureService
 | 
				
			|||||||
                'swap' => (int) $server->swap,
 | 
					                'swap' => (int) $server->swap,
 | 
				
			||||||
                'io' => (int) $server->io,
 | 
					                'io' => (int) $server->io,
 | 
				
			||||||
                'cpu' => (int) $server->cpu,
 | 
					                'cpu' => (int) $server->cpu,
 | 
				
			||||||
 | 
					                'threads' => $server->threads,
 | 
				
			||||||
                'disk' => (int) $server->disk,
 | 
					                'disk' => (int) $server->disk,
 | 
				
			||||||
                'image' => $server->image,
 | 
					                'image' => $server->image,
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,6 @@ namespace Pterodactyl\Services\Servers;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use Ramsey\Uuid\Uuid;
 | 
					use Ramsey\Uuid\Uuid;
 | 
				
			||||||
use Illuminate\Support\Arr;
 | 
					use Illuminate\Support\Arr;
 | 
				
			||||||
use Pterodactyl\Models\Node;
 | 
					 | 
				
			||||||
use Pterodactyl\Models\User;
 | 
					use Pterodactyl\Models\User;
 | 
				
			||||||
use Pterodactyl\Models\Server;
 | 
					use Pterodactyl\Models\Server;
 | 
				
			||||||
use Illuminate\Support\Collection;
 | 
					use Illuminate\Support\Collection;
 | 
				
			||||||
@ -242,6 +241,7 @@ class ServerCreationService
 | 
				
			|||||||
            'disk' => Arr::get($data, 'disk'),
 | 
					            'disk' => Arr::get($data, 'disk'),
 | 
				
			||||||
            'io' => Arr::get($data, 'io'),
 | 
					            'io' => Arr::get($data, 'io'),
 | 
				
			||||||
            'cpu' => Arr::get($data, 'cpu'),
 | 
					            'cpu' => Arr::get($data, 'cpu'),
 | 
				
			||||||
 | 
					            'threads' => Arr::get($data, 'threads'),
 | 
				
			||||||
            'oom_disabled' => Arr::get($data, 'oom_disabled', true),
 | 
					            'oom_disabled' => Arr::get($data, 'oom_disabled', true),
 | 
				
			||||||
            'allocation_id' => Arr::get($data, 'allocation_id'),
 | 
					            'allocation_id' => Arr::get($data, 'allocation_id'),
 | 
				
			||||||
            'nest_id' => Arr::get($data, 'nest_id'),
 | 
					            'nest_id' => Arr::get($data, 'nest_id'),
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										46
									
								
								app/Services/Servers/TransferService.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								app/Services/Servers/TransferService.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pterodactyl\Services\Servers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Server;
 | 
				
			||||||
 | 
					use Pterodactyl\Repositories\Wings\DaemonServerRepository;
 | 
				
			||||||
 | 
					use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TransferService
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $repository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @var \Pterodactyl\Repositories\Wings\DaemonServerRepository
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private $daemonServerRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * TransferService constructor.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Repositories\Wings\DaemonServerRepository $daemonServerRepository
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $repository
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function __construct(
 | 
				
			||||||
 | 
					        DaemonServerRepository $daemonServerRepository,
 | 
				
			||||||
 | 
					        ServerRepositoryInterface $repository
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        $this->repository = $repository;
 | 
				
			||||||
 | 
					        $this->daemonServerRepository = $daemonServerRepository;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Requests an archive from the daemon.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param int|\Pterodactyl\Models\Server $server
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @throws \Throwable
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function requestArchive(Server $server)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->daemonServerRepository->setServer($server)->requestArchive();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,63 +0,0 @@
 | 
				
			|||||||
<?php
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Pterodactyl - Panel
 | 
					 | 
				
			||||||
 * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * This software is licensed under the terms of the MIT license.
 | 
					 | 
				
			||||||
 * https://opensource.org/licenses/MIT
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace Pterodactyl\Services\Subusers;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use Webmozart\Assert\Assert;
 | 
					 | 
				
			||||||
use Pterodactyl\Models\Permission;
 | 
					 | 
				
			||||||
use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class PermissionCreationService
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @var \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    protected $repository;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * PermissionCreationService constructor.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface $repository
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function __construct(PermissionRepositoryInterface $repository)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        $this->repository = $repository;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Assign permissions to a given subuser.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param int $subuser
 | 
					 | 
				
			||||||
     * @param array $permissions
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @throws \Pterodactyl\Exceptions\Model\DataValidationException
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function handle($subuser, array $permissions)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        Assert::integerish($subuser, 'First argument passed to handle must be an integer, received %s.');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $permissionMappings = Permission::getPermissions(true);
 | 
					 | 
				
			||||||
        $insertPermissions = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        foreach ($permissions as $permission) {
 | 
					 | 
				
			||||||
            if (array_key_exists($permission, $permissionMappings)) {
 | 
					 | 
				
			||||||
                Assert::stringNotEmpty($permission, 'Permission argument provided must be a non-empty string, received %s.');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                array_push($insertPermissions, [
 | 
					 | 
				
			||||||
                    'subuser_id' => $subuser,
 | 
					 | 
				
			||||||
                    'permission' => $permission,
 | 
					 | 
				
			||||||
                ]);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (! empty($insertPermissions)) {
 | 
					 | 
				
			||||||
            $this->repository->withoutFreshModel()->insert($insertPermissions);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,22 +1,14 @@
 | 
				
			|||||||
<?php
 | 
					<?php
 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Pterodactyl - Panel
 | 
					 | 
				
			||||||
 * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * This software is licensed under the terms of the MIT license.
 | 
					 | 
				
			||||||
 * https://opensource.org/licenses/MIT
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Pterodactyl\Services\Subusers;
 | 
					namespace Pterodactyl\Services\Subusers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use Pterodactyl\Models\Server;
 | 
					use Pterodactyl\Models\Server;
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Subuser;
 | 
				
			||||||
use Illuminate\Database\ConnectionInterface;
 | 
					use Illuminate\Database\ConnectionInterface;
 | 
				
			||||||
use Pterodactyl\Services\Users\UserCreationService;
 | 
					use Pterodactyl\Services\Users\UserCreationService;
 | 
				
			||||||
 | 
					use Pterodactyl\Repositories\Eloquent\SubuserRepository;
 | 
				
			||||||
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
 | 
					use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
 | 
				
			||||||
use Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService;
 | 
					 | 
				
			||||||
use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
 | 
					use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
 | 
				
			||||||
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
 | 
					 | 
				
			||||||
use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
 | 
					 | 
				
			||||||
use Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException;
 | 
					use Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException;
 | 
				
			||||||
use Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException;
 | 
					use Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -25,86 +17,61 @@ class SubuserCreationService
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @var \Illuminate\Database\ConnectionInterface
 | 
					     * @var \Illuminate\Database\ConnectionInterface
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected $connection;
 | 
					    private $connection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService
 | 
					     * @var \Pterodactyl\Repositories\Eloquent\SubuserRepository
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected $keyCreationService;
 | 
					    private $subuserRepository;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @var \Pterodactyl\Services\Subusers\PermissionCreationService
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    protected $permissionService;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    protected $subuserRepository;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    protected $serverRepository;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @var \Pterodactyl\Services\Users\UserCreationService
 | 
					     * @var \Pterodactyl\Services\Users\UserCreationService
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected $userCreationService;
 | 
					    private $userCreationService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
 | 
					     * @var \Pterodactyl\Contracts\Repository\UserRepositoryInterface
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected $userRepository;
 | 
					    private $userRepository;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * SubuserCreationService constructor.
 | 
					     * SubuserCreationService constructor.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param \Illuminate\Database\ConnectionInterface $connection
 | 
					     * @param \Illuminate\Database\ConnectionInterface $connection
 | 
				
			||||||
     * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyCreationService $keyCreationService
 | 
					     * @param \Pterodactyl\Repositories\Eloquent\SubuserRepository $subuserRepository
 | 
				
			||||||
     * @param \Pterodactyl\Services\Subusers\PermissionCreationService $permissionService
 | 
					 | 
				
			||||||
     * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface $serverRepository
 | 
					 | 
				
			||||||
     * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $subuserRepository
 | 
					 | 
				
			||||||
     * @param \Pterodactyl\Services\Users\UserCreationService $userCreationService
 | 
					     * @param \Pterodactyl\Services\Users\UserCreationService $userCreationService
 | 
				
			||||||
     * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository
 | 
					     * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $userRepository
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function __construct(
 | 
					    public function __construct(
 | 
				
			||||||
        ConnectionInterface $connection,
 | 
					        ConnectionInterface $connection,
 | 
				
			||||||
        DaemonKeyCreationService $keyCreationService,
 | 
					        SubuserRepository $subuserRepository,
 | 
				
			||||||
        PermissionCreationService $permissionService,
 | 
					 | 
				
			||||||
        ServerRepositoryInterface $serverRepository,
 | 
					 | 
				
			||||||
        SubuserRepositoryInterface $subuserRepository,
 | 
					 | 
				
			||||||
        UserCreationService $userCreationService,
 | 
					        UserCreationService $userCreationService,
 | 
				
			||||||
        UserRepositoryInterface $userRepository
 | 
					        UserRepositoryInterface $userRepository
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        $this->connection = $connection;
 | 
					        $this->connection = $connection;
 | 
				
			||||||
        $this->keyCreationService = $keyCreationService;
 | 
					 | 
				
			||||||
        $this->permissionService = $permissionService;
 | 
					 | 
				
			||||||
        $this->serverRepository = $serverRepository;
 | 
					 | 
				
			||||||
        $this->subuserRepository = $subuserRepository;
 | 
					        $this->subuserRepository = $subuserRepository;
 | 
				
			||||||
        $this->userRepository = $userRepository;
 | 
					        $this->userRepository = $userRepository;
 | 
				
			||||||
        $this->userCreationService = $userCreationService;
 | 
					        $this->userCreationService = $userCreationService;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @param int|\Pterodactyl\Models\Server $server
 | 
					     * Creates a new user on the system and assigns them access to the provided server.
 | 
				
			||||||
 | 
					     * If the email address already belongs to a user on the system a new user will not
 | 
				
			||||||
 | 
					     * be created.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Models\Server $server
 | 
				
			||||||
     * @param string $email
 | 
					     * @param string $email
 | 
				
			||||||
     * @param array $permissions
 | 
					     * @param array $permissions
 | 
				
			||||||
     * @return \Pterodactyl\Models\Subuser
 | 
					     * @return \Pterodactyl\Models\Subuser
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @throws \Exception
 | 
					 | 
				
			||||||
     * @throws \Pterodactyl\Exceptions\Model\DataValidationException
 | 
					     * @throws \Pterodactyl\Exceptions\Model\DataValidationException
 | 
				
			||||||
     * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
 | 
					 | 
				
			||||||
     * @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException
 | 
					     * @throws \Pterodactyl\Exceptions\Service\Subuser\ServerSubuserExistsException
 | 
				
			||||||
     * @throws \Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException
 | 
					     * @throws \Pterodactyl\Exceptions\Service\Subuser\UserIsServerOwnerException
 | 
				
			||||||
 | 
					     * @throws \Throwable
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function handle($server, $email, array $permissions)
 | 
					    public function handle(Server $server, string $email, array $permissions): Subuser
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if (! $server instanceof Server) {
 | 
					        return $this->connection->transaction(function () use ($server, $email, $permissions) {
 | 
				
			||||||
            $server = $this->serverRepository->find($server);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $this->connection->beginTransaction();
 | 
					 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                $user = $this->userRepository->findFirstWhere([['email', '=', $email]]);
 | 
					                $user = $this->userRepository->findFirstWhere([['email', '=', $email]]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -117,21 +84,20 @@ class SubuserCreationService
 | 
				
			|||||||
                    throw new ServerSubuserExistsException(trans('exceptions.subusers.subuser_exists'));
 | 
					                    throw new ServerSubuserExistsException(trans('exceptions.subusers.subuser_exists'));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } catch (RecordNotFoundException $exception) {
 | 
					            } catch (RecordNotFoundException $exception) {
 | 
				
			||||||
            $username = preg_replace('/([^\w\.-]+)/', '', strtok($email, '@'));
 | 
					 | 
				
			||||||
                $user = $this->userCreationService->handle([
 | 
					                $user = $this->userCreationService->handle([
 | 
				
			||||||
                    'email' => $email,
 | 
					                    'email' => $email,
 | 
				
			||||||
                'username' => $username . str_random(3),
 | 
					                    'username' => preg_replace('/([^\w\.-]+)/', '', strtok($email, '@')) . str_random(3),
 | 
				
			||||||
                    'name_first' => 'Server',
 | 
					                    'name_first' => 'Server',
 | 
				
			||||||
                    'name_last' => 'Subuser',
 | 
					                    'name_last' => 'Subuser',
 | 
				
			||||||
                    'root_admin' => false,
 | 
					                    'root_admin' => false,
 | 
				
			||||||
                ]);
 | 
					                ]);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $subuser = $this->subuserRepository->create(['user_id' => $user->id, 'server_id' => $server->id]);
 | 
					            return $this->subuserRepository->create([
 | 
				
			||||||
        $this->keyCreationService->handle($server->id, $user->id);
 | 
					                'user_id' => $user->id,
 | 
				
			||||||
        $this->permissionService->handle($subuser->id, $permissions);
 | 
					                'server_id' => $server->id,
 | 
				
			||||||
        $this->connection->commit();
 | 
					                'permissions' => array_unique($permissions),
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
        return $subuser;
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,35 +0,0 @@
 | 
				
			|||||||
<?php
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace Pterodactyl\Services\Subusers;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use Pterodactyl\Models\Subuser;
 | 
					 | 
				
			||||||
use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SubuserDeletionService
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private $repository;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * SubuserDeletionService constructor.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function __construct(
 | 
					 | 
				
			||||||
        SubuserRepositoryInterface $repository
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
        $this->repository = $repository;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Delete a subuser and their associated permissions from the Panel and Daemon.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param \Pterodactyl\Models\Subuser $subuser
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function handle(Subuser $subuser)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        $this->repository->delete($subuser->id);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,107 +0,0 @@
 | 
				
			|||||||
<?php
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Pterodactyl - Panel
 | 
					 | 
				
			||||||
 * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * This software is licensed under the terms of the MIT license.
 | 
					 | 
				
			||||||
 * https://opensource.org/licenses/MIT
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace Pterodactyl\Services\Subusers;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use Pterodactyl\Models\Subuser;
 | 
					 | 
				
			||||||
use GuzzleHttp\Exception\RequestException;
 | 
					 | 
				
			||||||
use Illuminate\Database\ConnectionInterface;
 | 
					 | 
				
			||||||
use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService;
 | 
					 | 
				
			||||||
use Pterodactyl\Contracts\Repository\SubuserRepositoryInterface;
 | 
					 | 
				
			||||||
use Pterodactyl\Contracts\Repository\PermissionRepositoryInterface;
 | 
					 | 
				
			||||||
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
 | 
					 | 
				
			||||||
use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface as DaemonServerRepositoryInterface;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SubuserUpdateService
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @var \Illuminate\Database\ConnectionInterface
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private $connection;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @var \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private $daemonRepository;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @var \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private $keyProviderService;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @var \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private $permissionRepository;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @var \Pterodactyl\Services\Subusers\PermissionCreationService
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private $permissionService;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @var \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private $repository;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * SubuserUpdateService constructor.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param \Illuminate\Database\ConnectionInterface $connection
 | 
					 | 
				
			||||||
     * @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService
 | 
					 | 
				
			||||||
     * @param \Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface $daemonRepository
 | 
					 | 
				
			||||||
     * @param \Pterodactyl\Services\Subusers\PermissionCreationService $permissionService
 | 
					 | 
				
			||||||
     * @param \Pterodactyl\Contracts\Repository\PermissionRepositoryInterface $permissionRepository
 | 
					 | 
				
			||||||
     * @param \Pterodactyl\Contracts\Repository\SubuserRepositoryInterface $repository
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function __construct(
 | 
					 | 
				
			||||||
        ConnectionInterface $connection,
 | 
					 | 
				
			||||||
        DaemonKeyProviderService $keyProviderService,
 | 
					 | 
				
			||||||
        DaemonServerRepositoryInterface $daemonRepository,
 | 
					 | 
				
			||||||
        PermissionCreationService $permissionService,
 | 
					 | 
				
			||||||
        PermissionRepositoryInterface $permissionRepository,
 | 
					 | 
				
			||||||
        SubuserRepositoryInterface $repository
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
        $this->connection = $connection;
 | 
					 | 
				
			||||||
        $this->daemonRepository = $daemonRepository;
 | 
					 | 
				
			||||||
        $this->keyProviderService = $keyProviderService;
 | 
					 | 
				
			||||||
        $this->permissionRepository = $permissionRepository;
 | 
					 | 
				
			||||||
        $this->permissionService = $permissionService;
 | 
					 | 
				
			||||||
        $this->repository = $repository;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Update permissions for a given subuser.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param \Pterodactyl\Models\Subuser $subuser
 | 
					 | 
				
			||||||
     * @param array $permissions
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
 | 
					 | 
				
			||||||
     * @throws \Pterodactyl\Exceptions\Model\DataValidationException
 | 
					 | 
				
			||||||
     * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function handle(Subuser $subuser, array $permissions)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        $subuser = $this->repository->loadServerAndUserRelations($subuser);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $this->connection->beginTransaction();
 | 
					 | 
				
			||||||
        $this->permissionRepository->deleteWhere([['subuser_id', '=', $subuser->id]]);
 | 
					 | 
				
			||||||
        $this->permissionService->handle($subuser->id, $permissions);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            $token = $this->keyProviderService->handle($subuser->getRelation('server'), $subuser->getRelation('user'), false);
 | 
					 | 
				
			||||||
            $this->daemonRepository->setServer($subuser->getRelation('server'))->revokeAccessKey($token);
 | 
					 | 
				
			||||||
        } catch (RequestException $exception) {
 | 
					 | 
				
			||||||
            $this->connection->rollBack();
 | 
					 | 
				
			||||||
            throw new DaemonConnectionException($exception);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $this->connection->commit();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -75,6 +75,7 @@ class ServerTransformer extends BaseTransformer
 | 
				
			|||||||
                'disk' => $server->disk,
 | 
					                'disk' => $server->disk,
 | 
				
			||||||
                'io' => $server->io,
 | 
					                'io' => $server->io,
 | 
				
			||||||
                'cpu' => $server->cpu,
 | 
					                'cpu' => $server->cpu,
 | 
				
			||||||
 | 
					                'threads' => $server->threads,
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            'feature_limits' => [
 | 
					            'feature_limits' => [
 | 
				
			||||||
                'databases' => $server->database_limit,
 | 
					                'databases' => $server->database_limit,
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										33
									
								
								app/Transformers/Api/Client/BackupTransformer.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								app/Transformers/Api/Client/BackupTransformer.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Pterodactyl\Transformers\Api\Client;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Pterodactyl\Models\Backup;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BackupTransformer extends BaseClientTransformer
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @return string
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function getResourceName(): string
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return Backup::RESOURCE_NAME;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @param \Pterodactyl\Models\Backup $backup
 | 
				
			||||||
 | 
					     * @return array
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function transform(Backup $backup)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return [
 | 
				
			||||||
 | 
					            'uuid' => $backup->uuid,
 | 
				
			||||||
 | 
					            'name' => $backup->name,
 | 
				
			||||||
 | 
					            'ignored_files' => $backup->ignored_files,
 | 
				
			||||||
 | 
					            'sha256_hash' => $backup->sha256_hash,
 | 
				
			||||||
 | 
					            'bytes' => $backup->bytes,
 | 
				
			||||||
 | 
					            'created_at' => $backup->created_at->toIso8601String(),
 | 
				
			||||||
 | 
					            'completed_at' => $backup->completed_at ? $backup->completed_at->toIso8601String() : null,
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -2,16 +2,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace Pterodactyl\Transformers\Api\Client;
 | 
					namespace Pterodactyl\Transformers\Api\Client;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use Pterodactyl\Models\User;
 | 
					 | 
				
			||||||
use Pterodactyl\Models\Subuser;
 | 
					use Pterodactyl\Models\Subuser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SubuserTransformer extends BaseClientTransformer
 | 
					class SubuserTransformer extends BaseClientTransformer
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * @var array
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    protected $defaultIncludes = ['user'];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Return the resource name for the JSONAPI output.
 | 
					     * Return the resource name for the JSONAPI output.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@ -27,23 +21,13 @@ class SubuserTransformer extends BaseClientTransformer
 | 
				
			|||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param \Pterodactyl\Models\Subuser $model
 | 
					     * @param \Pterodactyl\Models\Subuser $model
 | 
				
			||||||
     * @return array|void
 | 
					     * @return array|void
 | 
				
			||||||
 | 
					     * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function transform(Subuser $model)
 | 
					    public function transform(Subuser $model)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return [
 | 
					        return array_merge(
 | 
				
			||||||
            'permissions' => $model->permissions->pluck('permission'),
 | 
					            $this->makeTransformer(UserTransformer::class)->transform($model->user),
 | 
				
			||||||
        ];
 | 
					            ['permissions' => $model->permissions]
 | 
				
			||||||
    }
 | 
					        );
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Include the permissions associated with this subuser.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param \Pterodactyl\Models\Subuser $model
 | 
					 | 
				
			||||||
     * @return \League\Fractal\Resource\Item
 | 
					 | 
				
			||||||
     * @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function includeUser(Subuser $model)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        return $this->item($model->user, $this->makeTransformer(UserTransformer::class), User::RESOURCE_NAME);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -15,16 +15,16 @@
 | 
				
			|||||||
        "ext-mbstring": "*",
 | 
					        "ext-mbstring": "*",
 | 
				
			||||||
        "ext-pdo_mysql": "*",
 | 
					        "ext-pdo_mysql": "*",
 | 
				
			||||||
        "ext-zip": "*",
 | 
					        "ext-zip": "*",
 | 
				
			||||||
        "appstract/laravel-blade-directives": "^1.6",
 | 
					        "appstract/laravel-blade-directives": "^1.8",
 | 
				
			||||||
        "aws/aws-sdk-php": "^3.110",
 | 
					        "aws/aws-sdk-php": "^3.134",
 | 
				
			||||||
        "cakephp/chronos": "^1.2",
 | 
					        "cakephp/chronos": "^1.3",
 | 
				
			||||||
        "doctrine/dbal": "^2.9",
 | 
					        "doctrine/dbal": "^2.10",
 | 
				
			||||||
        "fideloper/proxy": "^4.2",
 | 
					        "fideloper/proxy": "^4.2",
 | 
				
			||||||
        "guzzlehttp/guzzle": "^6.3",
 | 
					        "guzzlehttp/guzzle": "^6.5",
 | 
				
			||||||
        "hashids/hashids": "^4.0",
 | 
					        "hashids/hashids": "^4.0",
 | 
				
			||||||
        "laracasts/utilities": "^3.0",
 | 
					        "laracasts/utilities": "^3.1",
 | 
				
			||||||
        "laravel/framework": "^6.0.0",
 | 
					        "laravel/framework": "^6.18",
 | 
				
			||||||
        "laravel/helpers": "^1.1",
 | 
					        "laravel/helpers": "^1.2",
 | 
				
			||||||
        "laravel/tinker": "^1.0",
 | 
					        "laravel/tinker": "^1.0",
 | 
				
			||||||
        "lcobucci/jwt": "^3.3",
 | 
					        "lcobucci/jwt": "^3.3",
 | 
				
			||||||
        "matriphe/iso-639": "^1.2",
 | 
					        "matriphe/iso-639": "^1.2",
 | 
				
			||||||
@ -32,18 +32,18 @@
 | 
				
			|||||||
        "predis/predis": "^1.1",
 | 
					        "predis/predis": "^1.1",
 | 
				
			||||||
        "prologue/alerts": "^0.4",
 | 
					        "prologue/alerts": "^0.4",
 | 
				
			||||||
        "s1lentium/iptools": "^1.1",
 | 
					        "s1lentium/iptools": "^1.1",
 | 
				
			||||||
        "spatie/laravel-fractal": "^5.6",
 | 
					        "spatie/laravel-fractal": "^5.7",
 | 
				
			||||||
        "staudenmeir/belongs-to-through": "^2.6",
 | 
					        "staudenmeir/belongs-to-through": "^2.9",
 | 
				
			||||||
        "symfony/yaml": "^4.0",
 | 
					        "symfony/yaml": "^4.4",
 | 
				
			||||||
        "webmozart/assert": "^1.5"
 | 
					        "webmozart/assert": "^1.7"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "require-dev": {
 | 
					    "require-dev": {
 | 
				
			||||||
        "barryvdh/laravel-debugbar": "^3.2",
 | 
					        "barryvdh/laravel-debugbar": "^3.2",
 | 
				
			||||||
        "barryvdh/laravel-ide-helper": "^2.6",
 | 
					        "barryvdh/laravel-ide-helper": "^2.6",
 | 
				
			||||||
        "codedungeon/phpunit-result-printer": "0.25.1",
 | 
					        "codedungeon/phpunit-result-printer": "0.25.1",
 | 
				
			||||||
        "friendsofphp/php-cs-fixer": "^2.15.1",
 | 
					        "friendsofphp/php-cs-fixer": "^2.16.1",
 | 
				
			||||||
        "laravel/dusk": "^5.5",
 | 
					        "laravel/dusk": "^5.11",
 | 
				
			||||||
        "php-mock/php-mock-phpunit": "^2.4",
 | 
					        "php-mock/php-mock-phpunit": "^2.6",
 | 
				
			||||||
        "phpunit/phpunit": "^7"
 | 
					        "phpunit/phpunit": "^7"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "autoload": {
 | 
					    "autoload": {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2321
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2321
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										29
									
								
								config/backups.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								config/backups.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					return [
 | 
				
			||||||
 | 
					    // The backup driver to use for this Panel instance. All client generated server backups
 | 
				
			||||||
 | 
					    // will be stored in this location by default. It is possible to change this once backups
 | 
				
			||||||
 | 
					    // have been made, without losing data.
 | 
				
			||||||
 | 
					    'driver' => env('APP_BACKUP_DRIVER', 'local'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    'disks' => [
 | 
				
			||||||
 | 
					        // There is no configuration for the local disk for Wings. That configuration
 | 
				
			||||||
 | 
					        // is determined by the Daemon configuration, and not the Panel.
 | 
				
			||||||
 | 
					        'local' => [],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Configuration for storing backups in Amazon S3.
 | 
				
			||||||
 | 
					        's3' => [
 | 
				
			||||||
 | 
					            'region' => '',
 | 
				
			||||||
 | 
					            'access_key' => '',
 | 
				
			||||||
 | 
					            'access_secret_key' => '',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // The S3 bucket to use for backups.
 | 
				
			||||||
 | 
					            'bucket' => '',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // The location within the S3 bucket where backups will be stored. Backups
 | 
				
			||||||
 | 
					            // are stored within a folder using the server's UUID as the name. Each
 | 
				
			||||||
 | 
					            // backup for that server lives within that folder.
 | 
				
			||||||
 | 
					            'location' => '',
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\Schema;
 | 
				
			||||||
 | 
					use Illuminate\Database\Schema\Blueprint;
 | 
				
			||||||
 | 
					use Illuminate\Database\Migrations\Migration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AddThreadsColumnToServersTable extends Migration
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Run the migrations.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return void
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function up()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Schema::table('servers', function (Blueprint $table) {
 | 
				
			||||||
 | 
					            $table->string('threads')->nullable();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Reverse the migrations.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return void
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function down()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Schema::table('servers', function (Blueprint $table) {
 | 
				
			||||||
 | 
					            $table->dropColumn('threads');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Illuminate\Database\Migrations\Migration;
 | 
				
			||||||
 | 
					use Illuminate\Database\Schema\Blueprint;
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\Schema;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CreateBackupsTable extends Migration
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Run the migrations.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return void
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function up()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Schema::create('backups', function (Blueprint $table) {
 | 
				
			||||||
 | 
					            $table->bigIncrements('id');
 | 
				
			||||||
 | 
					            $table->unsignedInteger('server_id');
 | 
				
			||||||
 | 
					            $table->char('uuid', 36);
 | 
				
			||||||
 | 
					            $table->string('name');
 | 
				
			||||||
 | 
					            $table->text('ignored_files');
 | 
				
			||||||
 | 
					            $table->string('disk');
 | 
				
			||||||
 | 
					            $table->string('sha256_hash')->nullable();
 | 
				
			||||||
 | 
					            $table->integer('bytes')->default(0);
 | 
				
			||||||
 | 
					            $table->timestamp('completed_at')->nullable();
 | 
				
			||||||
 | 
					            $table->timestamps();
 | 
				
			||||||
 | 
					            $table->softDeletes();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $table->unique('uuid');
 | 
				
			||||||
 | 
					            $table->foreign('server_id')->references('id')->on('servers')->onDelete('cascade');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Reverse the migrations.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return void
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function down()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Schema::dropIfExists('backups');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\Schema;
 | 
				
			||||||
 | 
					use Illuminate\Database\Schema\Blueprint;
 | 
				
			||||||
 | 
					use Illuminate\Database\Migrations\Migration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AddTableServerTransfers extends Migration
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Run the migrations.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return void
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function up()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Schema::dropIfExists('server_transfers');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Schema::create('server_transfers', function (Blueprint $table) {
 | 
				
			||||||
 | 
					            $table->increments('id');
 | 
				
			||||||
 | 
					            $table->integer('server_id')->unsigned();
 | 
				
			||||||
 | 
					            $table->integer('old_node')->unsigned();
 | 
				
			||||||
 | 
					            $table->integer('new_node')->unsigned();
 | 
				
			||||||
 | 
					            $table->integer('old_allocation')->unsigned();
 | 
				
			||||||
 | 
					            $table->integer('new_allocation')->unsigned();
 | 
				
			||||||
 | 
					            $table->string('old_additional_allocations')->nullable();
 | 
				
			||||||
 | 
					            $table->string('new_additional_allocations')->nullable();
 | 
				
			||||||
 | 
					            $table->timestamps();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Schema::table('server_transfers', function (Blueprint $table) {
 | 
				
			||||||
 | 
					            $table->foreign('server_id')->references('id')->on('servers');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Reverse the migrations.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return void
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function down()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Schema::dropIfExists('server_transfers');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\Schema;
 | 
				
			||||||
 | 
					use Illuminate\Database\Schema\Blueprint;
 | 
				
			||||||
 | 
					use Illuminate\Database\Migrations\Migration;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AddSuccessfulColumnToServerTransfers extends Migration
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Run the migrations.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return void
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function up()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Schema::table('server_transfers', function (Blueprint $table) {
 | 
				
			||||||
 | 
					            $table->tinyInteger('successful')->unsigned()->default(0);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Reverse the migrations.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return void
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function down()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Schema::table('server_transfers', function (Blueprint $table) {
 | 
				
			||||||
 | 
					            $table->dropColumn('successful');
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										56
									
								
								public/themes/pterodactyl/js/admin/server/transfer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								public/themes/pterodactyl/js/admin/server/transfer.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					$(document).ready(function () {
 | 
				
			||||||
 | 
					    $('#pNodeId').select2({
 | 
				
			||||||
 | 
					        placeholder: 'Select a Node',
 | 
				
			||||||
 | 
					    }).change();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $('#pAllocation').select2({
 | 
				
			||||||
 | 
					        placeholder: 'Select a Default Allocation',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $('#pAllocationAdditional').select2({
 | 
				
			||||||
 | 
					        placeholder: 'Select Additional Allocations',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$('#pNodeId').on('change', function () {
 | 
				
			||||||
 | 
					    let currentNode = $(this).val();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $.each(Pterodactyl.nodeData, function (i, v) {
 | 
				
			||||||
 | 
					        if (v.id == currentNode) {
 | 
				
			||||||
 | 
					            $('#pAllocation').html('').select2({
 | 
				
			||||||
 | 
					                data: v.allocations,
 | 
				
			||||||
 | 
					                placeholder: 'Select a Default Allocation',
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            updateAdditionalAllocations();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$('#pAllocation').on('change', function () {
 | 
				
			||||||
 | 
					    updateAdditionalAllocations();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function updateAdditionalAllocations() {
 | 
				
			||||||
 | 
					    let currentAllocation = $('#pAllocation').val();
 | 
				
			||||||
 | 
					    let currentNode = $('#pNodeId').val();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $.each(Pterodactyl.nodeData, function (i, v) {
 | 
				
			||||||
 | 
					        if (v.id == currentNode) {
 | 
				
			||||||
 | 
					            let allocations = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (let i = 0; i < v.allocations.length; i++) {
 | 
				
			||||||
 | 
					                const allocation = v.allocations[i];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (allocation.id != currentAllocation) {
 | 
				
			||||||
 | 
					                    allocations.push(allocation);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $('#pAllocationAdditional').html('').select2({
 | 
				
			||||||
 | 
					                data: allocations,
 | 
				
			||||||
 | 
					                placeholder: 'Select Additional Allocations',
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -27,5 +27,8 @@ return [
 | 
				
			|||||||
        'details_updated' => 'Server details have been successfully updated.',
 | 
					        'details_updated' => 'Server details have been successfully updated.',
 | 
				
			||||||
        'docker_image_updated' => 'Successfully changed the default Docker image to use for this server. A reboot is required to apply this change.',
 | 
					        'docker_image_updated' => 'Successfully changed the default Docker image to use for this server. A reboot is required to apply this change.',
 | 
				
			||||||
        'node_required' => 'You must have at least one node configured before you can add a server to this panel.',
 | 
					        'node_required' => 'You must have at least one node configured before you can add a server to this panel.',
 | 
				
			||||||
 | 
					        'transfer_nodes_required' => 'You must have at least two nodes configured before you can transfer servers.',
 | 
				
			||||||
 | 
					        'transfer_started' => 'Server transfer has been started.',
 | 
				
			||||||
 | 
					        'transfer_not_viable' => 'The node you selected is not viable for this transfer.',
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,9 @@
 | 
				
			|||||||
import { rawDataToServerObject, Server } from '@/api/server/getServer';
 | 
					import { rawDataToServerObject, Server } from '@/api/server/getServer';
 | 
				
			||||||
import http, { getPaginationSet, PaginatedResult } from '@/api/http';
 | 
					import http, { getPaginationSet, PaginatedResult } from '@/api/http';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default (): Promise<PaginatedResult<Server>> => {
 | 
					export default (query?: string): Promise<PaginatedResult<Server>> => {
 | 
				
			||||||
    return new Promise((resolve, reject) => {
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
        http.get(`/api/client`, { params: { include: [ 'allocation' ] } })
 | 
					        http.get(`/api/client`, { params: { include: [ 'allocation' ], query } })
 | 
				
			||||||
            .then(({ data }) => resolve({
 | 
					            .then(({ data }) => resolve({
 | 
				
			||||||
                items: (data.data || []).map((datum: any) => rawDataToServerObject(datum.attributes)),
 | 
					                items: (data.data || []).map((datum: any) => rawDataToServerObject(datum.attributes)),
 | 
				
			||||||
                pagination: getPaginationSet(data.meta.pagination),
 | 
					                pagination: getPaginationSet(data.meta.pagination),
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import { SubuserPermission } from '@/state/server/subusers';
 | 
					import { PanelPermissions } from '@/state/permissions';
 | 
				
			||||||
import http from '@/api/http';
 | 
					import http from '@/api/http';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default (): Promise<SubuserPermission[]> => {
 | 
					export default (): Promise<PanelPermissions> => {
 | 
				
			||||||
    return new Promise((resolve, reject) => {
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
        http.get(`/api/client/permissions`)
 | 
					        http.get(`/api/client/permissions`)
 | 
				
			||||||
            .then(({ data }) => resolve(data.attributes.permissions))
 | 
					            .then(({ data }) => resolve(data.attributes.permissions))
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										12
									
								
								resources/scripts/api/server/backups/createServerBackup.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								resources/scripts/api/server/backups/createServerBackup.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					import { rawDataToServerBackup, ServerBackup } from '@/api/server/backups/getServerBackups';
 | 
				
			||||||
 | 
					import http from '@/api/http';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default (uuid: string, name?: string, ignore?: string): Promise<ServerBackup> => {
 | 
				
			||||||
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
					        http.post(`/api/client/servers/${uuid}/backups`, {
 | 
				
			||||||
 | 
					            name, ignore,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					            .then(({ data }) => resolve(rawDataToServerBackup(data)))
 | 
				
			||||||
 | 
					            .catch(reject);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										32
									
								
								resources/scripts/api/server/backups/getServerBackups.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								resources/scripts/api/server/backups/getServerBackups.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					import http, { FractalResponseData, getPaginationSet, PaginatedResult } from '@/api/http';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ServerBackup {
 | 
				
			||||||
 | 
					    uuid: string;
 | 
				
			||||||
 | 
					    name: string;
 | 
				
			||||||
 | 
					    ignoredFiles: string;
 | 
				
			||||||
 | 
					    sha256Hash: string;
 | 
				
			||||||
 | 
					    bytes: number;
 | 
				
			||||||
 | 
					    createdAt: Date;
 | 
				
			||||||
 | 
					    completedAt: Date | null;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const rawDataToServerBackup = ({ attributes }: FractalResponseData): ServerBackup => ({
 | 
				
			||||||
 | 
					    uuid: attributes.uuid,
 | 
				
			||||||
 | 
					    name: attributes.name,
 | 
				
			||||||
 | 
					    ignoredFiles: attributes.ignored_files,
 | 
				
			||||||
 | 
					    sha256Hash: attributes.sha256_hash,
 | 
				
			||||||
 | 
					    bytes: attributes.bytes,
 | 
				
			||||||
 | 
					    createdAt: new Date(attributes.created_at),
 | 
				
			||||||
 | 
					    completedAt: attributes.completed_at ? new Date(attributes.completed_at) : null,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default (uuid: string, page?: number | string): Promise<PaginatedResult<ServerBackup>> => {
 | 
				
			||||||
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
					        http.get(`/api/client/servers/${uuid}/backups`, { params: { page } })
 | 
				
			||||||
 | 
					            .then(({ data }) => resolve({
 | 
				
			||||||
 | 
					                items: (data.data || []).map(rawDataToServerBackup),
 | 
				
			||||||
 | 
					                pagination: getPaginationSet(data.meta.pagination),
 | 
				
			||||||
 | 
					            }))
 | 
				
			||||||
 | 
					            .catch(reject);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -24,6 +24,7 @@ export interface Server {
 | 
				
			|||||||
        disk: number;
 | 
					        disk: number;
 | 
				
			||||||
        io: number;
 | 
					        io: number;
 | 
				
			||||||
        cpu: number;
 | 
					        cpu: number;
 | 
				
			||||||
 | 
					        threads: string;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    featureLimits: {
 | 
					    featureLimits: {
 | 
				
			||||||
        databases: number;
 | 
					        databases: number;
 | 
				
			||||||
@ -41,20 +42,23 @@ export const rawDataToServerObject = (data: any): Server => ({
 | 
				
			|||||||
        port: data.sftp_details.port,
 | 
					        port: data.sftp_details.port,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    description: data.description ? ((data.description.length > 0) ? data.description : null) : null,
 | 
					    description: data.description ? ((data.description.length > 0) ? data.description : null) : null,
 | 
				
			||||||
    allocations: [{
 | 
					    allocations: [ {
 | 
				
			||||||
        ip: data.allocation.ip,
 | 
					        ip: data.allocation.ip,
 | 
				
			||||||
        alias: null,
 | 
					        alias: null,
 | 
				
			||||||
        port: data.allocation.port,
 | 
					        port: data.allocation.port,
 | 
				
			||||||
        default: true,
 | 
					        default: true,
 | 
				
			||||||
    }],
 | 
					    } ],
 | 
				
			||||||
    limits: { ...data.limits },
 | 
					    limits: { ...data.limits },
 | 
				
			||||||
    featureLimits: { ...data.feature_limits },
 | 
					    featureLimits: { ...data.feature_limits },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default (uuid: string): Promise<Server> => {
 | 
					export default (uuid: string): Promise<[ Server, string[] ]> => {
 | 
				
			||||||
    return new Promise((resolve, reject) => {
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
        http.get(`/api/client/servers/${uuid}`)
 | 
					        http.get(`/api/client/servers/${uuid}`)
 | 
				
			||||||
            .then(response => resolve(rawDataToServerObject(response.data.attributes)))
 | 
					            .then(({ data }) => resolve([
 | 
				
			||||||
 | 
					                rawDataToServerObject(data.attributes),
 | 
				
			||||||
 | 
					                data.meta?.is_server_owner ? ['*'] : (data.meta?.user_permissions || []),
 | 
				
			||||||
 | 
					            ]))
 | 
				
			||||||
            .catch(reject);
 | 
					            .catch(reject);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										9
									
								
								resources/scripts/api/server/reinstallServer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								resources/scripts/api/server/reinstallServer.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					import http from '@/api/http';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default (uuid: string): Promise<void> => {
 | 
				
			||||||
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
					        http.post(`/api/client/servers/${uuid}/settings/reinstall`)
 | 
				
			||||||
 | 
					            .then(() => resolve())
 | 
				
			||||||
 | 
					            .catch(reject);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								resources/scripts/api/server/users/createOrUpdateSubuser.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								resources/scripts/api/server/users/createOrUpdateSubuser.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					import http from '@/api/http';
 | 
				
			||||||
 | 
					import { rawDataToServerSubuser } from '@/api/server/users/getServerSubusers';
 | 
				
			||||||
 | 
					import { Subuser } from '@/state/server/subusers';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface Params {
 | 
				
			||||||
 | 
					    email: string;
 | 
				
			||||||
 | 
					    permissions: string[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default (uuid: string, params: Params, subuser?: Subuser): Promise<Subuser> => {
 | 
				
			||||||
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
					        http.post(`/api/client/servers/${uuid}/users${subuser ? `/${subuser.uuid}` : ''}`, {
 | 
				
			||||||
 | 
					            ...params,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					            .then(data => resolve(rawDataToServerSubuser(data.data)))
 | 
				
			||||||
 | 
					            .catch(reject);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										9
									
								
								resources/scripts/api/server/users/deleteSubuser.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								resources/scripts/api/server/users/deleteSubuser.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					import http from '@/api/http';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default (uuid: string, userId: string): Promise<void> => {
 | 
				
			||||||
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
					        http.delete(`/api/client/servers/${uuid}/users/${userId}`)
 | 
				
			||||||
 | 
					            .then(() => resolve())
 | 
				
			||||||
 | 
					            .catch(reject);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -8,13 +8,13 @@ export const rawDataToServerSubuser = (data: FractalResponseData): Subuser => ({
 | 
				
			|||||||
    image: data.attributes.image,
 | 
					    image: data.attributes.image,
 | 
				
			||||||
    twoFactorEnabled: data.attributes['2fa_enabled'],
 | 
					    twoFactorEnabled: data.attributes['2fa_enabled'],
 | 
				
			||||||
    createdAt: new Date(data.attributes.created_at),
 | 
					    createdAt: new Date(data.attributes.created_at),
 | 
				
			||||||
    permissions: data.attributes.relationships!.permissions.attributes.permissions,
 | 
					    permissions: data.attributes.permissions || [],
 | 
				
			||||||
    can: permission => data.attributes.relationships!.permissions.attributes.permissions.indexOf(permission) >= 0,
 | 
					    can: permission => (data.attributes.permissions || []).indexOf(permission) >= 0,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default (uuid: string): Promise<Subuser[]> => {
 | 
					export default (uuid: string): Promise<Subuser[]> => {
 | 
				
			||||||
    return new Promise((resolve, reject) => {
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
        http.get(`/api/client/servers/${uuid}/users`, { params: { include: [ 'permissions' ] } })
 | 
					        http.get(`/api/client/servers/${uuid}/users`)
 | 
				
			||||||
            .then(({ data }) => resolve((data.data || []).map(rawDataToServerSubuser)))
 | 
					            .then(({ data }) => resolve((data.data || []).map(rawDataToServerSubuser)))
 | 
				
			||||||
            .catch(reject);
 | 
					            .catch(reject);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
				
			|||||||
@ -8,6 +8,8 @@ import { faSwatchbook } from '@fortawesome/free-solid-svg-icons/faSwatchbook';
 | 
				
			|||||||
import { faCogs } from '@fortawesome/free-solid-svg-icons/faCogs';
 | 
					import { faCogs } from '@fortawesome/free-solid-svg-icons/faCogs';
 | 
				
			||||||
import { useStoreState } from 'easy-peasy';
 | 
					import { useStoreState } from 'easy-peasy';
 | 
				
			||||||
import { ApplicationStore } from '@/state';
 | 
					import { ApplicationStore } from '@/state';
 | 
				
			||||||
 | 
					import { faSearch } from '@fortawesome/free-solid-svg-icons/faSearch';
 | 
				
			||||||
 | 
					import SearchContainer from '@/components/dashboard/search/SearchContainer';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default () => {
 | 
					export default () => {
 | 
				
			||||||
    const user = useStoreState((state: ApplicationStore) => state.user.data!);
 | 
					    const user = useStoreState((state: ApplicationStore) => state.user.data!);
 | 
				
			||||||
@ -22,6 +24,7 @@ export default () => {
 | 
				
			|||||||
                    </Link>
 | 
					                    </Link>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div className={'right-navigation'}>
 | 
					                <div className={'right-navigation'}>
 | 
				
			||||||
 | 
					                    <SearchContainer/>
 | 
				
			||||||
                    <NavLink to={'/'} exact={true}>
 | 
					                    <NavLink to={'/'} exact={true}>
 | 
				
			||||||
                        <FontAwesomeIcon icon={faLayerGroup}/>
 | 
					                        <FontAwesomeIcon icon={faLayerGroup}/>
 | 
				
			||||||
                    </NavLink>
 | 
					                    </NavLink>
 | 
				
			||||||
 | 
				
			|||||||
@ -4,25 +4,24 @@ import requestPasswordResetEmail from '@/api/auth/requestPasswordResetEmail';
 | 
				
			|||||||
import { httpErrorToHuman } from '@/api/http';
 | 
					import { httpErrorToHuman } from '@/api/http';
 | 
				
			||||||
import LoginFormContainer from '@/components/auth/LoginFormContainer';
 | 
					import LoginFormContainer from '@/components/auth/LoginFormContainer';
 | 
				
			||||||
import { Actions, useStoreActions } from 'easy-peasy';
 | 
					import { Actions, useStoreActions } from 'easy-peasy';
 | 
				
			||||||
import FlashMessageRender from '@/components/FlashMessageRender';
 | 
					 | 
				
			||||||
import { ApplicationStore } from '@/state';
 | 
					import { ApplicationStore } from '@/state';
 | 
				
			||||||
 | 
					import Field from '@/components/elements/Field';
 | 
				
			||||||
 | 
					import { Formik, FormikHelpers } from 'formik';
 | 
				
			||||||
 | 
					import { object, string } from 'yup';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface Values {
 | 
				
			||||||
 | 
					    email: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default () => {
 | 
					export default () => {
 | 
				
			||||||
    const [ isSubmitting, setSubmitting ] = React.useState(false);
 | 
					 | 
				
			||||||
    const [ email, setEmail ] = React.useState('');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const { clearFlashes, addFlash } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
 | 
					    const { clearFlashes, addFlash } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const handleFieldUpdate = (e: React.ChangeEvent<HTMLInputElement>) => setEmail(e.target.value);
 | 
					    const handleSubmission = ({ email }: Values, { setSubmitting, resetForm }: FormikHelpers<Values>) => {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    const handleSubmission = (e: React.FormEvent<HTMLFormElement>) => {
 | 
					 | 
				
			||||||
        e.preventDefault();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        setSubmitting(true);
 | 
					        setSubmitting(true);
 | 
				
			||||||
        clearFlashes();
 | 
					        clearFlashes();
 | 
				
			||||||
        requestPasswordResetEmail(email)
 | 
					        requestPasswordResetEmail(email)
 | 
				
			||||||
            .then(response => {
 | 
					            .then(response => {
 | 
				
			||||||
                setEmail('');
 | 
					                resetForm();
 | 
				
			||||||
                addFlash({ type: 'success', title: 'Success', message: response });
 | 
					                addFlash({ type: 'success', title: 'Success', message: response });
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            .catch(error => {
 | 
					            .catch(error => {
 | 
				
			||||||
@ -33,29 +32,31 @@ export default () => {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <div>
 | 
					        <Formik
 | 
				
			||||||
            <h2 className={'text-center text-neutral-100 font-medium py-4'}>
 | 
					            onSubmit={handleSubmission}
 | 
				
			||||||
                Request Password Reset
 | 
					            initialValues={{ email: '' }}
 | 
				
			||||||
            </h2>
 | 
					            validationSchema={object().shape({
 | 
				
			||||||
            <FlashMessageRender/>
 | 
					                email: string().email('A valid email address must be provided to continue.')
 | 
				
			||||||
            <LoginFormContainer onSubmit={handleSubmission}>
 | 
					                    .required('A valid email address must be provided to continue.'),
 | 
				
			||||||
                <label htmlFor={'email'}>Email</label>
 | 
					            })}
 | 
				
			||||||
                <input
 | 
					        >
 | 
				
			||||||
                    id={'email'}
 | 
					            {({ isSubmitting }) => (
 | 
				
			||||||
 | 
					                <LoginFormContainer
 | 
				
			||||||
 | 
					                    title={'Request Password Reset'}
 | 
				
			||||||
 | 
					                    className={'w-full flex'}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    <Field
 | 
				
			||||||
 | 
					                        light={true}
 | 
				
			||||||
 | 
					                        label={'Email'}
 | 
				
			||||||
 | 
					                        description={'Enter your account email address to receive instructions on resetting your password.'}
 | 
				
			||||||
 | 
					                        name={'email'}
 | 
				
			||||||
                        type={'email'}
 | 
					                        type={'email'}
 | 
				
			||||||
                    required={true}
 | 
					 | 
				
			||||||
                    className={'input'}
 | 
					 | 
				
			||||||
                    value={email}
 | 
					 | 
				
			||||||
                    onChange={handleFieldUpdate}
 | 
					 | 
				
			||||||
                    autoFocus={true}
 | 
					 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                <p className={'input-help'}>
 | 
					 | 
				
			||||||
                    Enter your account email address to receive instructions on resetting your password.
 | 
					 | 
				
			||||||
                </p>
 | 
					 | 
				
			||||||
                    <div className={'mt-6'}>
 | 
					                    <div className={'mt-6'}>
 | 
				
			||||||
                        <button
 | 
					                        <button
 | 
				
			||||||
 | 
					                            type={'submit'}
 | 
				
			||||||
                            className={'btn btn-primary btn-jumbo flex justify-center'}
 | 
					                            className={'btn btn-primary btn-jumbo flex justify-center'}
 | 
				
			||||||
                        disabled={isSubmitting || email.length < 5}
 | 
					                            disabled={isSubmitting}
 | 
				
			||||||
                        >
 | 
					                        >
 | 
				
			||||||
                            {isSubmitting ?
 | 
					                            {isSubmitting ?
 | 
				
			||||||
                                <div className={'spinner-circle spinner-sm spinner-white'}></div>
 | 
					                                <div className={'spinner-circle spinner-sm spinner-white'}></div>
 | 
				
			||||||
@ -66,6 +67,7 @@ export default () => {
 | 
				
			|||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    <div className={'mt-6 text-center'}>
 | 
					                    <div className={'mt-6 text-center'}>
 | 
				
			||||||
                        <Link
 | 
					                        <Link
 | 
				
			||||||
 | 
					                            type={'button'}
 | 
				
			||||||
                            to={'/auth/login'}
 | 
					                            to={'/auth/login'}
 | 
				
			||||||
                            className={'text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700'}
 | 
					                            className={'text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700'}
 | 
				
			||||||
                        >
 | 
					                        >
 | 
				
			||||||
@ -73,6 +75,7 @@ export default () => {
 | 
				
			|||||||
                        </Link>
 | 
					                        </Link>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </LoginFormContainer>
 | 
					                </LoginFormContainer>
 | 
				
			||||||
        </div>
 | 
					            )}
 | 
				
			||||||
 | 
					        </Formik>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,6 @@ import React, { useRef } from 'react';
 | 
				
			|||||||
import { Link, RouteComponentProps } from 'react-router-dom';
 | 
					import { Link, RouteComponentProps } from 'react-router-dom';
 | 
				
			||||||
import login, { LoginData } from '@/api/auth/login';
 | 
					import login, { LoginData } from '@/api/auth/login';
 | 
				
			||||||
import LoginFormContainer from '@/components/auth/LoginFormContainer';
 | 
					import LoginFormContainer from '@/components/auth/LoginFormContainer';
 | 
				
			||||||
import FlashMessageRender from '@/components/FlashMessageRender';
 | 
					 | 
				
			||||||
import { ActionCreator, Actions, useStoreActions, useStoreState } from 'easy-peasy';
 | 
					import { ActionCreator, Actions, useStoreActions, useStoreState } from 'easy-peasy';
 | 
				
			||||||
import { ApplicationStore } from '@/state';
 | 
					import { ApplicationStore } from '@/state';
 | 
				
			||||||
import { FormikProps, withFormik } from 'formik';
 | 
					import { FormikProps, withFormik } from 'formik';
 | 
				
			||||||
@ -12,33 +11,12 @@ import { httpErrorToHuman } from '@/api/http';
 | 
				
			|||||||
import { FlashMessage } from '@/state/flashes';
 | 
					import { FlashMessage } from '@/state/flashes';
 | 
				
			||||||
import ReCAPTCHA from 'react-google-recaptcha';
 | 
					import ReCAPTCHA from 'react-google-recaptcha';
 | 
				
			||||||
import Spinner from '@/components/elements/Spinner';
 | 
					import Spinner from '@/components/elements/Spinner';
 | 
				
			||||||
import styled from 'styled-components';
 | 
					 | 
				
			||||||
import { breakpoint } from 'styled-components-breakpoint';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
type OwnProps = RouteComponentProps & {
 | 
					type OwnProps = RouteComponentProps & {
 | 
				
			||||||
    clearFlashes: ActionCreator<void>;
 | 
					    clearFlashes: ActionCreator<void>;
 | 
				
			||||||
    addFlash: ActionCreator<FlashMessage>;
 | 
					    addFlash: ActionCreator<FlashMessage>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Container = styled.div`
 | 
					 | 
				
			||||||
    ${breakpoint('sm')`
 | 
					 | 
				
			||||||
        ${tw`w-4/5 mx-auto`}
 | 
					 | 
				
			||||||
    `};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ${breakpoint('md')`
 | 
					 | 
				
			||||||
        ${tw`p-10`}
 | 
					 | 
				
			||||||
    `};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ${breakpoint('lg')`
 | 
					 | 
				
			||||||
        ${tw`w-3/5`}
 | 
					 | 
				
			||||||
    `};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ${breakpoint('xl')`
 | 
					 | 
				
			||||||
        ${tw`w-full`}
 | 
					 | 
				
			||||||
        max-width: 660px;
 | 
					 | 
				
			||||||
    `};
 | 
					 | 
				
			||||||
`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const LoginContainer = ({ isSubmitting, setFieldValue, values, submitForm, handleSubmit }: OwnProps & FormikProps<LoginData>) => {
 | 
					const LoginContainer = ({ isSubmitting, setFieldValue, values, submitForm, handleSubmit }: OwnProps & FormikProps<LoginData>) => {
 | 
				
			||||||
    const ref = useRef<ReCAPTCHA | null>(null);
 | 
					    const ref = useRef<ReCAPTCHA | null>(null);
 | 
				
			||||||
    const { enabled: recaptchaEnabled, siteKey } = useStoreState<ApplicationStore, any>(state => state.settings.data!.recaptcha);
 | 
					    const { enabled: recaptchaEnabled, siteKey } = useStoreState<ApplicationStore, any>(state => state.settings.data!.recaptcha);
 | 
				
			||||||
@ -56,12 +34,8 @@ const LoginContainer = ({ isSubmitting, setFieldValue, values, submitForm, handl
 | 
				
			|||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <React.Fragment>
 | 
					        <React.Fragment>
 | 
				
			||||||
            {ref.current && ref.current.render()}
 | 
					            {ref.current && ref.current.render()}
 | 
				
			||||||
            <h2 className={'text-center text-neutral-100 font-medium py-4'}>
 | 
					 | 
				
			||||||
                Login to Continue
 | 
					 | 
				
			||||||
            </h2>
 | 
					 | 
				
			||||||
            <Container>
 | 
					 | 
				
			||||||
                <FlashMessageRender className={'mb-2 px-1'}/>
 | 
					 | 
				
			||||||
            <LoginFormContainer
 | 
					            <LoginFormContainer
 | 
				
			||||||
 | 
					                title={'Login to Continue'}
 | 
				
			||||||
                className={'w-full flex'}
 | 
					                className={'w-full flex'}
 | 
				
			||||||
                onSubmit={submit}
 | 
					                onSubmit={submit}
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
@ -115,7 +89,6 @@ const LoginContainer = ({ isSubmitting, setFieldValue, values, submitForm, handl
 | 
				
			|||||||
                    </Link>
 | 
					                    </Link>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </LoginFormContainer>
 | 
					            </LoginFormContainer>
 | 
				
			||||||
            </Container>
 | 
					 | 
				
			||||||
        </React.Fragment>
 | 
					        </React.Fragment>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,40 @@
 | 
				
			|||||||
import React, { forwardRef } from 'react';
 | 
					import React, { forwardRef } from 'react';
 | 
				
			||||||
import { Form } from 'formik';
 | 
					import { Form } from 'formik';
 | 
				
			||||||
 | 
					import styled from 'styled-components';
 | 
				
			||||||
 | 
					import { breakpoint } from 'styled-components-breakpoint';
 | 
				
			||||||
 | 
					import FlashMessageRender from '@/components/FlashMessageRender';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>;
 | 
					type Props = React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement> & {
 | 
				
			||||||
 | 
					    title?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default forwardRef<any, Props>(({ ...props }, ref) => (
 | 
					const Container = styled.div`
 | 
				
			||||||
    <Form {...props}>
 | 
					    ${breakpoint('sm')`
 | 
				
			||||||
        <div className={'md:flex w-full bg-white shadow-lg rounded-lg p-6 mx-1'}>
 | 
					        ${tw`w-4/5 mx-auto`}
 | 
				
			||||||
 | 
					    `};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ${breakpoint('md')`
 | 
				
			||||||
 | 
					        ${tw`p-10`}
 | 
				
			||||||
 | 
					    `};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ${breakpoint('lg')`
 | 
				
			||||||
 | 
					        ${tw`w-3/5`}
 | 
				
			||||||
 | 
					    `};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ${breakpoint('xl')`
 | 
				
			||||||
 | 
					        ${tw`w-full`}
 | 
				
			||||||
 | 
					        max-width: 700px;
 | 
				
			||||||
 | 
					    `};
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default forwardRef<HTMLFormElement, Props>(({ title, ...props }, ref) => (
 | 
				
			||||||
 | 
					    <Container>
 | 
				
			||||||
 | 
					        {title && <h2 className={'text-center text-neutral-100 font-medium py-4'}>
 | 
				
			||||||
 | 
					            {title}
 | 
				
			||||||
 | 
					        </h2>}
 | 
				
			||||||
 | 
					        <FlashMessageRender className={'mb-2 px-1'}/>
 | 
				
			||||||
 | 
					        <Form {...props} ref={ref}>
 | 
				
			||||||
 | 
					            <div className={'md:flex w-full bg-white shadow-lg rounded-lg p-6 md:pl-0 mx-1'}>
 | 
				
			||||||
                <div className={'flex-none select-none mb-6 md:mb-0 self-center'}>
 | 
					                <div className={'flex-none select-none mb-6 md:mb-0 self-center'}>
 | 
				
			||||||
                    <img src={'/assets/pterodactyl.svg'} className={'block w-48 md:w-64 mx-auto'}/>
 | 
					                    <img src={'/assets/pterodactyl.svg'} className={'block w-48 md:w-64 mx-auto'}/>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
@ -14,4 +43,5 @@ export default forwardRef<any, Props>(({ ...props }, ref) => (
 | 
				
			|||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </Form>
 | 
					        </Form>
 | 
				
			||||||
 | 
					    </Container>
 | 
				
			||||||
));
 | 
					));
 | 
				
			||||||
 | 
				
			|||||||
@ -5,91 +5,93 @@ import { Link } from 'react-router-dom';
 | 
				
			|||||||
import performPasswordReset from '@/api/auth/performPasswordReset';
 | 
					import performPasswordReset from '@/api/auth/performPasswordReset';
 | 
				
			||||||
import { httpErrorToHuman } from '@/api/http';
 | 
					import { httpErrorToHuman } from '@/api/http';
 | 
				
			||||||
import LoginFormContainer from '@/components/auth/LoginFormContainer';
 | 
					import LoginFormContainer from '@/components/auth/LoginFormContainer';
 | 
				
			||||||
import FlashMessageRender from '@/components/FlashMessageRender';
 | 
					 | 
				
			||||||
import { Actions, useStoreActions } from 'easy-peasy';
 | 
					import { Actions, useStoreActions } from 'easy-peasy';
 | 
				
			||||||
import { ApplicationStore } from '@/state';
 | 
					import { ApplicationStore } from '@/state';
 | 
				
			||||||
import Spinner from '@/components/elements/Spinner';
 | 
					import Spinner from '@/components/elements/Spinner';
 | 
				
			||||||
 | 
					import { Formik, FormikHelpers } from 'formik';
 | 
				
			||||||
 | 
					import { object, ref, string } from 'yup';
 | 
				
			||||||
 | 
					import Field from '@/components/elements/Field';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = Readonly<RouteComponentProps<{ token: string }> & {}>;
 | 
					type Props = Readonly<RouteComponentProps<{ token: string }> & {}>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default (props: Props) => {
 | 
					interface Values {
 | 
				
			||||||
    const [ isLoading, setIsLoading ] = useState(false);
 | 
					    password: string;
 | 
				
			||||||
 | 
					    passwordConfirmation: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default ({ match, history, location }: Props) => {
 | 
				
			||||||
    const [ email, setEmail ] = useState('');
 | 
					    const [ email, setEmail ] = useState('');
 | 
				
			||||||
    const [ password, setPassword ] = useState('');
 | 
					 | 
				
			||||||
    const [ passwordConfirm, setPasswordConfirm ] = useState('');
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { clearFlashes, addFlash } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
 | 
					    const { clearFlashes, addFlash } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const parsed = parse(props.location.search);
 | 
					    const parsed = parse(location.search);
 | 
				
			||||||
    if (email.length === 0 && parsed.email) {
 | 
					    if (email.length === 0 && parsed.email) {
 | 
				
			||||||
        setEmail(parsed.email as string);
 | 
					        setEmail(parsed.email as string);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const canSubmit = () => password && email && password.length >= 8 && password === passwordConfirm;
 | 
					    const submit = ({ password, passwordConfirmation }: Values, { setSubmitting }: FormikHelpers<Values>) => {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    const submit = (e: React.FormEvent<HTMLFormElement>) => {
 | 
					 | 
				
			||||||
        e.preventDefault();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (!password || !email || !passwordConfirm) {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        setIsLoading(true);
 | 
					 | 
				
			||||||
        clearFlashes();
 | 
					        clearFlashes();
 | 
				
			||||||
 | 
					        performPasswordReset(email, { token: match.params.token, password, passwordConfirmation })
 | 
				
			||||||
        performPasswordReset(email, {
 | 
					 | 
				
			||||||
            token: props.match.params.token, password, passwordConfirmation: passwordConfirm,
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
            .then(() => {
 | 
					            .then(() => {
 | 
				
			||||||
                addFlash({ type: 'success', message: 'Your password has been reset, please login to continue.' });
 | 
					                // @ts-ignore
 | 
				
			||||||
                props.history.push('/auth/login');
 | 
					                window.location = '/';
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            .catch(error => {
 | 
					            .catch(error => {
 | 
				
			||||||
                console.error(error);
 | 
					                console.error(error);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                setSubmitting(false);
 | 
				
			||||||
                addFlash({ type: 'error', title: 'Error', message: httpErrorToHuman(error) });
 | 
					                addFlash({ type: 'error', title: 'Error', message: httpErrorToHuman(error) });
 | 
				
			||||||
            })
 | 
					            });
 | 
				
			||||||
            .then(() => setIsLoading(false));
 | 
					 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
 | 
					        <Formik
 | 
				
			||||||
 | 
					            onSubmit={submit}
 | 
				
			||||||
 | 
					            initialValues={{
 | 
				
			||||||
 | 
					                password: '',
 | 
				
			||||||
 | 
					                passwordConfirmation: '',
 | 
				
			||||||
 | 
					            }}
 | 
				
			||||||
 | 
					            validationSchema={object().shape({
 | 
				
			||||||
 | 
					                password: string().required('A new password is required.')
 | 
				
			||||||
 | 
					                    .min(8, 'Your new password should be at least 8 characters in length.'),
 | 
				
			||||||
 | 
					                passwordConfirmation: string()
 | 
				
			||||||
 | 
					                    .required('Your new password does not match.')
 | 
				
			||||||
 | 
					                    .oneOf([ref('password'), null], 'Your new password does not match.'),
 | 
				
			||||||
 | 
					            })}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            {({ isSubmitting }) => (
 | 
				
			||||||
 | 
					                <LoginFormContainer
 | 
				
			||||||
 | 
					                    title={'Reset Password'}
 | 
				
			||||||
 | 
					                    className={'w-full flex'}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
                    <div>
 | 
					                    <div>
 | 
				
			||||||
            <h2 className={'text-center text-neutral-100 font-medium py-4'}>
 | 
					 | 
				
			||||||
                Reset Password
 | 
					 | 
				
			||||||
            </h2>
 | 
					 | 
				
			||||||
            <FlashMessageRender/>
 | 
					 | 
				
			||||||
            <LoginFormContainer onSubmit={submit}>
 | 
					 | 
				
			||||||
                        <label>Email</label>
 | 
					                        <label>Email</label>
 | 
				
			||||||
                        <input className={'input'} value={email} disabled={true}/>
 | 
					                        <input className={'input'} value={email} disabled={true}/>
 | 
				
			||||||
                <div className={'mt-6'}>
 | 
					 | 
				
			||||||
                    <label htmlFor={'new_password'}>New Password</label>
 | 
					 | 
				
			||||||
                    <input
 | 
					 | 
				
			||||||
                        id={'new_password'}
 | 
					 | 
				
			||||||
                        className={'input'}
 | 
					 | 
				
			||||||
                        type={'password'}
 | 
					 | 
				
			||||||
                        required={true}
 | 
					 | 
				
			||||||
                        onChange={e => setPassword(e.target.value)}
 | 
					 | 
				
			||||||
                    />
 | 
					 | 
				
			||||||
                    <p className={'input-help'}>
 | 
					 | 
				
			||||||
                        Passwords must be at least 8 characters in length.
 | 
					 | 
				
			||||||
                    </p>
 | 
					 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    <div className={'mt-6'}>
 | 
					                    <div className={'mt-6'}>
 | 
				
			||||||
                    <label htmlFor={'new_password_confirm'}>Confirm New Password</label>
 | 
					                        <Field
 | 
				
			||||||
                    <input
 | 
					                            light={true}
 | 
				
			||||||
                        id={'new_password_confirm'}
 | 
					                            label={'New Password'}
 | 
				
			||||||
                        className={'input'}
 | 
					                            name={'password'}
 | 
				
			||||||
 | 
					                            type={'password'}
 | 
				
			||||||
 | 
					                            description={'Passwords must be at least 8 characters in length.'}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <div className={'mt-6'}>
 | 
				
			||||||
 | 
					                        <Field
 | 
				
			||||||
 | 
					                            light={true}
 | 
				
			||||||
 | 
					                            label={'Confirm New Password'}
 | 
				
			||||||
 | 
					                            name={'passwordConfirmation'}
 | 
				
			||||||
                            type={'password'}
 | 
					                            type={'password'}
 | 
				
			||||||
                        required={true}
 | 
					 | 
				
			||||||
                        onChange={e => setPasswordConfirm(e.target.value)}
 | 
					 | 
				
			||||||
                        />
 | 
					                        />
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                    <div className={'mt-6'}>
 | 
					                    <div className={'mt-6'}>
 | 
				
			||||||
                        <button
 | 
					                        <button
 | 
				
			||||||
                            type={'submit'}
 | 
					                            type={'submit'}
 | 
				
			||||||
                            className={'btn btn-primary btn-jumbo'}
 | 
					                            className={'btn btn-primary btn-jumbo'}
 | 
				
			||||||
                        disabled={isLoading || !canSubmit()}
 | 
					                            disabled={isSubmitting}
 | 
				
			||||||
                        >
 | 
					                        >
 | 
				
			||||||
                        {isLoading ?
 | 
					                            {isSubmitting ?
 | 
				
			||||||
                                <Spinner size={'tiny'} className={'mx-auto'}/>
 | 
					                                <Spinner size={'tiny'} className={'mx-auto'}/>
 | 
				
			||||||
                                :
 | 
					                                :
 | 
				
			||||||
                                'Reset Password'
 | 
					                                'Reset Password'
 | 
				
			||||||
@ -105,6 +107,7 @@ export default (props: Props) => {
 | 
				
			|||||||
                        </Link>
 | 
					                        </Link>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </LoginFormContainer>
 | 
					                </LoginFormContainer>
 | 
				
			||||||
        </div>
 | 
					            )}
 | 
				
			||||||
 | 
					        </Formik>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -46,10 +46,11 @@ export default () => {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <div className={'my-10 flex'}>
 | 
					        <div className={'my-10'}>
 | 
				
			||||||
            <FlashMessageRender byKey={'account'} className={'mb-4'}/>
 | 
					            <FlashMessageRender byKey={'account'} className={'mb-4'}/>
 | 
				
			||||||
 | 
					            <div className={'flex'}>
 | 
				
			||||||
                <ContentBox title={'Create API Key'} className={'flex-1'}>
 | 
					                <ContentBox title={'Create API Key'} className={'flex-1'}>
 | 
				
			||||||
                <CreateApiKeyForm onKeyCreated={key => setKeys(s => ([...s!, key]))}/>
 | 
					                    <CreateApiKeyForm onKeyCreated={key => setKeys(s => ([ ...s!, key ]))}/>
 | 
				
			||||||
                </ContentBox>
 | 
					                </ContentBox>
 | 
				
			||||||
                <ContentBox title={'API Keys'} className={'ml-10 flex-1'}>
 | 
					                <ContentBox title={'API Keys'} className={'ml-10 flex-1'}>
 | 
				
			||||||
                    <SpinnerOverlay visible={loading}/>
 | 
					                    <SpinnerOverlay visible={loading}/>
 | 
				
			||||||
@ -62,7 +63,7 @@ export default () => {
 | 
				
			|||||||
                            doDeletion(deleteIdentifier);
 | 
					                            doDeletion(deleteIdentifier);
 | 
				
			||||||
                            setDeleteIdentifier('');
 | 
					                            setDeleteIdentifier('');
 | 
				
			||||||
                        }}
 | 
					                        }}
 | 
				
			||||||
                    onCanceled={() => setDeleteIdentifier('')}
 | 
					                        onDismissed={() => setDeleteIdentifier('')}
 | 
				
			||||||
                    >
 | 
					                    >
 | 
				
			||||||
                        Are you sure you wish to delete this API key? All requests using it will immediately be
 | 
					                        Are you sure you wish to delete this API key? All requests using it will immediately be
 | 
				
			||||||
                        invalidated and will fail.
 | 
					                        invalidated and will fail.
 | 
				
			||||||
@ -75,7 +76,10 @@ export default () => {
 | 
				
			|||||||
                            </p>
 | 
					                            </p>
 | 
				
			||||||
                            :
 | 
					                            :
 | 
				
			||||||
                            keys.map(key => (
 | 
					                            keys.map(key => (
 | 
				
			||||||
                            <div key={key.identifier} className={'grey-row-box bg-neutral-600 mb-2 flex items-center'}>
 | 
					                                <div
 | 
				
			||||||
 | 
					                                    key={key.identifier}
 | 
				
			||||||
 | 
					                                    className={'grey-row-box bg-neutral-600 mb-2 flex items-center'}
 | 
				
			||||||
 | 
					                                >
 | 
				
			||||||
                                    <FontAwesomeIcon icon={faKey} className={'text-neutral-300'}/>
 | 
					                                    <FontAwesomeIcon icon={faKey} className={'text-neutral-300'}/>
 | 
				
			||||||
                                    <div className={'ml-4 flex-1'}>
 | 
					                                    <div className={'ml-4 flex-1'}>
 | 
				
			||||||
                                        <p className={'text-sm'}>{key.description}</p>
 | 
					                                        <p className={'text-sm'}>{key.description}</p>
 | 
				
			||||||
@ -95,7 +99,7 @@ export default () => {
 | 
				
			|||||||
                                    >
 | 
					                                    >
 | 
				
			||||||
                                        <FontAwesomeIcon
 | 
					                                        <FontAwesomeIcon
 | 
				
			||||||
                                            icon={faTrashAlt}
 | 
					                                            icon={faTrashAlt}
 | 
				
			||||||
                                        className={'text-neutral-400 hover:text-red-400 transition-color duration-150'}
 | 
					                                            className={'text-neutral-400 hover:text-red-400 transition-colors duration-150'}
 | 
				
			||||||
                                        />
 | 
					                                        />
 | 
				
			||||||
                                    </button>
 | 
					                                    </button>
 | 
				
			||||||
                                </div>
 | 
					                                </div>
 | 
				
			||||||
@ -103,5 +107,6 @@ export default () => {
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                </ContentBox>
 | 
					                </ContentBox>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					import React, { useState } from 'react';
 | 
				
			||||||
 | 
					import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 | 
				
			||||||
 | 
					import { faSearch } from '@fortawesome/free-solid-svg-icons/faSearch';
 | 
				
			||||||
 | 
					import useEventListener from '@/plugins/useEventListener';
 | 
				
			||||||
 | 
					import SearchModal from '@/components/dashboard/search/SearchModal';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default () => {
 | 
				
			||||||
 | 
					    const [ visible, setVisible ] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEventListener('keydown', (e: KeyboardEvent) => {
 | 
				
			||||||
 | 
					        if ([ 'input', 'textarea' ].indexOf(((e.target as HTMLElement).tagName || 'input').toLowerCase()) < 0) {
 | 
				
			||||||
 | 
					            if (!visible && e.key.toLowerCase() === 'k') {
 | 
				
			||||||
 | 
					                setVisible(true);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <>
 | 
				
			||||||
 | 
					            {visible &&
 | 
				
			||||||
 | 
					            <SearchModal
 | 
				
			||||||
 | 
					                appear={true}
 | 
				
			||||||
 | 
					                visible={visible}
 | 
				
			||||||
 | 
					                onDismissed={() => setVisible(false)}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            <div className={'navigation-link'} onClick={() => setVisible(true)}>
 | 
				
			||||||
 | 
					                <FontAwesomeIcon icon={faSearch}/>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										123
									
								
								resources/scripts/components/dashboard/search/SearchModal.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								resources/scripts/components/dashboard/search/SearchModal.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,123 @@
 | 
				
			|||||||
 | 
					import React, { useEffect, useRef, useState } from 'react';
 | 
				
			||||||
 | 
					import Modal, { RequiredModalProps } from '@/components/elements/Modal';
 | 
				
			||||||
 | 
					import { Field, Form, Formik, FormikHelpers, useFormikContext } from 'formik';
 | 
				
			||||||
 | 
					import { Actions, useStoreActions, useStoreState } from 'easy-peasy';
 | 
				
			||||||
 | 
					import { object, string } from 'yup';
 | 
				
			||||||
 | 
					import { debounce } from 'lodash-es';
 | 
				
			||||||
 | 
					import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper';
 | 
				
			||||||
 | 
					import InputSpinner from '@/components/elements/InputSpinner';
 | 
				
			||||||
 | 
					import getServers from '@/api/getServers';
 | 
				
			||||||
 | 
					import { Server } from '@/api/server/getServer';
 | 
				
			||||||
 | 
					import { ApplicationStore } from '@/state';
 | 
				
			||||||
 | 
					import { httpErrorToHuman } from '@/api/http';
 | 
				
			||||||
 | 
					import { Link } from 'react-router-dom';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Props = RequiredModalProps;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface Values {
 | 
				
			||||||
 | 
					    term: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SearchWatcher = () => {
 | 
				
			||||||
 | 
					    const { values, submitForm } = useFormikContext<Values>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        if (values.term.length >= 3) {
 | 
				
			||||||
 | 
					            submitForm();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, [ values.term ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default ({ ...props }: Props) => {
 | 
				
			||||||
 | 
					    const ref = useRef<HTMLInputElement>(null);
 | 
				
			||||||
 | 
					    const [ loading, setLoading ] = useState(false);
 | 
				
			||||||
 | 
					    const [ servers, setServers ] = useState<Server[]>([]);
 | 
				
			||||||
 | 
					    const isAdmin = useStoreState(state => state.user.data!.rootAdmin);
 | 
				
			||||||
 | 
					    const { addError, clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const search = debounce(({ term }: Values, { setSubmitting }: FormikHelpers<Values>) => {
 | 
				
			||||||
 | 
					        setLoading(true);
 | 
				
			||||||
 | 
					        setSubmitting(false);
 | 
				
			||||||
 | 
					        clearFlashes('search');
 | 
				
			||||||
 | 
					        getServers(term)
 | 
				
			||||||
 | 
					            .then(servers => setServers(servers.items.filter((_, index) => index < 5)))
 | 
				
			||||||
 | 
					            .catch(error => {
 | 
				
			||||||
 | 
					                console.error(error);
 | 
				
			||||||
 | 
					                addError({ key: 'search', message: httpErrorToHuman(error) });
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .then(() => setLoading(false));
 | 
				
			||||||
 | 
					    }, 500);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        if (props.visible) {
 | 
				
			||||||
 | 
					            setTimeout(() => ref.current?.focus(), 250);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, [ props.visible ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <Formik
 | 
				
			||||||
 | 
					            onSubmit={search}
 | 
				
			||||||
 | 
					            validationSchema={object().shape({
 | 
				
			||||||
 | 
					                term: string()
 | 
				
			||||||
 | 
					                    .min(3, 'Please enter at least three characters to begin searching.')
 | 
				
			||||||
 | 
					                    .required('A search term must be provided.'),
 | 
				
			||||||
 | 
					            })}
 | 
				
			||||||
 | 
					            initialValues={{ term: '' } as Values}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <Modal {...props}>
 | 
				
			||||||
 | 
					                <Form>
 | 
				
			||||||
 | 
					                    <FormikFieldWrapper
 | 
				
			||||||
 | 
					                        name={'term'}
 | 
				
			||||||
 | 
					                        label={'Search term'}
 | 
				
			||||||
 | 
					                        description={
 | 
				
			||||||
 | 
					                            isAdmin
 | 
				
			||||||
 | 
					                                ? 'Enter a server name, user email, or uuid to begin searching.'
 | 
				
			||||||
 | 
					                                : 'Enter a server name to begin searching.'
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
 | 
					                        <SearchWatcher/>
 | 
				
			||||||
 | 
					                        <InputSpinner visible={loading}>
 | 
				
			||||||
 | 
					                            <Field
 | 
				
			||||||
 | 
					                                innerRef={ref}
 | 
				
			||||||
 | 
					                                name={'term'}
 | 
				
			||||||
 | 
					                                className={'input-dark'}
 | 
				
			||||||
 | 
					                            />
 | 
				
			||||||
 | 
					                        </InputSpinner>
 | 
				
			||||||
 | 
					                    </FormikFieldWrapper>
 | 
				
			||||||
 | 
					                </Form>
 | 
				
			||||||
 | 
					                {servers.length > 0 &&
 | 
				
			||||||
 | 
					                <div className={'mt-6'}>
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        servers.map(server => (
 | 
				
			||||||
 | 
					                            <Link
 | 
				
			||||||
 | 
					                                key={server.uuid}
 | 
				
			||||||
 | 
					                                to={`/server/${server.id}`}
 | 
				
			||||||
 | 
					                                className={'flex items-center block bg-neutral-900 p-4 rounded border-l-4 border-neutral-900 no-underline hover:shadow hover:border-cyan-500 transition-colors duration-250'}
 | 
				
			||||||
 | 
					                                onClick={() => props.onDismissed()}
 | 
				
			||||||
 | 
					                            >
 | 
				
			||||||
 | 
					                                <div>
 | 
				
			||||||
 | 
					                                    <p className={'text-sm'}>{server.name}</p>
 | 
				
			||||||
 | 
					                                    <p className={'mt-1 text-xs text-neutral-400'}>
 | 
				
			||||||
 | 
					                                        {
 | 
				
			||||||
 | 
					                                            server.allocations.filter(alloc => alloc.default).map(allocation => (
 | 
				
			||||||
 | 
					                                                <span key={allocation.ip + allocation.port.toString()}>{allocation.alias || allocation.ip}:{allocation.port}</span>
 | 
				
			||||||
 | 
					                                            ))
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    </p>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                                <div className={'flex-1 text-right'}>
 | 
				
			||||||
 | 
					                                    <span className={'text-xs py-1 px-2 bg-cyan-800 text-cyan-100 rounded'}>
 | 
				
			||||||
 | 
					                                        {server.node}
 | 
				
			||||||
 | 
					                                    </span>
 | 
				
			||||||
 | 
					                                </div>
 | 
				
			||||||
 | 
					                            </Link>
 | 
				
			||||||
 | 
					                        ))
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            </Modal>
 | 
				
			||||||
 | 
					        </Formik>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										26
									
								
								resources/scripts/components/elements/Can.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								resources/scripts/components/elements/Can.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					import { usePermissions } from '@/plugins/usePermissions';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface Props {
 | 
				
			||||||
 | 
					    action: string | string[];
 | 
				
			||||||
 | 
					    matchAny?: boolean;
 | 
				
			||||||
 | 
					    renderOnError?: React.ReactNode | null;
 | 
				
			||||||
 | 
					    children: React.ReactNode;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Can = ({ action, matchAny = false, renderOnError, children }: Props) => {
 | 
				
			||||||
 | 
					    const can = usePermissions(action);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <>
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ((matchAny && can.filter(p => p).length > 0) || (!matchAny && can.every(p => p))) ?
 | 
				
			||||||
 | 
					                    children
 | 
				
			||||||
 | 
					                    :
 | 
				
			||||||
 | 
					                    renderOnError
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        </>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Can;
 | 
				
			||||||
@ -1,25 +1,25 @@
 | 
				
			|||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
import Modal from '@/components/elements/Modal';
 | 
					import Modal, { RequiredModalProps } from '@/components/elements/Modal';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface Props {
 | 
					type Props = {
 | 
				
			||||||
    title: string;
 | 
					    title: string;
 | 
				
			||||||
    buttonText: string;
 | 
					    buttonText: string;
 | 
				
			||||||
    children: string;
 | 
					    children: string;
 | 
				
			||||||
    visible: boolean;
 | 
					 | 
				
			||||||
    onConfirmed: () => void;
 | 
					    onConfirmed: () => void;
 | 
				
			||||||
    onCanceled: () => void;
 | 
					    showSpinnerOverlay?: boolean;
 | 
				
			||||||
}
 | 
					} & RequiredModalProps;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ConfirmationModal = ({ title, children, visible, buttonText, onConfirmed, onCanceled }: Props) => (
 | 
					const ConfirmationModal = ({ title, appear, children, visible, buttonText, onConfirmed, showSpinnerOverlay, onDismissed }: Props) => (
 | 
				
			||||||
    <Modal
 | 
					    <Modal
 | 
				
			||||||
        appear={true}
 | 
					        appear={appear || true}
 | 
				
			||||||
        visible={visible}
 | 
					        visible={visible}
 | 
				
			||||||
        onDismissed={() => onCanceled()}
 | 
					        showSpinnerOverlay={showSpinnerOverlay}
 | 
				
			||||||
 | 
					        onDismissed={() => onDismissed()}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
        <h3 className={'mb-6'}>{title}</h3>
 | 
					        <h3 className={'mb-6'}>{title}</h3>
 | 
				
			||||||
        <p className={'text-sm'}>{children}</p>
 | 
					        <p className={'text-sm'}>{children}</p>
 | 
				
			||||||
        <div className={'flex items-center justify-end mt-8'}>
 | 
					        <div className={'flex items-center justify-end mt-8'}>
 | 
				
			||||||
            <button className={'btn btn-secondary btn-sm'} onClick={() => onCanceled()}>
 | 
					            <button className={'btn btn-secondary btn-sm'} onClick={() => onDismissed()}>
 | 
				
			||||||
                Cancel
 | 
					                Cancel
 | 
				
			||||||
            </button>
 | 
					            </button>
 | 
				
			||||||
            <button className={'btn btn-red btn-sm ml-4'} onClick={() => onConfirmed()}>
 | 
					            <button className={'btn btn-red btn-sm ml-4'} onClick={() => onConfirmed()}>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,14 +1,16 @@
 | 
				
			|||||||
import * as React from 'react';
 | 
					import * as React from 'react';
 | 
				
			||||||
import classNames from 'classnames';
 | 
					import classNames from 'classnames';
 | 
				
			||||||
import FlashMessageRender from '@/components/FlashMessageRender';
 | 
					import FlashMessageRender from '@/components/FlashMessageRender';
 | 
				
			||||||
 | 
					import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Props = Readonly<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> & {
 | 
					type Props = Readonly<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> & {
 | 
				
			||||||
    title?: string;
 | 
					    title?: string;
 | 
				
			||||||
    borderColor?: string;
 | 
					    borderColor?: string;
 | 
				
			||||||
    showFlashes?: string | boolean;
 | 
					    showFlashes?: string | boolean;
 | 
				
			||||||
 | 
					    showLoadingOverlay?: boolean;
 | 
				
			||||||
}>;
 | 
					}>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ContentBox = ({ title, borderColor, showFlashes, children, ...props }: Props) => (
 | 
					const ContentBox = ({ title, borderColor, showFlashes, showLoadingOverlay, children, ...props }: Props) => (
 | 
				
			||||||
    <div {...props}>
 | 
					    <div {...props}>
 | 
				
			||||||
        {title && <h2 className={'text-neutral-300 mb-4 px-4'}>{title}</h2>}
 | 
					        {title && <h2 className={'text-neutral-300 mb-4 px-4'}>{title}</h2>}
 | 
				
			||||||
        {showFlashes &&
 | 
					        {showFlashes &&
 | 
				
			||||||
@ -20,6 +22,7 @@ const ContentBox = ({ title, borderColor, showFlashes, children, ...props }: Pro
 | 
				
			|||||||
        <div className={classNames('bg-neutral-700 p-4 rounded shadow-lg relative', borderColor, {
 | 
					        <div className={classNames('bg-neutral-700 p-4 rounded shadow-lg relative', borderColor, {
 | 
				
			||||||
            'border-t-4': !!borderColor,
 | 
					            'border-t-4': !!borderColor,
 | 
				
			||||||
        })}>
 | 
					        })}>
 | 
				
			||||||
 | 
					            <SpinnerOverlay visible={showLoadingOverlay || false}/>
 | 
				
			||||||
            {children}
 | 
					            {children}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,7 @@ import classNames from 'classnames';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
interface OwnProps {
 | 
					interface OwnProps {
 | 
				
			||||||
    name: string;
 | 
					    name: string;
 | 
				
			||||||
 | 
					    light?: boolean;
 | 
				
			||||||
    label?: string;
 | 
					    label?: string;
 | 
				
			||||||
    description?: string;
 | 
					    description?: string;
 | 
				
			||||||
    validate?: (value: any) => undefined | string | Promise<any>;
 | 
					    validate?: (value: any) => undefined | string | Promise<any>;
 | 
				
			||||||
@ -11,19 +12,19 @@ interface OwnProps {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
type Props = OwnProps & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'name'>;
 | 
					type Props = OwnProps & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'name'>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Field = ({ id, name, label, description, validate, className, ...props }: Props) => (
 | 
					const Field = ({ id, name, light = false, label, description, validate, className, ...props }: Props) => (
 | 
				
			||||||
    <FormikField name={name} validate={validate}>
 | 
					    <FormikField name={name} validate={validate}>
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            ({ field, form: { errors, touched } }: FieldProps) => (
 | 
					            ({ field, form: { errors, touched } }: FieldProps) => (
 | 
				
			||||||
                <React.Fragment>
 | 
					                <React.Fragment>
 | 
				
			||||||
                    {label &&
 | 
					                    {label &&
 | 
				
			||||||
                    <label htmlFor={id} className={'input-dark-label'}>{label}</label>
 | 
					                    <label htmlFor={id} className={light ? undefined : 'input-dark-label'}>{label}</label>
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    <input
 | 
					                    <input
 | 
				
			||||||
                        id={id}
 | 
					                        id={id}
 | 
				
			||||||
                        {...field}
 | 
					                        {...field}
 | 
				
			||||||
                        {...props}
 | 
					                        {...props}
 | 
				
			||||||
                        className={classNames((className || 'input-dark'), {
 | 
					                        className={classNames((className || (light ? 'input' : 'input-dark')), {
 | 
				
			||||||
                            error: touched[field.name] && errors[field.name],
 | 
					                            error: touched[field.name] && errors[field.name],
 | 
				
			||||||
                        })}
 | 
					                        })}
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										22
									
								
								resources/scripts/components/elements/InputSpinner.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								resources/scripts/components/elements/InputSpinner.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					import Spinner from '@/components/elements/Spinner';
 | 
				
			||||||
 | 
					import { CSSTransition } from 'react-transition-group';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const InputSpinner = ({ visible, children }: { visible: boolean, children: React.ReactNode }) => (
 | 
				
			||||||
 | 
					    <div className={'relative'}>
 | 
				
			||||||
 | 
					        <CSSTransition
 | 
				
			||||||
 | 
					            timeout={250}
 | 
				
			||||||
 | 
					            in={visible}
 | 
				
			||||||
 | 
					            unmountOnExit={true}
 | 
				
			||||||
 | 
					            appear={true}
 | 
				
			||||||
 | 
					            classNames={'fade'}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					            <div className={'absolute pin-r h-full flex items-center justify-end pr-3'}>
 | 
				
			||||||
 | 
					                <Spinner size={'tiny'}/>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </CSSTransition>
 | 
				
			||||||
 | 
					        {children}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default InputSpinner;
 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user