feat: enhance Proxmox Firewall functionality by increasing log limit, adding DHCP management during firewall state changes, and implementing IP geolocation features for improved user experience
This commit is contained in:
@@ -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([
|
||||
|
||||
@@ -460,10 +460,238 @@
|
||||
<td class="py-3 px-4 text-sm">
|
||||
<span class="px-2 py-1 rounded text-xs font-semibold" :class="log.action_badge_class" x-text="log.action || '-' "></span>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-sm" x-text="log.protocol || '-' "></td>
|
||||
<td class="py-3 px-4 text-sm font-mono" x-text="log.source || '-' "></td>
|
||||
<td class="py-3 px-4 text-sm">
|
||||
<div class="flex items-center gap-1">
|
||||
<span x-text="log.protocol || '-'"></span>
|
||||
<template x-if="log.protocol && log.protocol !== '-'">
|
||||
<div class="relative" x-data="{ open: false, protocolDescription: window.getProtocolDescription(log.protocol) }" x-cloak>
|
||||
<button type="button" @click="open = true" class="text-base/50 hover:text-base/70 transition-colors">
|
||||
<x-ri-information-line class="size-3" />
|
||||
</button>
|
||||
<!-- Modal -->
|
||||
<div x-show="open"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
@click="open = false"
|
||||
@keydown.escape.window="open = false"
|
||||
class="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4"
|
||||
style="display: none;">
|
||||
<div @click.stop class="relative w-full max-w-4xl bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl max-h-[90vh] overflow-y-auto overflow-x-hidden">
|
||||
<button @click="open = false" class="absolute top-4 right-4 text-base/50 hover:text-base transition-colors z-10">
|
||||
<x-ri-close-line class="size-5" />
|
||||
</button>
|
||||
<div class="p-6 break-words">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<x-ri-information-line class="size-5 text-primary flex-shrink-0" />
|
||||
<h3 class="text-lg font-semibold break-words">Protocol: <span class="font-mono break-all" x-text="log.protocol"></span></h3>
|
||||
</div>
|
||||
<div class="text-sm text-base/80 prose prose-sm max-w-none" x-html="protocolDescription"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-sm">
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="font-mono" x-text="log.source || '-'"></span>
|
||||
<template x-if="log.source && log.source !== '-' && window.isPublicIP(log.source)">
|
||||
<div class="relative" x-data="{ open: false, loading: false, ipData: null, ip: log.source }" x-cloak>
|
||||
<button type="button" @click="
|
||||
if (!ipData && !loading) {
|
||||
loading = true;
|
||||
open = true;
|
||||
window.fetchIPLocation(ip).then(data => {
|
||||
ipData = data;
|
||||
loading = false;
|
||||
}).catch(err => {
|
||||
ipData = { status: 'error', message: 'Failed to load location data' };
|
||||
loading = false;
|
||||
});
|
||||
} else {
|
||||
open = true;
|
||||
}
|
||||
" class="text-base/50 hover:text-base/70 transition-colors" title="View IP Location">
|
||||
<x-ri-map-pin-line class="size-3" />
|
||||
</button>
|
||||
<!-- Modal -->
|
||||
<div x-show="open"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
@click="open = false"
|
||||
@keydown.escape.window="open = false"
|
||||
class="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4"
|
||||
style="display: none;">
|
||||
<div @click.stop class="relative w-full max-w-4xl bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl max-h-[90vh] overflow-y-auto overflow-x-hidden">
|
||||
<button @click="open = false" class="absolute top-4 right-4 text-base/50 hover:text-base transition-colors z-10">
|
||||
<x-ri-close-line class="size-5" />
|
||||
</button>
|
||||
<div class="p-6 break-words">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<x-ri-map-pin-line class="size-5 text-primary flex-shrink-0" />
|
||||
<h3 class="text-lg font-semibold break-words">IP Address: <span class="font-mono break-all" x-text="log.source"></span></h3>
|
||||
</div>
|
||||
<div x-show="loading" class="text-center py-8">
|
||||
<svg fill="currentColor" stroke="currentColor" viewBox="0 0 128 128" class="size-6 mx-auto mb-2">
|
||||
<g>
|
||||
<path stroke-width="5" d="M31.6 8.98a4.02 4.02 0 0 0 1.7 5.3l6.12 3.22a52.56 52.56 0 1 0 63.13 10.73L96 33.97a43.88 43.88 0 1 1-53-8.5l-3.65 7.4a4.02 4.02 0 0 0 1.72 5.3 3.73 3.73 0 0 0 5.13-1.67l7.66-15.63a4.18 4.18 0 0 0-1.75-5.4L36.74 7.32a3.73 3.73 0 0 0-5.13 1.66z" fill="currentColor" stroke="currentColor"/>
|
||||
<animateTransform attributeName="transform" type="rotate" from="0 64 64" to="360 64 64" dur="1000ms" repeatCount="indefinite"></animateTransform>
|
||||
</g>
|
||||
</svg>
|
||||
<p class="text-sm text-base/60">Loading location data...</p>
|
||||
</div>
|
||||
<div x-show="ipData && !loading">
|
||||
<template x-if="ipData && (ipData.status === 'success')">
|
||||
<div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div class="space-y-2 break-words">
|
||||
<div class="break-words"><span class="text-xs text-base/60">Country:</span> <span class="font-semibold text-sm break-words" x-text="ipData.country"></span></div>
|
||||
<div class="break-words"><span class="text-xs text-base/60">Region:</span> <span class="font-semibold text-sm break-words" x-text="ipData.regionName + ' (' + ipData.region + ')'"></span></div>
|
||||
<div class="break-words"><span class="text-xs text-base/60">City:</span> <span class="font-semibold text-sm break-words" x-text="ipData.city"></span></div>
|
||||
<div class="break-words"><span class="text-xs text-base/60">ZIP Code:</span> <span class="font-semibold text-sm break-words" x-text="ipData.zip"></span></div>
|
||||
<div class="break-words"><span class="text-xs text-base/60">Timezone:</span> <span class="font-semibold text-sm break-words" x-text="ipData.timezone"></span></div>
|
||||
</div>
|
||||
<div class="space-y-2 break-words">
|
||||
<div class="break-words"><span class="text-xs text-base/60">ISP:</span> <span class="font-semibold text-sm break-words" x-text="ipData.isp"></span></div>
|
||||
<div class="break-words"><span class="text-xs text-base/60">Organization:</span> <span class="font-semibold text-sm break-words" x-text="ipData.org"></span></div>
|
||||
<div class="break-words"><span class="text-xs text-base/60">AS:</span> <span class="font-semibold text-sm break-all" x-text="ipData.as"></span></div>
|
||||
<div class="break-words"><span class="text-xs text-base/60">Coordinates:</span> <span class="font-semibold text-sm font-mono break-all" x-text="ipData.lat + ', ' + ipData.lon"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div x-show="ipData.lat && ipData.lon" class="mt-4 w-full">
|
||||
<p class="text-xs text-base/60 mb-2">Location Map:</p>
|
||||
<div class="w-full rounded-lg border border-neutral/20 overflow-hidden bg-background/50" style="position: relative; width: 100%; max-width: 100%; height: 256px; min-height: 256px;">
|
||||
<iframe
|
||||
x-bind:src="'https://www.openstreetmap.org/export/embed.html?bbox=' + encodeURIComponent((parseFloat(ipData.lon) - 0.01) + ',' + (parseFloat(ipData.lat) - 0.01) + ',' + (parseFloat(ipData.lon) + 0.01) + ',' + (parseFloat(ipData.lat) + 0.01)) + '&layer=mapnik&marker=' + encodeURIComponent(ipData.lat + ',' + ipData.lon)"
|
||||
style="width: 100%; height: 100%; border: 0;"
|
||||
frameborder="0"
|
||||
loading="lazy"
|
||||
allowfullscreen
|
||||
title="Location Map"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="ipData && (ipData.status === 'fail' || ipData.status === 'error')">
|
||||
<div class="text-center py-4">
|
||||
<p class="text-sm text-warning" x-text="ipData.message || 'Failed to load location data'"></p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-sm" x-text="log.source_port || '-' "></td>
|
||||
<td class="py-3 px-4 text-sm font-mono" x-text="log.destination || '-' "></td>
|
||||
<td class="py-3 px-4 text-sm">
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="font-mono" x-text="log.destination || '-'"></span>
|
||||
<template x-if="log.destination && log.destination !== '-' && window.isPublicIP(log.destination)">
|
||||
<div class="relative" x-data="{ open: false, loading: false, ipData: null, ip: log.destination }" x-cloak>
|
||||
<button type="button" @click="
|
||||
if (!ipData && !loading) {
|
||||
loading = true;
|
||||
open = true;
|
||||
window.fetchIPLocation(ip).then(data => {
|
||||
ipData = data;
|
||||
loading = false;
|
||||
}).catch(err => {
|
||||
ipData = { status: 'error', message: 'Failed to load location data' };
|
||||
loading = false;
|
||||
});
|
||||
} else {
|
||||
open = true;
|
||||
}
|
||||
" class="text-base/50 hover:text-base/70 transition-colors" title="View IP Location">
|
||||
<x-ri-map-pin-line class="size-3" />
|
||||
</button>
|
||||
<!-- Modal -->
|
||||
<div x-show="open"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
@click="open = false"
|
||||
@keydown.escape.window="open = false"
|
||||
class="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm flex items-center justify-center p-4"
|
||||
style="display: none;">
|
||||
<div @click.stop class="relative w-full max-w-4xl bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl max-h-[90vh] overflow-y-auto overflow-x-hidden">
|
||||
<button @click="open = false" class="absolute top-4 right-4 text-base/50 hover:text-base transition-colors z-10">
|
||||
<x-ri-close-line class="size-5" />
|
||||
</button>
|
||||
<div class="p-6 break-words">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<x-ri-map-pin-line class="size-5 text-primary flex-shrink-0" />
|
||||
<h3 class="text-lg font-semibold break-words">IP Address: <span class="font-mono break-all" x-text="log.destination"></span></h3>
|
||||
</div>
|
||||
<div x-show="loading" class="text-center py-8">
|
||||
<svg fill="currentColor" stroke="currentColor" viewBox="0 0 128 128" class="size-6 mx-auto mb-2">
|
||||
<g>
|
||||
<path stroke-width="5" d="M31.6 8.98a4.02 4.02 0 0 0 1.7 5.3l6.12 3.22a52.56 52.56 0 1 0 63.13 10.73L96 33.97a43.88 43.88 0 1 1-53-8.5l-3.65 7.4a4.02 4.02 0 0 0 1.72 5.3 3.73 3.73 0 0 0 5.13-1.67l7.66-15.63a4.18 4.18 0 0 0-1.75-5.4L36.74 7.32a3.73 3.73 0 0 0-5.13 1.66z" fill="currentColor" stroke="currentColor"/>
|
||||
<animateTransform attributeName="transform" type="rotate" from="0 64 64" to="360 64 64" dur="1000ms" repeatCount="indefinite"></animateTransform>
|
||||
</g>
|
||||
</svg>
|
||||
<p class="text-sm text-base/60">Loading location data...</p>
|
||||
</div>
|
||||
<div x-show="ipData && !loading">
|
||||
<template x-if="ipData && (ipData.status === 'success')">
|
||||
<div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div class="space-y-2 break-words">
|
||||
<div class="break-words"><span class="text-xs text-base/60">Country:</span> <span class="font-semibold text-sm break-words" x-text="ipData.country"></span></div>
|
||||
<div class="break-words"><span class="text-xs text-base/60">Region:</span> <span class="font-semibold text-sm break-words" x-text="ipData.regionName + ' (' + ipData.region + ')'"></span></div>
|
||||
<div class="break-words"><span class="text-xs text-base/60">City:</span> <span class="font-semibold text-sm break-words" x-text="ipData.city"></span></div>
|
||||
<div class="break-words"><span class="text-xs text-base/60">ZIP Code:</span> <span class="font-semibold text-sm break-words" x-text="ipData.zip"></span></div>
|
||||
<div class="break-words"><span class="text-xs text-base/60">Timezone:</span> <span class="font-semibold text-sm break-words" x-text="ipData.timezone"></span></div>
|
||||
</div>
|
||||
<div class="space-y-2 break-words">
|
||||
<div class="break-words"><span class="text-xs text-base/60">ISP:</span> <span class="font-semibold text-sm break-words" x-text="ipData.isp"></span></div>
|
||||
<div class="break-words"><span class="text-xs text-base/60">Organization:</span> <span class="font-semibold text-sm break-words" x-text="ipData.org"></span></div>
|
||||
<div class="break-words"><span class="text-xs text-base/60">AS:</span> <span class="font-semibold text-sm break-all" x-text="ipData.as"></span></div>
|
||||
<div class="break-words"><span class="text-xs text-base/60">Coordinates:</span> <span class="font-semibold text-sm font-mono break-all" x-text="ipData.lat + ', ' + ipData.lon"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div x-show="ipData.lat && ipData.lon" class="mt-4 w-full">
|
||||
<p class="text-xs text-base/60 mb-2">Location Map:</p>
|
||||
<div class="w-full rounded-lg border border-neutral/20 overflow-hidden bg-background/50" style="position: relative; width: 100%; max-width: 100%; height: 256px; min-height: 256px;">
|
||||
<iframe
|
||||
x-bind:src="'https://www.openstreetmap.org/export/embed.html?bbox=' + encodeURIComponent((parseFloat(ipData.lon) - 0.01) + ',' + (parseFloat(ipData.lat) - 0.01) + ',' + (parseFloat(ipData.lon) + 0.01) + ',' + (parseFloat(ipData.lat) + 0.01)) + '&layer=mapnik&marker=' + encodeURIComponent(ipData.lat + ',' + ipData.lon)"
|
||||
style="width: 100%; height: 100%; border: 0;"
|
||||
frameborder="0"
|
||||
loading="lazy"
|
||||
allowfullscreen
|
||||
title="Location Map"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="ipData && (ipData.status === 'fail' || ipData.status === 'error')">
|
||||
<div class="text-center py-4">
|
||||
<p class="text-sm text-warning" x-text="ipData.message || 'Failed to load location data'"></p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-3 px-4 text-sm" x-text="log.destination_port || '-' "></td>
|
||||
</tr>
|
||||
</template>
|
||||
@@ -734,5 +962,162 @@
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Helper function to extract IP from string (removes port)
|
||||
window.extractIP = function(input) {
|
||||
if (!input) return '';
|
||||
input = String(input).trim();
|
||||
|
||||
// Handle IPv6 with port: [2001:db8::1]:8080
|
||||
const ipv6WithPortMatch = input.match(/^\[([^\]]+)\](?::\d+)?$/);
|
||||
if (ipv6WithPortMatch) {
|
||||
return ipv6WithPortMatch[1];
|
||||
}
|
||||
|
||||
// Handle IPv4 with port: 192.168.1.1:8080
|
||||
const ipv4WithPortMatch = input.match(/^([^:]+):\d+$/);
|
||||
if (ipv4WithPortMatch && !ipv4WithPortMatch[1].includes(':')) {
|
||||
return ipv4WithPortMatch[1];
|
||||
}
|
||||
|
||||
return input;
|
||||
};
|
||||
|
||||
// Check if IP is public
|
||||
window.isPublicIP = function(ip) {
|
||||
if (!ip || typeof ip !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
ip = window.extractIP(ip);
|
||||
|
||||
// Check for localhost
|
||||
if (['localhost', '127.0.0.1', '::1'].includes(ip.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if it's IPv4
|
||||
if (/^(\d{1,3}\.){3}\d{1,3}$/.test(ip)) {
|
||||
const parts = ip.split('.').map(Number);
|
||||
|
||||
// Check for valid IPv4 range
|
||||
if (parts.some(part => part > 255)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for private ranges
|
||||
if (parts[0] === 10) return false;
|
||||
if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return false;
|
||||
if (parts[0] === 192 && parts[1] === 168) return false;
|
||||
if (parts[0] === 127) return false;
|
||||
if (parts[0] === 169 && parts[1] === 254) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if it's IPv6
|
||||
if (ip.includes(':')) {
|
||||
// Link-local (fe80::/10)
|
||||
if (/^fe[89ab][0-9a-fA-F]:/i.test(ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Unique Local Address - ULA (fc00::/7)
|
||||
if (/^f[cd][0-9a-fA-F]/i.test(ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// IPv4-mapped IPv6 (::ffff:x.x.x.x)
|
||||
if (ip.startsWith('::ffff:')) {
|
||||
const ipv4Part = ip.substring(7);
|
||||
return window.isPublicIP(ipv4Part);
|
||||
}
|
||||
|
||||
// If it's a valid-looking IPv6 and not in private ranges, consider it public
|
||||
const ipv6Regex = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/;
|
||||
return ipv6Regex.test(ip);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// Fetch IP geolocation data via server-side proxy
|
||||
window.fetchIPLocation = async function(ip) {
|
||||
if (!window.isPublicIP(ip)) {
|
||||
return { status: 'fail', message: 'Not a public IP address' };
|
||||
}
|
||||
|
||||
ip = window.extractIP(ip);
|
||||
|
||||
try {
|
||||
const data = await @this.call('fetchIPLocation', ip);
|
||||
return data || { status: 'fail', message: 'No data returned' };
|
||||
} catch (error) {
|
||||
console.error('Error fetching IP location:', error);
|
||||
return { status: 'fail', message: 'Failed to fetch location data: ' + (error.message || 'Unknown error') };
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to format descriptions with sections
|
||||
const formatStateDescription = (overview, meaning, scenarios, notes = null) => {
|
||||
let html = '<div class="space-y-4">';
|
||||
if (overview) {
|
||||
html += `<div><h4 class="font-semibold text-base mb-2">Overview</h4><p>${overview}</p></div>`;
|
||||
}
|
||||
if (meaning) {
|
||||
html += `<div><h4 class="font-semibold text-base mb-2">What This Means</h4><p>${meaning}</p></div>`;
|
||||
}
|
||||
if (scenarios) {
|
||||
html += `<div><h4 class="font-semibold text-base mb-2">Common Scenarios</h4>${scenarios}</div>`;
|
||||
}
|
||||
if (notes) {
|
||||
html += `<div><h4 class="font-semibold text-base mb-2">Additional Notes</h4><p>${notes}</p></div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
return html;
|
||||
};
|
||||
|
||||
// Protocol descriptions mapping
|
||||
window.getProtocolDescription = function(protocol) {
|
||||
if (!protocol || typeof protocol !== 'string') {
|
||||
return formatStateDescription('Unknown protocol', '', '');
|
||||
}
|
||||
|
||||
const protocolUpper = protocol.toUpperCase();
|
||||
const protocolDescriptions = {
|
||||
'TCP': formatStateDescription(
|
||||
'TCP (Transmission Control Protocol) is a connection-oriented protocol that provides reliable, ordered, and error-checked delivery of data between applications.',
|
||||
'TCP establishes a connection before data transfer and ensures data integrity through acknowledgments and retransmissions. It guarantees that data is delivered in order and without errors.',
|
||||
'<p>Common uses include:</p><ul class="list-disc list-inside space-y-1 ml-2 mt-2"><li>Web browsing (HTTP/HTTPS)</li><li>Email (SMTP, IMAP, POP3)</li><li>File transfers (FTP)</li><li>Remote access (SSH, RDP)</li><li>Database connections</li><li>API communications</li></ul>',
|
||||
'TCP is the most commonly used transport protocol on the Internet and provides reliable communication at the cost of slightly higher overhead compared to UDP.'
|
||||
),
|
||||
'UDP': formatStateDescription(
|
||||
'UDP (User Datagram Protocol) is a connectionless protocol that provides fast, low-overhead data transmission without guarantees of delivery, ordering, or duplicate protection.',
|
||||
'UDP does not establish connections before sending data and does not provide reliability features like acknowledgments or retransmissions. It prioritizes speed over reliability.',
|
||||
'<p>Common uses include:</p><ul class="list-disc list-inside space-y-1 ml-2 mt-2"><li>DNS queries</li><li>Video streaming</li><li>Online gaming</li><li>VoIP (Voice over IP)</li><li>Real-time applications</li><li>DHCP (Dynamic Host Configuration Protocol)</li></ul>',
|
||||
'UDP is ideal for applications where speed is more important than reliability, or where occasional packet loss is acceptable.'
|
||||
),
|
||||
'ICMP': formatStateDescription(
|
||||
'ICMP (Internet Control Message Protocol) is a network layer protocol used for diagnostic and control purposes.',
|
||||
'ICMP is used by network devices to send error messages and operational information. It is essential for network troubleshooting and does not carry application data.',
|
||||
'<p>Common uses include:</p><ul class="list-disc list-inside space-y-1 ml-2 mt-2"><li>Ping (echo request/reply) for connectivity testing</li><li>Traceroute for path discovery</li><li>Error reporting (destination unreachable, time exceeded)</li><li>Network diagnostics</li></ul>',
|
||||
'ICMP is a stateless protocol and does not use connection states like TCP or UDP. It operates at the network layer alongside IP.'
|
||||
),
|
||||
'ICMPV6': formatStateDescription(
|
||||
'ICMPv6 (Internet Control Message Protocol version 6) is the IPv6 equivalent of ICMP, used for error reporting and diagnostic functions in IPv6 networks.',
|
||||
'ICMPv6 includes additional features beyond what ICMP provides for IPv4, including integrated support for address configuration and network discovery.',
|
||||
'<p>Key features include:</p><ul class="list-disc list-inside space-y-1 ml-2 mt-2"><li>Neighbor Discovery Protocol (NDP) for IPv6</li><li>Path MTU Discovery for IPv6</li><li>Router discovery and configuration</li><li>Error reporting and diagnostics</li></ul>',
|
||||
'ICMPv6 is essential for IPv6 network operation and provides functions that in IPv4 are handled by multiple protocols (ARP, ICMP, etc.).'
|
||||
),
|
||||
};
|
||||
|
||||
return protocolDescriptions[protocolUpper] || formatStateDescription(
|
||||
`${protocol} is a network protocol used for communication between devices.`,
|
||||
'This protocol handles specific types of network traffic and follows particular rules for data transmission.',
|
||||
''
|
||||
);
|
||||
};
|
||||
</script>
|
||||
|
||||
</x-proxmox::layout>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user