feat: implement available IPs loading in Proxmox Firewall component to streamline IP selection for firewall rules and enhance user experience
This commit is contained in:
@@ -49,12 +49,26 @@ class Firewall extends Component
|
||||
{
|
||||
parent::mount($server);
|
||||
$this->server->loadMissing('ipAddresses');
|
||||
$this->loadAvailableIps();
|
||||
$this->loadFirewallOptions();
|
||||
$this->loadFirewallRules();
|
||||
$this->loadAliases();
|
||||
$this->loadIpsets();
|
||||
$this->loadNetworkAdapters();
|
||||
$this->loadFirewallLogs();
|
||||
// Don't load logs on mount - only load when user clicks refresh
|
||||
}
|
||||
|
||||
protected function loadAvailableIps(): void
|
||||
{
|
||||
$allIps = collect();
|
||||
if ($this->server->primaryIpv4) {
|
||||
$allIps->push(['ip' => $this->server->primaryIpv4->ip, 'type' => 'IPv4', 'label' => 'Primary']);
|
||||
}
|
||||
if ($this->server->primaryIpv6) {
|
||||
$allIps->push(['ip' => $this->server->primaryIpv6->ip, 'type' => 'IPv6', 'label' => 'Primary']);
|
||||
}
|
||||
foreach ($this->server->ipAddresses as $ipAddress) {
|
||||
$allIps->push(['ip' => $ipAddress->ip, 'type' => strtoupper($ipAddress->type), 'label' => 'Assigned']);
|
||||
}
|
||||
$this->availableIps = $allIps->unique('ip')->values()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,19 +211,8 @@ class Firewall extends Component
|
||||
$this->resetRuleForm();
|
||||
|
||||
// Set default destination to first IP address
|
||||
$allIps = collect();
|
||||
if ($this->server->primaryIpv4) {
|
||||
$allIps->push($this->server->primaryIpv4->ip);
|
||||
}
|
||||
if ($this->server->primaryIpv6) {
|
||||
$allIps->push($this->server->primaryIpv6->ip);
|
||||
}
|
||||
foreach ($this->server->ipAddresses as $ipAddress) {
|
||||
$allIps->push($ipAddress->ip);
|
||||
}
|
||||
$uniqueIps = $allIps->unique();
|
||||
if ($uniqueIps->isNotEmpty()) {
|
||||
$this->ruleDest = $uniqueIps->first();
|
||||
if (!empty($this->availableIps)) {
|
||||
$this->ruleDest = $this->availableIps[0]['ip'];
|
||||
}
|
||||
|
||||
// Set default interface to first network adapter
|
||||
@@ -655,7 +658,6 @@ class Firewall extends Component
|
||||
$segments = array_filter(array_map('trim', explode(',', $configString)));
|
||||
$model = 'virtio';
|
||||
$mac = null;
|
||||
$bridge = null;
|
||||
$firewallEnabled = false;
|
||||
|
||||
foreach ($segments as $segment) {
|
||||
@@ -669,18 +671,15 @@ class Firewall extends Component
|
||||
if (in_array($segmentKeyLower, ['virtio', 'e1000', 'e1000e', 'rtl8139', 'vmxnet3', 'pcnet'], true)) {
|
||||
$model = $segmentKeyLower;
|
||||
$mac = $segmentValue;
|
||||
} elseif ($segmentKeyLower === 'bridge') {
|
||||
$bridge = $segmentValue;
|
||||
} elseif ($segmentKeyLower === 'firewall') {
|
||||
$firewallEnabled = ($segmentValue == '1' || $segmentValue === '1');
|
||||
}
|
||||
}
|
||||
|
||||
$this->networkAdapters[$key] = [
|
||||
$this->networkAdapters[] = [
|
||||
'name' => $key,
|
||||
'model' => $model,
|
||||
'mac' => $mac,
|
||||
'bridge' => $bridge,
|
||||
];
|
||||
$this->adapterFirewallStates[$key] = $firewallEnabled;
|
||||
}
|
||||
@@ -766,33 +765,23 @@ class Firewall extends Component
|
||||
public function loadFirewallLogs(): void
|
||||
{
|
||||
try {
|
||||
// Try the firewall log endpoint
|
||||
$response = $this->firewallRequest(
|
||||
'/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/log',
|
||||
'get',
|
||||
['limit' => $this->logLimit]
|
||||
);
|
||||
|
||||
// Check if response is successful
|
||||
if (!$response->successful()) {
|
||||
\Log::warning('Firewall log API returned non-success status', [
|
||||
'status' => $response->status(),
|
||||
'body' => $response->body()
|
||||
]);
|
||||
$this->firewallLogs = [];
|
||||
return;
|
||||
}
|
||||
|
||||
$responseData = $response->json();
|
||||
|
||||
// Proxmox firewall log API can return data in different formats
|
||||
// Try multiple possible response structures
|
||||
$rawLogs = [];
|
||||
|
||||
if (isset($responseData['data']) && is_array($responseData['data'])) {
|
||||
$rawLogs = $responseData['data'];
|
||||
} elseif (is_array($responseData) && !isset($responseData['data'])) {
|
||||
// Response might be the array directly
|
||||
$rawLogs = $responseData;
|
||||
}
|
||||
|
||||
@@ -800,12 +789,10 @@ class Firewall extends Component
|
||||
if (empty($rawLogs)) {
|
||||
$body = $response->body();
|
||||
if (!empty($body) && is_string($body)) {
|
||||
// Try to parse as JSON first
|
||||
$jsonData = json_decode($body, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE && is_array($jsonData)) {
|
||||
$rawLogs = $jsonData['data'] ?? $jsonData;
|
||||
} else {
|
||||
// If not JSON, try to split by newlines (plain text format)
|
||||
$lines = explode("\n", trim($body));
|
||||
$rawLogs = array_filter($lines, function($line) {
|
||||
return !empty(trim($line));
|
||||
@@ -814,25 +801,16 @@ class Firewall extends Component
|
||||
}
|
||||
}
|
||||
|
||||
\Log::debug('Firewall logs raw response', [
|
||||
'raw_logs_count' => count($rawLogs),
|
||||
'sample_entry' => !empty($rawLogs) ? (is_array($rawLogs[0]) ? $rawLogs[0] : substr($rawLogs[0], 0, 100)) : null
|
||||
]);
|
||||
|
||||
$this->firewallLogs = [];
|
||||
|
||||
foreach ($rawLogs as $logEntry) {
|
||||
$logText = null;
|
||||
|
||||
// Try different possible field names for the log text
|
||||
if (is_string($logEntry)) {
|
||||
// If the entry is a string directly, use it
|
||||
$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) {
|
||||
if (is_string($value) && !empty(trim($value))) {
|
||||
@@ -847,24 +825,12 @@ class Firewall extends Component
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse the log entry to extract simplified information
|
||||
$parsed = $this->parseFirewallLog($logText);
|
||||
if ($parsed) {
|
||||
$this->firewallLogs[] = $parsed;
|
||||
}
|
||||
}
|
||||
|
||||
\Log::debug('Firewall logs parsed', [
|
||||
'parsed_count' => count($this->firewallLogs)
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
// Log the error for debugging
|
||||
\Log::error('Failed to load firewall logs: ' . $e->getMessage(), [
|
||||
'server_id' => $this->server->id,
|
||||
'vm_id' => $this->server->vm_id,
|
||||
'node' => $this->server->node->name ?? 'unknown',
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
$this->firewallLogs = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,20 +261,7 @@
|
||||
<div>
|
||||
<label class="block text-sm font-medium mb-2">Destination</label>
|
||||
<select wire:model="ruleDest" class="w-full px-3 py-2 border border-neutral rounded-lg bg-background">
|
||||
@php
|
||||
$allIps = collect();
|
||||
if ($server->primaryIpv4) {
|
||||
$allIps->push(['ip' => $server->primaryIpv4->ip, 'type' => 'IPv4', 'label' => 'Primary']);
|
||||
}
|
||||
if ($server->primaryIpv6) {
|
||||
$allIps->push(['ip' => $server->primaryIpv6->ip, 'type' => 'IPv6', 'label' => 'Primary']);
|
||||
}
|
||||
foreach ($server->ipAddresses as $ipAddress) {
|
||||
$allIps->push(['ip' => $ipAddress->ip, 'type' => strtoupper($ipAddress->type), 'label' => 'Assigned']);
|
||||
}
|
||||
$uniqueIps = $allIps->unique('ip');
|
||||
@endphp
|
||||
@foreach($uniqueIps as $ipData)
|
||||
@foreach($availableIps as $ipData)
|
||||
<option value="{{ $ipData['ip'] }}">{{ $ipData['ip'] }} ({{ $ipData['type'] }} - {{ $ipData['label'] }})</option>
|
||||
@endforeach
|
||||
</select>
|
||||
|
||||
Reference in New Issue
Block a user