84 lines
2.5 KiB
PHP
84 lines
2.5 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Docker Stats Collector — queries Wings REST API for per-server network stats.
|
|
*
|
|
* @author Ball Studios <https://git.balls.studio>
|
|
* @license MIT
|
|
*/
|
|
|
|
namespace Pterodactyl\Addons\AdvancedAdmin\Services\Network;
|
|
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Pterodactyl\Models\Node;
|
|
use Pterodactyl\Models\Server;
|
|
|
|
class DockerStatsCollector
|
|
{
|
|
/**
|
|
* Collect network stats for all running servers on a node.
|
|
* Returns array of ['server_id' => int, 'rx_bytes' => int, 'tx_bytes' => int]
|
|
*/
|
|
public function collectForNode(Node $node): array
|
|
{
|
|
$results = [];
|
|
|
|
$servers = Server::query()
|
|
->where('node_id', $node->id)
|
|
->whereNotNull('uuid')
|
|
->get();
|
|
|
|
foreach ($servers as $server) {
|
|
try {
|
|
$stats = $this->fetchStats($node, $server->uuid);
|
|
if ($stats !== null) {
|
|
$results[] = [
|
|
'server_id' => $server->id,
|
|
'node_id' => $node->id,
|
|
'rx_bytes' => $stats['network']['rx_bytes'] ?? 0,
|
|
'tx_bytes' => $stats['network']['tx_bytes'] ?? 0,
|
|
];
|
|
}
|
|
} catch (\Throwable $e) {
|
|
Log::warning("[AdvancedAdmin] Failed to collect stats for server {$server->uuid}: {$e->getMessage()}");
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Fetch stats from Wings for a single server UUID.
|
|
* Uses the Wings Application API (node-level auth token).
|
|
*
|
|
* SSRF Prevention: URL is constructed only from trusted Node model data,
|
|
* never from user input.
|
|
*/
|
|
private function fetchStats(Node $node, string $serverUuid): ?array
|
|
{
|
|
// Validate UUID format before use
|
|
if (!preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $serverUuid)) {
|
|
return null;
|
|
}
|
|
|
|
$scheme = $node->scheme;
|
|
$fqdn = $node->fqdn;
|
|
$port = $node->daemonListen;
|
|
|
|
// Allowlist: only known node FQDNs are used (from DB, not from user input)
|
|
$url = "{$scheme}://{$fqdn}:{$port}/api/servers/{$serverUuid}/resources";
|
|
|
|
$response = Http::withToken($node->daemon_token_id)
|
|
->withHeaders(['Authorization' => "Bearer {$node->daemon_token_id}"])
|
|
->timeout(5)
|
|
->get($url);
|
|
|
|
if (!$response->successful()) {
|
|
return null;
|
|
}
|
|
|
|
return $response->json('resources');
|
|
}
|
|
}
|