From d9593b23abd3d7af00a5873a80bfc00fbe77e76d Mon Sep 17 00:00:00 2001
From: Dane Everitt <dane@daneeveritt.com>
Date: Sat, 2 Mar 2019 15:58:56 -0800
Subject: [PATCH] Paginate server results when viewing a node, closes #1404

---
 CHANGELOG.md                                  |  3 ++
 .../Repository/NodeRepositoryInterface.php    | 10 -------
 .../Repository/ServerRepositoryInterface.php  | 10 +++++++
 .../Controllers/Admin/NodesController.php     | 30 +++++++++++--------
 app/Repositories/Eloquent/NodeRepository.php  | 21 -------------
 .../Eloquent/ServerRepository.php             | 17 +++++++++++
 .../admin/nodes/view/servers.blade.php        |  7 ++++-
 7 files changed, 54 insertions(+), 44 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index b3cd8f8b..200e0b34 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,9 @@ a server through the API.
 * The `PATCH` endpoint for `/api/applications/servers/{server}/build` now accepts an array called `limits` to match
 the response from the server `GET` endpoint.
 
+### Added
+* The server listing for a node is now paginated to 25 servers per page to improve performance on large nodes.
+
 ## v0.7.12 (Derelict Dermodactylus)
 ### Fixed
 * Fixes an issue with the locations API endpoint referencing an invalid namespace.
diff --git a/app/Contracts/Repository/NodeRepositoryInterface.php b/app/Contracts/Repository/NodeRepositoryInterface.php
index c533032c..aef04e2a 100644
--- a/app/Contracts/Repository/NodeRepositoryInterface.php
+++ b/app/Contracts/Repository/NodeRepositoryInterface.php
@@ -55,16 +55,6 @@ interface NodeRepositoryInterface extends RepositoryInterface, SearchableInterfa
      */
     public function loadNodeAllocations(Node $node, bool $refresh = false): Node;
 
-    /**
-     * Return a node with all of the servers attached to that node.
-     *
-     * @param int $id
-     * @return \Pterodactyl\Models\Node
-     *
-     * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
-     */
-    public function getNodeServers(int $id): Node;
-
     /**
      * Return a collection of nodes for all locations to use in server creation UI.
      *
diff --git a/app/Contracts/Repository/ServerRepositoryInterface.php b/app/Contracts/Repository/ServerRepositoryInterface.php
index f00d29d8..ba853398 100644
--- a/app/Contracts/Repository/ServerRepositoryInterface.php
+++ b/app/Contracts/Repository/ServerRepositoryInterface.php
@@ -152,4 +152,14 @@ interface ServerRepositoryInterface extends RepositoryInterface, SearchableInter
      * @return int
      */
     public function getSuspendedServersCount(): int;
+
+    /**
+     * Returns all of the servers that exist for a given node in a paginated response.
+     *
+     * @param int $node
+     * @param int $limit
+     *
+     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
+     */
+    public function loadAllServersForNode(int $node, int $limit): LengthAwarePaginator;
 }
diff --git a/app/Http/Controllers/Admin/NodesController.php b/app/Http/Controllers/Admin/NodesController.php
index ab1ce5eb..28266eac 100644
--- a/app/Http/Controllers/Admin/NodesController.php
+++ b/app/Http/Controllers/Admin/NodesController.php
@@ -24,6 +24,7 @@ use Pterodactyl\Services\Allocations\AssignmentService;
 use Pterodactyl\Services\Helpers\SoftwareVersionService;
 use Pterodactyl\Http\Requests\Admin\Node\NodeFormRequest;
 use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
+use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
 use Pterodactyl\Http\Requests\Admin\Node\AllocationFormRequest;
 use Pterodactyl\Services\Allocations\AllocationDeletionService;
 use Pterodactyl\Contracts\Repository\LocationRepositoryInterface;
@@ -32,6 +33,11 @@ use Pterodactyl\Http\Requests\Admin\Node\AllocationAliasFormRequest;
 
 class NodesController extends Controller
 {
+    /**
+     * @var \Pterodactyl\Services\Allocations\AllocationDeletionService
+     */
+    protected $allocationDeletionService;
+
     /**
      * @var \Prologue\Alerts\AlertsMessageBag
      */
@@ -72,6 +78,11 @@ class NodesController extends Controller
      */
     protected $repository;
 
+    /**
+     * @var \Pterodactyl\Contracts\Repository\ServerRepositoryInterface
+     */
+    protected $serverRepository;
+
     /**
      * @var \Pterodactyl\Services\Nodes\NodeUpdateService
      */
@@ -81,10 +92,6 @@ class NodesController extends Controller
      * @var \Pterodactyl\Services\Helpers\SoftwareVersionService
      */
     protected $versionService;
-    /**
-     * @var \Pterodactyl\Services\Allocations\AllocationDeletionService
-     */
-    private $allocationDeletionService;
 
     /**
      * NodesController constructor.
@@ -98,6 +105,7 @@ class NodesController extends Controller
      * @param \Pterodactyl\Services\Nodes\NodeDeletionService                 $deletionService
      * @param \Pterodactyl\Contracts\Repository\LocationRepositoryInterface   $locationRepository
      * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface       $repository
+     * @param \Pterodactyl\Contracts\Repository\ServerRepositoryInterface     $serverRepository
      * @param \Pterodactyl\Services\Nodes\NodeUpdateService                   $updateService
      * @param \Pterodactyl\Services\Helpers\SoftwareVersionService            $versionService
      */
@@ -111,6 +119,7 @@ class NodesController extends Controller
         NodeDeletionService $deletionService,
         LocationRepositoryInterface $locationRepository,
         NodeRepositoryInterface $repository,
+        ServerRepositoryInterface $serverRepository,
         NodeUpdateService $updateService,
         SoftwareVersionService $versionService
     ) {
@@ -123,6 +132,7 @@ class NodesController extends Controller
         $this->deletionService = $deletionService;
         $this->locationRepository = $locationRepository;
         $this->repository = $repository;
+        $this->serverRepository = $serverRepository;
         $this->updateService = $updateService;
         $this->versionService = $versionService;
     }
@@ -178,8 +188,6 @@ class NodesController extends Controller
      *
      * @param \Pterodactyl\Models\Node $node
      * @return \Illuminate\View\View
-     *
-     * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
      */
     public function viewIndex(Node $node)
     {
@@ -235,19 +243,17 @@ class NodesController extends Controller
     /**
      * Shows the server listing page for a specific node.
      *
-     * @param int $node
+     * @param \Pterodactyl\Models\Node $node
      * @return \Illuminate\View\View
-     *
-     * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
      */
-    public function viewServers($node)
+    public function viewServers(Node $node)
     {
-        $node = $this->repository->getNodeServers($node);
+        $servers = $this->serverRepository->loadAllServersForNode($node->id, 25);
         Javascript::put([
             'node' => collect($node->makeVisible('daemonSecret'))->only(['scheme', 'fqdn', 'daemonListen', 'daemonSecret']),
         ]);
 
-        return view('admin.nodes.view.servers', ['node' => $node]);
+        return view('admin.nodes.view.servers', ['node' => $node, 'servers' => $servers]);
     }
 
     /**
diff --git a/app/Repositories/Eloquent/NodeRepository.php b/app/Repositories/Eloquent/NodeRepository.php
index 4f59fddc..3b86f2ee 100644
--- a/app/Repositories/Eloquent/NodeRepository.php
+++ b/app/Repositories/Eloquent/NodeRepository.php
@@ -6,10 +6,8 @@ use Generator;
 use Pterodactyl\Models\Node;
 use Illuminate\Support\Collection;
 use Pterodactyl\Repositories\Concerns\Searchable;
-use Illuminate\Database\Eloquent\ModelNotFoundException;
 use Illuminate\Contracts\Pagination\LengthAwarePaginator;
 use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
-use Pterodactyl\Exceptions\Repository\RecordNotFoundException;
 
 class NodeRepository extends EloquentRepository implements NodeRepositoryInterface
 {
@@ -140,25 +138,6 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa
         return $node;
     }
 
-    /**
-     * Return a node with all of the servers attached to that node.
-     *
-     * @param int $id
-     * @return \Pterodactyl\Models\Node
-     *
-     * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
-     */
-    public function getNodeServers(int $id): Node
-    {
-        try {
-            return $this->getBuilder()->with([
-                'servers.user', 'servers.nest', 'servers.egg',
-            ])->findOrFail($id, $this->getColumns());
-        } catch (ModelNotFoundException $exception) {
-            throw new RecordNotFoundException;
-        }
-    }
-
     /**
      * Return a collection of nodes for all locations to use in server creation UI.
      *
diff --git a/app/Repositories/Eloquent/ServerRepository.php b/app/Repositories/Eloquent/ServerRepository.php
index 3c0248be..820093cf 100644
--- a/app/Repositories/Eloquent/ServerRepository.php
+++ b/app/Repositories/Eloquent/ServerRepository.php
@@ -2,6 +2,7 @@
 
 namespace Pterodactyl\Repositories\Eloquent;
 
+use Pterodactyl\Models\Node;
 use Pterodactyl\Models\User;
 use Webmozart\Assert\Assert;
 use Pterodactyl\Models\Server;
@@ -338,4 +339,20 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt
     {
         return $this->getBuilder()->where('suspended', true)->count();
     }
+
+    /**
+     * Returns all of the servers that exist for a given node in a paginated response.
+     *
+     * @param int $node
+     * @param int $limit
+     *
+     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
+     */
+    public function loadAllServersForNode(int $node, int $limit): LengthAwarePaginator
+    {
+        return $this->getBuilder()
+            ->with(['user', 'nest', 'egg'])
+            ->where('node_id', '=', $node)
+            ->paginate($limit);
+    }
 }
diff --git a/resources/themes/pterodactyl/admin/nodes/view/servers.blade.php b/resources/themes/pterodactyl/admin/nodes/view/servers.blade.php
index de8ef5de..5aed0ccb 100644
--- a/resources/themes/pterodactyl/admin/nodes/view/servers.blade.php
+++ b/resources/themes/pterodactyl/admin/nodes/view/servers.blade.php
@@ -51,7 +51,7 @@
                         <th class="text-center">CPU</th>
                         <th class="text-center">Status</th>
                     </tr>
-                    @foreach($node->servers as $server)
+                    @foreach($servers as $server)
                         <tr data-server="{{ $server->uuid }}">
                             <td><code>{{ $server->uuidShort }}</code></td>
                             <td><a href="{{ route('admin.servers.view', $server->id) }}">{{ $server->name }}</a></td>
@@ -64,6 +64,11 @@
                         </tr>
                     @endforeach
                 </table>
+                @if($servers->hasPages())
+                    <div class="box-footer with-border">
+                        <div class="col-md-12 text-center">{!! $servers->render() !!}</div>
+                    </div>
+                @endif
             </div>
         </div>
     </div>