From 5801f90c88083dd15693e9b144a31ae388a29510 Mon Sep 17 00:00:00 2001 From: "Joseph.Rawlings" Date: Thu, 4 Dec 2025 19:58:46 -0500 Subject: [PATCH] feat: enhance Proxmox firewall logs functionality by increasing log limit to 5000, adding auto-refresh feature, and improving log fetching mechanism for better data accuracy and user experience --- .../Servers/Proxmox/Livewire/Firewall.php | 164 +++++++++--------- .../Servers/Proxmox/views/firewall.blade.php | 81 +++++---- 2 files changed, 130 insertions(+), 115 deletions(-) diff --git a/extensions/Servers/Proxmox/Livewire/Firewall.php b/extensions/Servers/Proxmox/Livewire/Firewall.php index 2321c0669..2add15c69 100644 --- a/extensions/Servers/Proxmox/Livewire/Firewall.php +++ b/extensions/Servers/Proxmox/Livewire/Firewall.php @@ -155,12 +155,14 @@ class Firewall extends Component // Firewall Logs public array $firewallLogs = []; - public int $logLimit = 1000; + public int $logLimit = 5000; // Fetch more logs to ensure fresh data public string $logError = ''; // Pagination for logs public int $currentPage = 1; public int $perPage = 50; + public bool $autoRefreshLogs = false; + public ?string $lastLogRefresh = null; // Available IPs for rules public array $availableIps = []; @@ -228,10 +230,18 @@ class Firewall extends Component ? 'application/json, text/plain, */*' : 'application/json'; - $request = Http::withHeaders([ + $headers = [ 'Authorization' => $authHeader, 'Accept' => $acceptHeader, - ])->withoutVerifying(); + ]; + + // Prevent caching for log requests + if (str_contains($url, '/firewall/log')) { + $headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'; + $headers['Pragma'] = 'no-cache'; + } + + $request = Http::withHeaders($headers)->withoutVerifying(); $response = match($method) { 'post' => $request->post($fullUrl, $data), @@ -1136,94 +1146,76 @@ class Firewall extends Component $this->logError = ''; try { - // Fetch logs in batches to handle large log sets + // Fetch all available logs without pagination to get the freshest data + // Add timestamp to prevent caching + $response = $this->firewallRequest( + '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/log', + 'get', + [ + 'limit' => $this->logLimit, + 'start' => 0, + '_dc' => time() // Cache buster + ] + ); + + if (!$response->successful()) { + $this->logError = 'Failed to fetch logs (HTTP ' . $response->status() . ')'; + Log::warning('Firewall log API request failed', [ + 'status' => $response->status(), + 'server_id' => $this->server->id, + 'vm_id' => $this->server->vm_id, + ]); + $this->firewallLogs = []; + return; + } + + // Proxmox firewall log endpoint can return JSON or text/plain + $body = $response->body(); $allRawLogs = []; - $start = 0; - $batchSize = 500; - $maxLogs = $this->logLimit; - do { - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/log', - 'get', - [ - 'limit' => $batchSize, - 'start' => $start - ] - ); - - if (!$response->successful()) { - $this->logError = 'Failed to fetch logs (HTTP ' . $response->status() . ')'; - Log::warning('Firewall log API request failed', [ - 'status' => $response->status(), - 'server_id' => $this->server->id, - 'vm_id' => $this->server->vm_id, - ]); - $this->firewallLogs = []; - return; - } - - // Proxmox firewall log endpoint can return JSON or text/plain - $body = $response->body(); - $batchRawLogs = []; - - // Check content type - $contentType = $response->header('Content-Type'); - $isJson = str_contains($contentType ?? '', 'json'); - - if ($isJson) { - try { - $jsonData = $response->json(); - - if (isset($jsonData['data'])) { - if (is_array($jsonData['data'])) { - foreach ($jsonData['data'] as $item) { - if (is_array($item) && isset($item['t'])) { - $batchRawLogs[] = $item['t']; - } elseif (is_string($item)) { - $batchRawLogs[] = $item; - } - } - } elseif (is_string($jsonData['data'])) { - $lines = preg_split('/\r\n|\r|\n/', $jsonData['data']); - $batchRawLogs = array_filter($lines, fn($line) => !empty(trim($line))); - } - } elseif (is_array($jsonData)) { - foreach ($jsonData as $item) { + // Check content type + $contentType = $response->header('Content-Type'); + $isJson = str_contains($contentType ?? '', 'json'); + + if ($isJson) { + try { + $jsonData = $response->json(); + + if (isset($jsonData['data'])) { + if (is_array($jsonData['data'])) { + foreach ($jsonData['data'] as $item) { if (is_array($item) && isset($item['t'])) { - $batchRawLogs[] = $item['t']; + $allRawLogs[] = $item['t']; } elseif (is_string($item)) { - $batchRawLogs[] = $item; + $allRawLogs[] = $item; } } + } elseif (is_string($jsonData['data'])) { + $lines = preg_split('/\r\n|\r|\n/', $jsonData['data']); + $allRawLogs = array_filter($lines, fn($line) => !empty(trim($line))); + } + } elseif (is_array($jsonData)) { + foreach ($jsonData as $item) { + if (is_array($item) && isset($item['t'])) { + $allRawLogs[] = $item['t']; + } elseif (is_string($item)) { + $allRawLogs[] = $item; + } } - } catch (\Exception $e) { - Log::warning('Failed to parse JSON response', ['error' => $e->getMessage()]); } + } catch (\Exception $e) { + Log::warning('Failed to parse JSON response', ['error' => $e->getMessage()]); } - - // Fall back to text/plain format - if (empty($batchRawLogs) && !empty($body)) { - $lines = preg_split('/\r\n|\r|\n/', $body); - $batchRawLogs = array_filter($lines, fn($line) => !empty(trim($line))); - } - - $batchRawLogs = array_values($batchRawLogs); - $allRawLogs = array_merge($allRawLogs, $batchRawLogs); - $fetchedCount = count($batchRawLogs); - $start += $batchSize; - - // Break if we got fewer logs than requested or reached limit - if ($fetchedCount < $batchSize || count($allRawLogs) >= $maxLogs) { - break; - } - } while (true); - - // Limit to maxLogs - if (count($allRawLogs) > $maxLogs) { - $allRawLogs = array_slice($allRawLogs, 0, $maxLogs); } + // Fall back to text/plain format + if (empty($allRawLogs) && !empty($body)) { + $lines = preg_split('/\r\n|\r|\n/', $body); + $allRawLogs = array_filter($lines, fn($line) => !empty(trim($line))); + } + + $allRawLogs = array_values($allRawLogs); + Log::info('Firewall logs fetched from Proxmox API', [ 'raw_count' => count($allRawLogs), 'first_3_logs' => array_slice($allRawLogs, 0, 3) @@ -1231,9 +1223,21 @@ class Firewall extends Component $this->firewallLogs = $this->parseFirewallLogs($allRawLogs); + // Reset to page 1 when refreshing logs + $this->currentPage = 1; + + // Set last refresh timestamp + $this->lastLogRefresh = now()->format('Y-m-d H:i:s'); + + // Notify user of successful refresh + $this->notify('Loaded ' . count($this->firewallLogs) . ' firewall log entries', 'success'); + Log::info('Firewall log parsing summary', [ 'total_raw_logs' => count($allRawLogs), 'successfully_parsed' => count($this->firewallLogs), + 'last_refresh' => $this->lastLogRefresh, + 'first_log_timestamp' => $this->firewallLogs[0]['timestamp'] ?? 'none', + 'last_log_timestamp' => $this->firewallLogs[count($this->firewallLogs) - 1]['timestamp'] ?? 'none', ]); } catch (\Exception $e) { diff --git a/extensions/Servers/Proxmox/views/firewall.blade.php b/extensions/Servers/Proxmox/views/firewall.blade.php index fea4839ad..1520e5eee 100644 --- a/extensions/Servers/Proxmox/views/firewall.blade.php +++ b/extensions/Servers/Proxmox/views/firewall.blade.php @@ -726,43 +726,54 @@ {{-- LOGS TAB --}} -
+
-

Firewall Logs

- +
+
+ +
@if(!empty($logError))