From 91cdaf2a6bc5914b98d0cfd46fd8fe3da4c6749e Mon Sep 17 00:00:00 2001 From: "Joseph.Rawlings" Date: Sat, 14 Mar 2026 11:44:46 -0400 Subject: [PATCH] Refactor Proxmox components: optimize data fetching in Server and Overview; enhance macro handling in Firewall; improve task data refresh logic in Statistics and views --- .../Servers/Proxmox/Livewire/Firewall.php | 18 ++- .../Servers/Proxmox/Livewire/Overview.php | 113 +++++++++++------- .../Servers/Proxmox/Livewire/Server.php | 36 ++---- .../Servers/Proxmox/Livewire/Statistics.php | 11 ++ .../Proxmox/views/components/layout.blade.php | 4 +- .../Servers/Proxmox/views/firewall.blade.php | 2 +- .../Proxmox/views/statistics.blade.php | 4 +- 7 files changed, 116 insertions(+), 72 deletions(-) diff --git a/extensions/Servers/Proxmox/Livewire/Firewall.php b/extensions/Servers/Proxmox/Livewire/Firewall.php index 04ce37899..133452400 100644 --- a/extensions/Servers/Proxmox/Livewire/Firewall.php +++ b/extensions/Servers/Proxmox/Livewire/Firewall.php @@ -69,8 +69,8 @@ class Firewall extends Component public bool $showApplyToAllModal = false; public array $applyToAllResults = []; - // Available Proxmox Macros - public array $availableMacros = [ + // Available Proxmox Macros (constant to avoid serialization in every Livewire request) + protected const AVAILABLE_MACROS = [ '' => 'None', 'Amanda' => 'Amanda Backup', 'Auth' => 'Auth (identd)', @@ -148,7 +148,7 @@ class Firewall extends Component ]; // Macro Definitions with ports and protocols (as per Proxmox) - protected array $macroDefinitions = [ + protected const MACRO_DEFINITIONS = [ 'Amanda' => ['proto' => 'tcp', 'dport' => '10080,10081,10082,10083'], 'Auth' => ['proto' => 'tcp', 'dport' => '113'], 'BGP' => ['proto' => 'tcp', 'dport' => '179'], @@ -598,12 +598,20 @@ class Firewall extends Component // ==================== RULES ==================== + /** + * Get available macros for frontend use (avoids serializing large static data on every request) + */ + public function getAvailableMacrosProperty(): array + { + return self::AVAILABLE_MACROS; + } + /** * Get macro definitions for frontend use */ public function getMacroDefinitionsProperty(): array { - return $this->macroDefinitions; + return self::MACRO_DEFINITIONS; } /** @@ -611,7 +619,7 @@ class Firewall extends Component */ public function getMacroDetails(string $macro): ?array { - return $this->macroDefinitions[$macro] ?? null; + return self::MACRO_DEFINITIONS[$macro] ?? null; } protected function loadFirewallRules(): void diff --git a/extensions/Servers/Proxmox/Livewire/Overview.php b/extensions/Servers/Proxmox/Livewire/Overview.php index 9aee24726..9c63f0c99 100644 --- a/extensions/Servers/Proxmox/Livewire/Overview.php +++ b/extensions/Servers/Proxmox/Livewire/Overview.php @@ -71,10 +71,60 @@ class Overview extends Component #[On('proxmox:stats')] public function stats() { - $this->fetchGraphData('cpu', $this->cpuTimeRange, $this->cpuAggregation); - $this->fetchGraphData('memory', $this->memoryTimeRange, $this->memoryAggregation); - $this->fetchGraphData('network', $this->networkTimeRange, $this->networkAggregation); - $this->fetchGraphData('disk', $this->diskTimeRange, $this->diskAggregation); + // Group graph types by their (timeRange, aggregation) to batch API calls. + // The Proxmox /rrddata endpoint returns ALL metrics in a single response, + // so we can serve multiple graph types from one API call when they share parameters. + $groups = []; + $graphConfigs = [ + 'cpu' => [$this->cpuTimeRange, $this->cpuAggregation], + 'memory' => [$this->memoryTimeRange, $this->memoryAggregation], + 'network' => [$this->networkTimeRange, $this->networkAggregation], + 'disk' => [$this->diskTimeRange, $this->diskAggregation], + ]; + + foreach ($graphConfigs as $graphType => [$timeRange, $aggregation]) { + $key = $timeRange . '|' . $aggregation; + $groups[$key]['timeRange'] = $timeRange; + $groups[$key]['aggregation'] = $aggregation; + $groups[$key]['types'][] = $graphType; + } + + foreach ($groups as $group) { + $this->fetchGraphDataBatch($group['types'], $group['timeRange'], $group['aggregation']); + } + } + + /** + * Fetch rrddata once and dispatch to multiple graph types that share the same parameters. + */ + protected function fetchGraphDataBatch(array $graphTypes, string $timeRange, string $aggregation): void + { + $allowedRanges = ['hour', 'day', 'week', 'month', 'year']; + $allowedAggs = ['average', 'maximum']; + if (!in_array($timeRange, $allowedRanges, true) || !in_array($aggregation, $allowedAggs, true)) { + return; + } + + // Use a shared cache key for the raw API data (not per-graph-type) + $cacheKey = 'proxmox-stats-' . $this->server->id . '-rrd-' . $timeRange . '-' . $aggregation; + $data = Cache::remember($cacheKey, 60, function () use ($timeRange, $aggregation) { + $proxmox = new Proxmox; + $vmType = $this->server->node->vmType(); + $stats = $proxmox->request('/nodes/' . $this->server->node->name . '/' . $vmType . '/' . $this->server->vm_id . '/rrddata', data: [ + 'timeframe' => $timeRange, + 'cf' => $aggregation === 'maximum' ? 'MAX' : 'AVERAGE', + ]); + + return $stats->json()['data'] ?? []; + }); + + foreach ($graphTypes as $graphType) { + // Also store per-graph-type cache keys for individual update/invalidation + $perGraphKey = 'proxmox-stats-' . $this->server->id . '-' . $graphType . '-' . $timeRange . '-' . $aggregation; + Cache::put($perGraphKey, $data, 60); + + $this->dispatch('proxmox:stats:' . $this->server->id . ':' . $graphType, $data); + } } protected function fetchGraphData(string $graphType, string $timeRange, string $aggregation) @@ -214,56 +264,39 @@ class Overview extends Component public function getPowerEventStatsProperty(): array { + // Compute uptime percentage from DB statistics $stats = ServerStatistics::where('server_id', $this->server->id) ->orderBy('DateTime') ->limit(500) - ->get(['vmstatus', 'uptime']); + ->get(['vmstatus']); - $counts = [ - 'starts' => 0, - 'shutdowns' => 0, - 'reboots' => 0, - 'samples' => $stats->count(), - 'running_samples' => 0, - 'uptime_percent' => 0, - ]; - - $previousStatus = null; - $previousUptime = null; + $samples = $stats->count(); + $runningSamples = 0; foreach ($stats as $stat) { - $status = strtolower($stat->vmstatus ?? ''); - - if ($status === 'running') { - $counts['running_samples']++; - - if ($previousStatus !== 'running') { - $counts['starts']++; - } elseif (!is_null($stat->uptime) && !is_null($previousUptime) && $stat->uptime < $previousUptime) { - $counts['reboots']++; - } - } elseif (in_array($status, ['stopped', 'shutdown', 'offline', 'stopped - shutdown', 'suspended'])) { - if ($previousStatus === 'running') { - $counts['shutdowns']++; - } + if (strtolower($stat->vmstatus ?? '') === 'running') { + $runningSamples++; } - - $previousStatus = $status; - $previousUptime = $stat->uptime; } - if ($counts['samples'] > 0) { - $counts['uptime_percent'] = round(($counts['running_samples'] / $counts['samples']) * 100, 1); + $uptimePercent = 0; + if ($samples > 0) { + $uptimePercent = round(($runningSamples / $samples) * 100, 1); } elseif ($this->status) { - $counts['uptime_percent'] = strtolower($this->status->status ?? '') === 'running' ? 100 : 0; + $uptimePercent = strtolower($this->status->status ?? '') === 'running' ? 100 : 0; } + // Get start/shutdown/reboot counts from Proxmox API (authoritative source) $taskCounts = $this->getTaskEventCounts(); - $counts['starts'] = $taskCounts['starts']; - $counts['shutdowns'] = $taskCounts['shutdowns']; - $counts['reboots'] = $taskCounts['reboots']; - return $counts; + return [ + 'starts' => $taskCounts['starts'], + 'shutdowns' => $taskCounts['shutdowns'], + 'reboots' => $taskCounts['reboots'], + 'samples' => $samples, + 'running_samples' => $runningSamples, + 'uptime_percent' => $uptimePercent, + ]; } public function getNetworkRateStatsProperty(): array diff --git a/extensions/Servers/Proxmox/Livewire/Server.php b/extensions/Servers/Proxmox/Livewire/Server.php index b80ff244a..85652d2a1 100644 --- a/extensions/Servers/Proxmox/Livewire/Server.php +++ b/extensions/Servers/Proxmox/Livewire/Server.php @@ -58,36 +58,26 @@ class Server extends Component return Cache::remember('bandwidth_threshold_95_' . Carbon::now()->format('Y-m'), 900, function () { $startOfMonth = Carbon::now()->startOfMonth(); - // Get all servers - $allServers = ServerModel::all(); + // 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 ($allServers->isEmpty()) { + if ($trafficData->isEmpty()) { return 10; // Minimum threshold of 10 GiB } $trafficValues = []; - foreach ($allServers as $server) { - // Get the earliest and latest statistics for this month for each server - $firstStat = ServerStatistics::where('server_id', $server->id) - ->where('DateTime', '>=', $startOfMonth) - ->orderBy('DateTime', 'asc') - ->first(); - - $latestStat = ServerStatistics::where('server_id', $server->id) - ->where('DateTime', '>=', $startOfMonth) - ->orderBy('DateTime', 'desc') - ->first(); + 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; - if ($firstStat && $latestStat) { - // 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 and collect - $trafficValues[] = $totalBytes / (1024 * 1024 * 1024); - } + // Convert to GiB and collect + $trafficValues[] = $totalBytes / (1024 * 1024 * 1024); } if (empty($trafficValues)) { diff --git a/extensions/Servers/Proxmox/Livewire/Statistics.php b/extensions/Servers/Proxmox/Livewire/Statistics.php index 53874f95f..f79687de8 100644 --- a/extensions/Servers/Proxmox/Livewire/Statistics.php +++ b/extensions/Servers/Proxmox/Livewire/Statistics.php @@ -790,6 +790,17 @@ class Statistics extends Component { $this->goToLifecycleEventsPage($this->lifecycleEventsCurrentPage + 1); } + + /** + * Targeted refresh for task history and lifecycle events sections. + * Replaces wire:poll="$refresh" which would re-evaluate ALL computed + * properties (graphs, network analysis, etc.) causing unnecessary load. + */ + public function refreshTaskData(): void + { + // Invalidate the task history cache so it re-fetches from API + Cache::forget('proxmox_task_history_' . $this->server->id); + } /** * Extract IP address from field (handles "IP[port]" and "IP:port" formats) diff --git a/extensions/Servers/Proxmox/views/components/layout.blade.php b/extensions/Servers/Proxmox/views/components/layout.blade.php index dfcd021ae..99d6e464e 100644 --- a/extensions/Servers/Proxmox/views/components/layout.blade.php +++ b/extensions/Servers/Proxmox/views/components/layout.blade.php @@ -76,7 +76,8 @@ -
+
+