Skip to content

Commit fd8d7c3

Browse files
authored
Merge pull request pterodactyl#1130 from stanjg/feature/stats-page
Added a statistics page to monitor the panel usage
2 parents 5be4520 + 60e1ffa commit fd8d7c3

File tree

14 files changed

+576
-0
lines changed

14 files changed

+576
-0
lines changed

app/Contracts/Repository/NodeRepositoryInterface.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ interface NodeRepositoryInterface extends RepositoryInterface, SearchableInterfa
2121
*/
2222
public function getUsageStats(Node $node): array;
2323

24+
/**
25+
* Return the usage stats for a single node.
26+
*
27+
* @param \Pterodactyl\Models\Node $node
28+
* @return array
29+
*/
30+
public function getUsageStatsRaw(Node $node): array;
31+
2432
/**
2533
* Return all available nodes with a searchable interface.
2634
*

app/Contracts/Repository/RepositoryInterface.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,4 +200,11 @@ public function insert(array $data): bool;
200200
* @return bool
201201
*/
202202
public function insertIgnore(array $values): bool;
203+
204+
/**
205+
* Get the amount of entries in the database
206+
*
207+
* @return int
208+
*/
209+
public function count(): int;
203210
}

app/Contracts/Repository/ServerRepositoryInterface.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,11 @@ public function getServersForPowerActionCount(array $servers = [], array $nodes
145145
* @return bool
146146
*/
147147
public function isUniqueUuidCombo(string $uuid, string $short): bool;
148+
149+
/**
150+
* Get the amount of servers that are suspended
151+
*
152+
* @return int
153+
*/
154+
public function getSuspendedServersCount(): int;
148155
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
namespace Pterodactyl\Http\Controllers\Admin;
4+
5+
use Illuminate\Http\Request;
6+
use Illuminate\Support\Facades\DB;
7+
use Pterodactyl\Contracts\Repository\AllocationRepositoryInterface;
8+
use Pterodactyl\Contracts\Repository\DatabaseRepositoryInterface;
9+
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
10+
use Pterodactyl\Contracts\Repository\NodeRepositoryInterface;
11+
use Pterodactyl\Contracts\Repository\ServerRepositoryInterface;
12+
use Pterodactyl\Contracts\Repository\UserRepositoryInterface;
13+
use Pterodactyl\Http\Controllers\Controller;
14+
use Pterodactyl\Traits\Controllers\PlainJavascriptInjection;
15+
16+
class StatisticsController extends Controller
17+
{
18+
use PlainJavascriptInjection;
19+
20+
private $allocationRepository;
21+
22+
private $databaseRepository;
23+
24+
private $eggRepository;
25+
26+
private $nodeRepository;
27+
28+
private $serverRepository;
29+
30+
private $userRepository;
31+
32+
function __construct(
33+
AllocationRepositoryInterface $allocationRepository,
34+
DatabaseRepositoryInterface $databaseRepository,
35+
EggRepositoryInterface $eggRepository,
36+
NodeRepositoryInterface $nodeRepository,
37+
ServerRepositoryInterface $serverRepository,
38+
UserRepositoryInterface $userRepository
39+
)
40+
{
41+
$this->allocationRepository = $allocationRepository;
42+
$this->databaseRepository = $databaseRepository;
43+
$this->eggRepository = $eggRepository;
44+
$this->nodeRepository = $nodeRepository;
45+
$this->serverRepository = $serverRepository;
46+
$this->userRepository = $userRepository;
47+
}
48+
49+
public function index()
50+
{
51+
$servers = $this->serverRepository->all();
52+
$nodes = $this->nodeRepository->all();
53+
$usersCount = $this->userRepository->count();
54+
$eggsCount = $this->eggRepository->count();
55+
$databasesCount = $this->databaseRepository->count();
56+
$totalAllocations = $this->allocationRepository->count();
57+
$suspendedServersCount = $this->serverRepository->getSuspendedServersCount();
58+
59+
$totalServerRam = 0;
60+
$totalNodeRam = 0;
61+
$totalServerDisk = 0;
62+
$totalNodeDisk = 0;
63+
foreach ($nodes as $node) {
64+
$stats = $this->nodeRepository->getUsageStatsRaw($node);
65+
$totalServerRam += $stats['memory']['value'];
66+
$totalNodeRam += $stats['memory']['max'];
67+
$totalServerDisk += $stats['disk']['value'];
68+
$totalNodeDisk += $stats['disk']['max'];
69+
}
70+
71+
$tokens = [];
72+
foreach ($nodes as $node) {
73+
$tokens[$node->id] = $node->daemonSecret;
74+
}
75+
76+
$this->injectJavascript([
77+
'servers' => $servers,
78+
'suspendedServers' => $suspendedServersCount,
79+
'totalServerRam' => $totalServerRam,
80+
'totalNodeRam' => $totalNodeRam,
81+
'totalServerDisk' => $totalServerDisk,
82+
'totalNodeDisk' => $totalNodeDisk,
83+
'nodes' => $nodes,
84+
'tokens' => $tokens,
85+
]);
86+
87+
return view('admin.statistics', [
88+
'servers' => $servers,
89+
'nodes' => $nodes,
90+
'usersCount' => $usersCount,
91+
'eggsCount' => $eggsCount,
92+
'totalServerRam' => $totalServerRam,
93+
'databasesCount' => $databasesCount,
94+
'totalNodeRam' => $totalNodeRam,
95+
'totalNodeDisk' => $totalNodeDisk,
96+
'totalServerDisk' => $totalServerDisk,
97+
'totalAllocations' => $totalAllocations,
98+
]);
99+
}
100+
101+
}

app/Repositories/Eloquent/EloquentRepository.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,4 +296,14 @@ public function insertIgnore(array $values): bool
296296

297297
return $this->getBuilder()->getConnection()->statement($statement, $bindings);
298298
}
299+
300+
/**
301+
* Get the amount of entries in the database
302+
*
303+
* @return int
304+
*/
305+
public function count(): int
306+
{
307+
return $this->getBuilder()->count();
308+
}
299309
}

app/Repositories/Eloquent/NodeRepository.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,33 @@ public function getUsageStats(Node $node): array
5656
})->toArray();
5757
}
5858

59+
/**
60+
* Return the usage stats for a single node.
61+
*
62+
* @param \Pterodactyl\Models\Node $node
63+
* @return array
64+
*/
65+
public function getUsageStatsRaw(Node $node): array
66+
{
67+
$stats = $this->getBuilder()->select(
68+
$this->getBuilder()->raw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk')
69+
)->join('servers', 'servers.node_id', '=', 'nodes.id')->where('node_id', $node->id)->first();
70+
71+
return collect(['disk' => $stats->sum_disk, 'memory' => $stats->sum_memory])->mapWithKeys(function ($value, $key) use ($node) {
72+
$maxUsage = $node->{$key};
73+
if ($node->{$key . '_overallocate'} > 0) {
74+
$maxUsage = $node->{$key} * (1 + ($node->{$key . '_overallocate'} / 100));
75+
}
76+
77+
return [
78+
$key => [
79+
'value' => $value,
80+
'max' => $maxUsage,
81+
],
82+
];
83+
})->toArray();
84+
}
85+
5986
/**
6087
* Return all available nodes with a searchable interface.
6188
*

app/Repositories/Eloquent/ServerRepository.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,4 +328,14 @@ private function getUserAccessServers(int $user): array
328328
$this->app->make(SubuserRepository::class)->getBuilder()->select('server_id')->where('user_id', $user)
329329
)->pluck('id')->all();
330330
}
331+
332+
/**
333+
* Get the amount of servers that are suspended
334+
*
335+
* @return int
336+
*/
337+
public function getSuspendedServersCount(): int
338+
{
339+
return $this->getBuilder()->where('suspended', true)->count();
340+
}
331341
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
/**
3+
* Created by PhpStorm.
4+
* User: Stan
5+
* Date: 26-5-2018
6+
* Time: 20:56
7+
*/
8+
9+
namespace Pterodactyl\Traits\Controllers;
10+
11+
use JavaScript;
12+
13+
trait PlainJavascriptInjection
14+
{
15+
16+
/**
17+
* Injects statistics into javascript
18+
*/
19+
public function injectJavascript($data)
20+
{
21+
Javascript::put($data);
22+
}
23+
24+
}

public/themes/pterodactyl/css/pterodactyl.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,3 +473,7 @@ label.control-label > span.field-optional:before {
473473
height: 42px;
474474
width: auto;
475475
}
476+
477+
.number-info-box-content {
478+
padding: 15px 10px 0;
479+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
var freeDisk = Pterodactyl.totalNodeDisk - Pterodactyl.totalServerDisk;
2+
let diskChart = new Chart($('#disk_chart'), {
3+
type: 'pie',
4+
data: {
5+
labels: ['Free Disk', 'Used Disk'],
6+
datasets: [
7+
{
8+
label: 'Disk (in MB)',
9+
backgroundColor: ['#51B060', '#ff0000'],
10+
data: [freeDisk, Pterodactyl.totalServerDisk]
11+
}
12+
]
13+
}
14+
});
15+
16+
var freeRam = Pterodactyl.totalNodeRam - Pterodactyl.totalServerRam;
17+
let ramChart = new Chart($('#ram_chart'), {
18+
type: 'pie',
19+
data: {
20+
labels: ['Free RAM', 'Used RAM'],
21+
datasets: [
22+
{
23+
label: 'Memory (in MB)',
24+
backgroundColor: ['#51B060', '#ff0000'],
25+
data: [freeRam, Pterodactyl.totalServerRam]
26+
}
27+
]
28+
}
29+
});
30+
31+
var activeServers = Pterodactyl.servers.length - Pterodactyl.suspendedServers;
32+
let serversChart = new Chart($('#servers_chart'), {
33+
type: 'pie',
34+
data: {
35+
labels: ['Active', 'Suspended'],
36+
datasets: [
37+
{
38+
label: 'Servers',
39+
backgroundColor: ['#51B060', '#E08E0B'],
40+
data: [activeServers, Pterodactyl.suspendedServers]
41+
}
42+
]
43+
}
44+
});
45+
46+
let statusChart = new Chart($('#status_chart'), {
47+
type: 'pie',
48+
data: {
49+
labels: ['Online', 'Offline', 'Installing', 'Error'],
50+
datasets: [
51+
{
52+
label: '',
53+
backgroundColor: ['#51B060', '#b7b7b7', '#E08E0B', '#ff0000'],
54+
data: [0,0,0,0]
55+
}
56+
]
57+
}
58+
});
59+
60+
var servers = Pterodactyl.servers;
61+
var nodes = Pterodactyl.nodes;
62+
63+
for (let i = 0; i < servers.length; i++) {
64+
setTimeout(getStatus, 200 * i, servers[i]);
65+
}
66+
67+
function getStatus(server) {
68+
var uuid = server.uuid;
69+
var node = getNodeByID(server.node_id);
70+
71+
$.ajax({
72+
type: 'GET',
73+
url: node.scheme + '://' + node.fqdn + ':'+node.daemonListen+'/v1/server',
74+
timeout: 5000,
75+
headers: {
76+
'X-Access-Server': uuid,
77+
'X-Access-Token': Pterodactyl.tokens[node.id],
78+
}
79+
}).done(function (data) {
80+
81+
if (typeof data.status === 'undefined') {
82+
// Error
83+
statusChart.data.datasets[0].data[3]++;
84+
return;
85+
}
86+
87+
switch (data.status) {
88+
case 0:
89+
case 3:
90+
case 30:
91+
// Offline
92+
statusChart.data.datasets[0].data[1]++;
93+
break;
94+
case 1:
95+
case 2:
96+
// Online
97+
statusChart.data.datasets[0].data[0]++;
98+
break;
99+
case 20:
100+
// Installing
101+
statusChart.data.datasets[0].data[2]++;
102+
break;
103+
}
104+
statusChart.update();
105+
}).fail(function (jqXHR) {
106+
// Error
107+
statusChart.data.datasets[0].data[3]++;
108+
statusChart.update();
109+
});
110+
}
111+
112+
function getNodeByID(id) {
113+
for (var i = 0; i < nodes.length; i++) {
114+
if (nodes[i].id === id) {
115+
return nodes[i];
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)