diff --git a/extensions/Servers/Proxmox/Livewire/Firewall.php b/extensions/Servers/Proxmox/Livewire/Firewall.php index 05dae636c..2e73162c5 100644 --- a/extensions/Servers/Proxmox/Livewire/Firewall.php +++ b/extensions/Servers/Proxmox/Livewire/Firewall.php @@ -21,7 +21,7 @@ class Firewall extends Component public array $networkAdapters = []; public array $adapterFirewallStates = []; public array $firewallLogs = []; - public int $logLimit = 100; + public int $logLimit = 5000; public array $availableIps = []; public bool $showDeleteModal = false; public ?int $deletingRulePos = null; @@ -737,6 +737,7 @@ class Firewall extends Component $segments = array_filter(array_map('trim', explode(',', $currentConfig))); $newSegments = []; $firewallFound = false; + $dhcpFound = false; $currentFirewallState = $this->adapterFirewallStates[$adapterName] ?? false; $newFirewallState = !$currentFirewallState; @@ -754,6 +755,15 @@ class Firewall extends Component $newSegments[] = 'firewall=1'; } // If disabling, we skip adding firewall=1, effectively removing it + } elseif (strtolower($segmentKey) === 'dhcp') { + $dhcpFound = true; + // If enabling firewall, disable DHCP + if ($newFirewallState) { + $newSegments[] = 'dhcp=0'; + } else { + // Keep existing DHCP setting when disabling firewall + $newSegments[] = $segment; + } } else { $newSegments[] = $segment; } @@ -764,6 +774,11 @@ class Firewall extends Component $newSegments[] = 'firewall=1'; } + // If DHCP wasn't in the config and we're enabling firewall, explicitly disable DHCP + if (!$dhcpFound && $newFirewallState) { + $newSegments[] = 'dhcp=0'; + } + $newConfig = implode(',', $newSegments); $proxmox->request( @@ -802,7 +817,10 @@ class Firewall extends Component $response = $this->firewallRequest( '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/log', 'get', - ['limit' => $this->logLimit] + [ + 'limit' => $this->logLimit, + 'start' => 0 + ] ); if (! $response->successful()) { @@ -1001,6 +1019,101 @@ class Firewall extends Component return $result; } + /** + * Check if an IP address is public (callable from frontend) + */ + public function checkIsPublicIP(string $ip): bool + { + return $this->isPublicIP($ip); + } + + /** + * Check if an IP address is public (not private/localhost) + */ + protected function isPublicIP(string $ip): bool + { + $ip = $this->extractIPFromString($ip); + + if (empty($ip)) { + return false; + } + + // Check for localhost + if (in_array(strtolower($ip), ['localhost', '127.0.0.1', '::1'])) { + return false; + } + + // Validate IP format + if (!filter_var($ip, FILTER_VALIDATE_IP)) { + return false; + } + + // Check if it's a private IP + if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { + return true; + } + + return false; + } + + /** + * Extract IP address from a string (removes port if present) + */ + protected function extractIPFromString(string $input): string + { + $input = trim($input); + + // Handle IPv6 with port: [2001:db8::1]:8080 + if (preg_match('/^\[([^\]]+)\](?::\d+)?$/', $input, $matches)) { + return $matches[1]; + } + + // Handle IPv4 with port: 192.168.1.1:8080 + if (preg_match('/^([^:]+):\d+$/', $input, $matches)) { + // Make sure it's not IPv6 without brackets + if (!str_contains($matches[1], ':')) { + return $matches[1]; + } + } + + // Return as-is if no port detected + return $input; + } + + /** + * Fetch IP geolocation data + */ + public function fetchIPLocation($ip): array + { + if (empty($ip)) { + return ['status' => 'fail', 'message' => 'IP address is required']; + } + + // Extract and validate IP address + $cleanIP = $this->extractIPFromString((string)$ip); + + if (!$this->isPublicIP($cleanIP)) { + return ['status' => 'fail', 'message' => 'Not a valid public IP address']; + } + + try { + // Use HTTP protocol to fetch geolocation data + $response = \Illuminate\Support\Facades\Http::timeout(5)->get("http://ip-api.com/json/{$cleanIP}"); + + if ($response->successful()) { + return $response->json(); + } + + return ['status' => 'fail', 'message' => 'Failed to fetch location data']; + } catch (\Exception $e) { + \Log::error('Error fetching IP location', [ + 'ip' => $cleanIP, + 'error' => $e->getMessage(), + ]); + return ['status' => 'fail', 'message' => 'Error: ' . $e->getMessage()]; + } + } + public function render() { return view('proxmox::firewall')->layoutData([ diff --git a/extensions/Servers/Proxmox/views/firewall.blade.php b/extensions/Servers/Proxmox/views/firewall.blade.php index f0f142027..bda14b224 100644 --- a/extensions/Servers/Proxmox/views/firewall.blade.php +++ b/extensions/Servers/Proxmox/views/firewall.blade.php @@ -460,10 +460,238 @@