Merge pull request #1889 from matthewpi/feature/server-transfers-actually
Server Transfers (pterodactyl/panel#18)
This commit is contained in:
commit
536f056f2c
|
@ -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,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -171,6 +171,7 @@ class Node extends Model
|
||||||
],
|
],
|
||||||
'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,
|
||||||
|
@ -236,4 +237,19 @@ class Node extends Model
|
||||||
{
|
{
|
||||||
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@ 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
|
* @property \Pterodactyl\Models\Backup[]|\Illuminate\Database\Eloquent\Collection $backups
|
||||||
*/
|
*/
|
||||||
class Server extends Model
|
class Server extends Model
|
||||||
|
@ -186,7 +187,7 @@ class Server extends Model
|
||||||
*/
|
*/
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
@ -341,6 +342,16 @@ class Server extends Model
|
||||||
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
|
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
@include('admin.servers.partials.navigation')
|
@include('admin.servers.partials.navigation')
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<div class="box box-primary">
|
<div class="box box-danger">
|
||||||
<div class="box-header with-border">
|
<div class="box-header with-border">
|
||||||
<h3 class="box-title">Reinstall Server</h3>
|
<h3 class="box-title">Reinstall Server</h3>
|
||||||
</div>
|
</div>
|
||||||
|
@ -95,5 +95,89 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@if($canTransfer)
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="box box-success">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">Transfer Server</h3>
|
||||||
|
</div>
|
||||||
|
<div class="box-body">
|
||||||
|
<p>
|
||||||
|
Hopefully, you will soon be able to move servers around without needing to do a bunch of confusing
|
||||||
|
operations manually and it will work fluidly and with no problems.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="box-footer">
|
||||||
|
<button class="btn btn-success" data-toggle="modal" data-target="#transferServerModal">Transfer Server</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal fade" id="transferServerModal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<!-- TODO: Change route -->
|
||||||
|
<form action="{{ route('admin.servers.view.manage.transfer', $server->id) }}" method="POST">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title">Transfer Server</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="pNodeId">Node</label>
|
||||||
|
<select name="node_id" id="pNodeId" class="form-control">
|
||||||
|
@foreach($locations as $location)
|
||||||
|
<optgroup label="{{ $location->long }} ({{ $location->short }})">
|
||||||
|
@foreach($location->nodes as $node)
|
||||||
|
|
||||||
|
@if($node->id != $server->node_id)
|
||||||
|
<option value="{{ $node->id }}"
|
||||||
|
@if($location->id === old('location_id')) selected @endif
|
||||||
|
>{{ $node->name }}</option>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@endforeach
|
||||||
|
</optgroup>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
<p class="small text-muted no-margin">The node which this server will be transferred to.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="pAllocation">Default Allocation</label>
|
||||||
|
<select name="allocation_id" id="pAllocation" class="form-control"></select>
|
||||||
|
<p class="small text-muted no-margin">The main allocation that will be assigned to this server.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group col-md-12">
|
||||||
|
<label for="pAllocationAdditional">Additional Allocation(s)</label>
|
||||||
|
<select name="allocation_additional[]" id="pAllocationAdditional" class="form-control" multiple></select>
|
||||||
|
<p class="small text-muted no-margin">Additional allocations to assign to this server on creation.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
{!! csrf_field() !!}
|
||||||
|
<button type="button" class="btn btn-default btn-sm pull-left" data-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-success btn-sm">Confirm</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
|
@section('footer-scripts')
|
||||||
|
@parent
|
||||||
|
{!! Theme::js('vendor/lodash/lodash.js') !!}
|
||||||
|
|
||||||
|
@if($canTransfer)
|
||||||
|
{!! Theme::js('js/admin/server/transfer.js') !!}
|
||||||
|
@endif
|
||||||
|
@endsection
|
||||||
|
|
|
@ -125,6 +125,7 @@ Route::group(['prefix' => 'servers'], function () {
|
||||||
Route::post('/view/{server}/manage/toggle', 'ServersController@toggleInstall')->name('admin.servers.view.manage.toggle');
|
Route::post('/view/{server}/manage/toggle', 'ServersController@toggleInstall')->name('admin.servers.view.manage.toggle');
|
||||||
Route::post('/view/{server}/manage/suspension', 'ServersController@manageSuspension')->name('admin.servers.view.manage.suspension');
|
Route::post('/view/{server}/manage/suspension', 'ServersController@manageSuspension')->name('admin.servers.view.manage.suspension');
|
||||||
Route::post('/view/{server}/manage/reinstall', 'ServersController@reinstallServer')->name('admin.servers.view.manage.reinstall');
|
Route::post('/view/{server}/manage/reinstall', 'ServersController@reinstallServer')->name('admin.servers.view.manage.reinstall');
|
||||||
|
Route::post('/view/{server}/manage/transfer', 'Servers\ServerTransferController@transfer')->name('admin.servers.view.manage.transfer');
|
||||||
Route::post('/view/{server}/delete', 'ServersController@delete');
|
Route::post('/view/{server}/delete', 'ServersController@delete');
|
||||||
|
|
||||||
Route::patch('/view/{server}/details', 'ServersController@setDetails');
|
Route::patch('/view/{server}/details', 'ServersController@setDetails');
|
||||||
|
|
|
@ -12,5 +12,9 @@ Route::group(['prefix' => '/servers/{uuid}'], function () {
|
||||||
Route::get('/install', 'Servers\ServerInstallController@index');
|
Route::get('/install', 'Servers\ServerInstallController@index');
|
||||||
Route::post('/install', 'Servers\ServerInstallController@store');
|
Route::post('/install', 'Servers\ServerInstallController@store');
|
||||||
|
|
||||||
|
Route::post('/archive', 'Servers\ServerTransferController@archive');
|
||||||
|
Route::get('/transfer/failure', 'Servers\ServerTransferController@failure');
|
||||||
|
Route::get('/transfer/success', 'Servers\ServerTransferController@success');
|
||||||
|
|
||||||
Route::post('/backup/{backup}', 'Servers\ServerBackupController');
|
Route::post('/backup/{backup}', 'Servers\ServerBackupController');
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue