loadAttempts = 1; // Check and enforce bandwidth throttling if exceeded (only if status loaded) if ($this->status !== null) { $this->checkAndEnforceBandwidthLimit(); } } public function getMonthlyTrafficProperty() { $startOfMonth = Carbon::now()->startOfMonth(); // Get the earliest and latest statistics for this month $firstStat = $this->server->statistics() ->where('DateTime', '>=', $startOfMonth) ->orderBy('DateTime', 'asc') ->first(); $latestStat = $this->server->statistics() ->where('DateTime', '>=', $startOfMonth) ->orderBy('DateTime', 'desc') ->first(); if (!$firstStat || !$latestStat) { return 0; } // Calculate total traffic (in + out) for the month $netinDiff = max(0, $latestStat->netin - $firstStat->netin); $netoutDiff = max(0, $latestStat->netout - $firstStat->netout); $totalBytes = $netinDiff + $netoutDiff; // Convert to GiB return round($totalBytes / (1024 * 1024 * 1024), 2); } public function getBandwidthThreshold95Property() { // Cache for 15 minutes to avoid recalculating for every server return Cache::remember('bandwidth_threshold_95_' . Carbon::now()->format('Y-m'), 900, function () { $startOfMonth = Carbon::now()->startOfMonth(); // Use a single query with subqueries to get min/max netin/netout per server, // avoiding N+1 queries (previously 2 queries per server) $trafficData = ServerStatistics::where('DateTime', '>=', $startOfMonth) ->selectRaw('server_id, MIN(netin) as min_netin, MAX(netin) as max_netin, MIN(netout) as min_netout, MAX(netout) as max_netout') ->groupBy('server_id') ->get(); if ($trafficData->isEmpty()) { return 10; // Minimum threshold of 10 GiB } $trafficValues = []; foreach ($trafficData as $row) { $netinDiff = max(0, $row->max_netin - $row->min_netin); $netoutDiff = max(0, $row->max_netout - $row->min_netout); $totalBytes = $netinDiff + $netoutDiff; // Convert to GiB and collect $trafficValues[] = $totalBytes / (1024 * 1024 * 1024); } if (empty($trafficValues)) { return 10; // Minimum threshold of 10 GiB } // Calculate actual 95th percentile by sorting and taking the value at the 95th rank sort($trafficValues); $index = (int) ceil(0.95 * count($trafficValues)) - 1; $threshold95Percent = $trafficValues[$index]; // Return the maximum of 95th percentile or 10 GiB minimum return round(max($threshold95Percent, 10), 2); }); } protected function checkAndEnforceBandwidthLimit() { $monthlyTraffic = $this->monthlyTraffic; $threshold = $this->bandwidthThreshold95; // Check if bandwidth is exceeded if ($monthlyTraffic > $threshold) { // Throttle the server to 1 MiB/s $this->throttleServerBandwidth(); } } protected function throttleServerBandwidth() { try { $proxmox = new \Paymenter\Extensions\Servers\Proxmox\Proxmox; // Get current VM config $config = $proxmox->request('/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config')->json()['data'] ?? []; $updates = []; // Update all network interfaces to rate limit of 1 MiB/s foreach ($config as $key => $value) { if (preg_match('/^net\d+$/', $key)) { // Parse existing network config $parts = explode(',', $value); $newParts = []; $hasRate = false; foreach ($parts as $part) { if (strpos($part, 'rate=') === 0) { // Replace existing rate $newParts[] = 'rate=1'; $hasRate = true; } else { $newParts[] = $part; } } // Add rate if not present if (!$hasRate) { $newParts[] = 'rate=1'; } $updates[$key] = implode(',', $newParts); } } // Apply the updates if any network interfaces were found if (!empty($updates)) { $proxmox->request('/nodes/' . $this->server->node->name . '/' . $this->server->node->vmType() . '/' . $this->server->vm_id . '/config', 'put', $updates); } } catch (\Throwable $e) { // Log the error but don't fail the page load \Log::error('Failed to throttle bandwidth for server ' . $this->server->id, [ 'error' => $e->getMessage(), 'server_id' => $this->server->id, ]); } } public function retryLoadStatus(): void { $this->loadAttempts++; $this->loadVmStatus(); // Check bandwidth throttling on successful load if ($this->status !== null) { $this->checkAndEnforceBandwidthLimit(); } } /** * Auto-retry loading status via polling (called by wire:poll when status is null) */ public function autoRetryLoadStatus(): void { if ($this->status === null && $this->loadAttempts < $this->maxRetries) { $this->loadAttempts++; $this->loadVmStatus(); // Check bandwidth throttling on successful load if ($this->status !== null) { $this->checkAndEnforceBandwidthLimit(); } } } public function render() { return view('proxmox::server'); } public function placeholder(array $params = []) { return view('proxmox::serverplaceholder', $params); } }