feat: enhance firewall log error handling in Proxmox component to provide clearer feedback on log loading issues and improve user experience

This commit is contained in:
2025-12-01 16:56:14 -05:00
parent d5c9a6b2b7
commit 944f53554b
2 changed files with 82 additions and 19 deletions

View File

@@ -24,6 +24,7 @@ class Firewall extends Component
public array $availableIps = [];
public bool $showDeleteModal = false;
public ?int $deletingRulePos = null;
public string $logError = '';
// Rule creation/editing
public ?int $editingRulePos = null;
@@ -89,9 +90,14 @@ class Firewall extends Component
// Proxmox API token format: PVEAPIToken=USER@REALM!TOKENID=SECRET
$authHeader = 'PVEAPIToken=' . $user . '!' . $tokenId . '=' . $secret;
// For firewall log endpoint, accept text/plain as well
$acceptHeader = str_contains($url, '/firewall/log')
? 'application/json, text/plain, */*'
: 'application/json';
$request = Http::withHeaders([
'Authorization' => $authHeader,
'Accept' => 'application/json',
'Accept' => $acceptHeader,
])->withOptions([
'verify' => true,
]);
@@ -787,7 +793,9 @@ class Firewall extends Component
public function loadFirewallLogs(): void
{
$this->logError = '';
try {
// Try the firewall log endpoint - Proxmox returns logs as text/plain, not JSON
$response = $this->firewallRequest(
'/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/log',
'get',
@@ -795,35 +803,52 @@ class Firewall extends Component
);
if (!$response->successful()) {
$errorData = $response->json();
$errorMsg = $errorData['message'] ?? 'HTTP ' . $response->status();
$this->logError = 'Failed to fetch logs: ' . $errorMsg;
\Log::warning('Firewall log API request failed', [
'status' => $response->status(),
'body' => $response->body(),
'server_id' => $this->server->id,
'vm_id' => $this->server->vm_id
]);
$this->firewallLogs = [];
return;
}
$responseData = $response->json();
// Proxmox firewall log endpoint returns plain text, not JSON
$body = $response->body();
$rawLogs = [];
if (isset($responseData['data']) && is_array($responseData['data'])) {
$rawLogs = $responseData['data'];
} elseif (is_array($responseData) && !isset($responseData['data'])) {
$rawLogs = $responseData;
if (!empty($body)) {
// Split by newlines and filter empty lines
$lines = explode("\n", trim($body));
$rawLogs = array_filter($lines, function($line) {
return !empty(trim($line));
});
}
// If still empty, try to get logs from the response body as text
// If we got JSON instead, try to parse it
if (empty($rawLogs)) {
$body = $response->body();
if (!empty($body) && is_string($body)) {
$jsonData = json_decode($body, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($jsonData)) {
$rawLogs = $jsonData['data'] ?? $jsonData;
} else {
$lines = explode("\n", trim($body));
$rawLogs = array_filter($lines, function($line) {
return !empty(trim($line));
});
try {
$responseData = $response->json();
if (isset($responseData['data']) && is_array($responseData['data'])) {
$rawLogs = $responseData['data'];
} elseif (is_array($responseData) && !isset($responseData['data'])) {
$rawLogs = $responseData;
}
} catch (\Exception $jsonError) {
// Not JSON, that's fine - we already tried plain text
}
}
\Log::debug('Firewall logs fetched', [
'raw_count' => count($rawLogs),
'body_length' => strlen($body),
'content_type' => $response->header('Content-Type'),
'sample' => !empty($rawLogs) ? (is_array($rawLogs[0]) ? json_encode($rawLogs[0]) : substr($rawLogs[0], 0, 200)) : null
]);
$this->firewallLogs = [];
foreach ($rawLogs as $logEntry) {
@@ -832,10 +857,12 @@ class Firewall extends Component
if (is_string($logEntry)) {
$logText = trim($logEntry);
} elseif (is_array($logEntry)) {
// Try common field names
$logText = $logEntry['n'] ?? $logEntry['t'] ?? $logEntry['text'] ?? $logEntry['log'] ?? $logEntry['message'] ?? $logEntry['line'] ?? '';
// If still empty, try to get the first string value
if (empty($logText)) {
foreach ($logEntry as $value) {
foreach ($logEntry as $key => $value) {
if (is_string($value) && !empty(trim($value))) {
$logText = trim($value);
break;
@@ -853,7 +880,31 @@ class Firewall extends Component
$this->firewallLogs[] = $parsed;
}
}
if (empty($this->firewallLogs) && !empty($rawLogs)) {
$this->logError = 'Logs received but could not be parsed. Please check that firewall logging is enabled in Proxmox.';
} elseif (empty($this->firewallLogs)) {
$this->logError = 'No firewall logs available. Ensure firewall logging is enabled in Proxmox (Datacenter > Firewall > Options).';
}
\Log::debug('Firewall logs parsed', [
'parsed_count' => count($this->firewallLogs)
]);
} catch (\Exception $e) {
$errorMessage = $e->getMessage();
if (method_exists($e, 'response') && $e->response()) {
$errorData = $e->response()->json();
if (isset($errorData['message'])) {
$errorMessage = $errorData['message'];
}
}
$this->logError = 'Error loading logs: ' . $errorMessage;
\Log::error('Failed to load firewall logs', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
'server_id' => $this->server->id,
'vm_id' => $this->server->vm_id
]);
$this->firewallLogs = [];
}
}

View File

@@ -307,6 +307,17 @@
</button>
</div>
@if(!empty($logError))
<div class="bg-warning/10 border border-warning/30 rounded-lg p-4 mb-4">
<div class="flex items-start gap-3">
<x-ri-error-warning-line class="size-5 text-warning flex-shrink-0 mt-0.5" />
<div class="flex-1">
<p class="text-sm text-warning/90">{{ $logError }}</p>
</div>
</div>
</div>
@endif
@if(count($firewallLogs) > 0)
<div class="overflow-x-auto">
<table class="w-full border-collapse">
@@ -347,10 +358,11 @@
</tbody>
</table>
</div>
@else
@elseif(empty($logError))
<div class="text-center py-8 text-base/60">
<x-ri-file-list-3-line class="size-12 mx-auto mb-3 opacity-50" />
<p>No firewall logs available</p>
<p class="text-xs mt-2">Ensure firewall logging is enabled in Proxmox (Datacenter > Firewall > Options)</p>
</div>
@endif
</div>