feat: enhance Proxmox Firewall log view with detailed IP and port information, including modals for geolocation and protocol descriptions, improving user interaction and data accessibility
This commit is contained in:
@@ -1152,6 +1152,49 @@ class Firewall extends Component
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch IP location data from ip-api.com
|
||||||
|
*/
|
||||||
|
public function fetchIPLocation(string $ip): array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Validate IP address
|
||||||
|
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
|
||||||
|
return [
|
||||||
|
'status' => 'fail',
|
||||||
|
'message' => 'Invalid or private IP address'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch location data from ip-api.com (HTTP endpoint)
|
||||||
|
$response = Http::timeout(5)->get("http://ip-api.com/json/{$ip}");
|
||||||
|
|
||||||
|
if ($response->successful()) {
|
||||||
|
$data = $response->json();
|
||||||
|
|
||||||
|
// Check if the API returned success
|
||||||
|
if (isset($data['status']) && $data['status'] === 'success') {
|
||||||
|
return $data;
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
'status' => 'fail',
|
||||||
|
'message' => $data['message'] ?? 'Unable to fetch location data'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'status' => 'fail',
|
||||||
|
'message' => 'Failed to fetch location data (HTTP ' . $response->status() . ')'
|
||||||
|
];
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return [
|
||||||
|
'status' => 'fail',
|
||||||
|
'message' => 'Error: ' . $e->getMessage()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('proxmox::firewall')->layoutData([
|
return view('proxmox::firewall')->layoutData([
|
||||||
|
|||||||
@@ -537,18 +537,350 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<template x-for="(log, index) in paginatedLogs" :key="log.index || index">
|
<template x-for="(log, index) in paginatedLogs" :key="log.index || index">
|
||||||
<tr class="border-b border-neutral/20 hover:bg-background-secondary/50">
|
<tr class="border-b border-neutral/20 hover:bg-background-secondary/50">
|
||||||
|
<!-- Timestamp -->
|
||||||
<td class="py-3 px-4 text-sm" x-text="log.timestamp_display || '-' "></td>
|
<td class="py-3 px-4 text-sm" x-text="log.timestamp_display || '-' "></td>
|
||||||
|
|
||||||
|
<!-- Level -->
|
||||||
<td class="py-3 px-4 text-sm">
|
<td class="py-3 px-4 text-sm">
|
||||||
<span class="px-2 py-1 rounded text-xs font-semibold uppercase" :class="log.level_badge_class" x-text="log.level_label || '-' "></span>
|
<span class="px-2 py-1 rounded text-xs font-semibold uppercase" :class="log.level_badge_class" x-text="log.level_label || '-' "></span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
<!-- Action -->
|
||||||
<td class="py-3 px-4 text-sm">
|
<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>
|
<span class="px-2 py-1 rounded text-xs font-semibold" :class="log.action_badge_class" x-text="log.action || '-' "></span>
|
||||||
</td>
|
</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>
|
<!-- Protocol with info modal -->
|
||||||
<td class="py-3 px-4 text-sm" x-text="log.source_port || '-' "></td>
|
<td class="py-3 px-4 text-sm">
|
||||||
<td class="py-3 px-4 text-sm font-mono" x-text="log.destination || '-' "></td>
|
<div class="flex items-center gap-1">
|
||||||
<td class="py-3 px-4 text-sm" x-text="log.destination_port || '-' "></td>
|
<span x-text="log.protocol || '-' "></span>
|
||||||
|
<template x-if="log.protocol && log.protocol !== '-'">
|
||||||
|
<div class="relative" x-data="{ open: false }" x-cloak>
|
||||||
|
<button type="button" @click="open = true" class="text-base/50 hover:text-primary transition-colors">
|
||||||
|
<x-ri-information-line class="size-3" />
|
||||||
|
</button>
|
||||||
|
<!-- Modal Backdrop -->
|
||||||
|
<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"
|
||||||
|
style="display: none;">
|
||||||
|
</div>
|
||||||
|
<!-- Modal -->
|
||||||
|
<div x-show="open"
|
||||||
|
x-transition:enter="transition ease-out duration-200"
|
||||||
|
x-transition:enter-start="opacity-0 scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 scale-100"
|
||||||
|
x-transition:leave="transition ease-in duration-150"
|
||||||
|
x-transition:leave-start="opacity-100 scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 scale-95"
|
||||||
|
@click.away="open = false"
|
||||||
|
@keydown.escape.window="open = false"
|
||||||
|
class="fixed inset-0 z-50 flex items-center justify-center p-4"
|
||||||
|
style="display: none;">
|
||||||
|
<div class="relative w-full max-w-2xl bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl p-6 max-h-[80vh] overflow-y-auto">
|
||||||
|
<button @click="open = false" class="absolute top-4 right-4 text-base/50 hover:text-base transition-colors">
|
||||||
|
<x-ri-close-line class="size-5" />
|
||||||
|
</button>
|
||||||
|
<div class="pr-8">
|
||||||
|
<div class="flex items-center gap-2 mb-4">
|
||||||
|
<x-ri-information-line class="size-6 text-primary" />
|
||||||
|
<h3 class="text-lg font-semibold" x-text="`${log.protocol} Protocol`"></h3>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-base/90" x-html="window.getProtocolDescription(log.protocol)"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Source IP with location lookup -->
|
||||||
|
<td class="py-3 px-4 text-sm">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<span class="font-mono text-xs" 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 }" x-cloak>
|
||||||
|
<button type="button" @click="
|
||||||
|
if (!ipData && !loading) {
|
||||||
|
loading = true;
|
||||||
|
open = true;
|
||||||
|
window.fetchIPLocation(log.source).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-primary transition-colors" title="View IP Location">
|
||||||
|
<x-ri-map-pin-line class="size-3" />
|
||||||
|
</button>
|
||||||
|
<!-- Modal Backdrop -->
|
||||||
|
<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"
|
||||||
|
style="display: none;">
|
||||||
|
</div>
|
||||||
|
<!-- Modal -->
|
||||||
|
<div x-show="open"
|
||||||
|
x-transition:enter="transition ease-out duration-200"
|
||||||
|
x-transition:enter-start="opacity-0 scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 scale-100"
|
||||||
|
x-transition:leave="transition ease-in duration-150"
|
||||||
|
x-transition:leave-start="opacity-100 scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 scale-95"
|
||||||
|
@click.away="open = false"
|
||||||
|
@keydown.escape.window="open = false"
|
||||||
|
class="fixed inset-0 z-50 flex items-center justify-center p-4"
|
||||||
|
style="display: none;">
|
||||||
|
<div class="relative w-full max-w-md bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl p-6">
|
||||||
|
<button @click="open = false" class="absolute top-4 right-4 text-base/50 hover:text-base transition-colors">
|
||||||
|
<x-ri-close-line class="size-5" />
|
||||||
|
</button>
|
||||||
|
<div class="pr-8">
|
||||||
|
<div class="flex items-center gap-2 mb-4">
|
||||||
|
<x-ri-map-pin-line class="size-6 text-primary" />
|
||||||
|
<h3 class="text-lg font-semibold">IP Location</h3>
|
||||||
|
</div>
|
||||||
|
<template x-if="loading">
|
||||||
|
<div class="flex items-center justify-center py-8">
|
||||||
|
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template x-if="!loading && ipData">
|
||||||
|
<div>
|
||||||
|
<template x-if="ipData.status === 'success'">
|
||||||
|
<div class="space-y-3 text-sm">
|
||||||
|
<div><span class="font-semibold">IP:</span> <span class="font-mono" x-text="log.source"></span></div>
|
||||||
|
<div><span class="font-semibold">Country:</span> <span x-text="ipData.country"></span></div>
|
||||||
|
<div><span class="font-semibold">Region:</span> <span x-text="ipData.regionName"></span></div>
|
||||||
|
<div><span class="font-semibold">City:</span> <span x-text="ipData.city"></span></div>
|
||||||
|
<div><span class="font-semibold">ISP:</span> <span x-text="ipData.isp"></span></div>
|
||||||
|
<div><span class="font-semibold">Organization:</span> <span x-text="ipData.org"></span></div>
|
||||||
|
<div><span class="font-semibold">Timezone:</span> <span x-text="ipData.timezone"></span></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template x-if="ipData.status !== 'success'">
|
||||||
|
<p class="text-sm text-warning" x-text="ipData.message || 'Failed to load location data'"></p>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Source Port with description -->
|
||||||
|
<td class="py-3 px-4 text-sm">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<span x-text="log.source_port || '-' "></span>
|
||||||
|
<template x-if="log.source_port && log.source_port !== '-'">
|
||||||
|
<div class="relative" x-data="{ open: false }" x-cloak>
|
||||||
|
<button type="button" @click="open = true" class="text-base/50 hover:text-primary transition-colors">
|
||||||
|
<x-ri-information-line class="size-3" />
|
||||||
|
</button>
|
||||||
|
<!-- Modal Backdrop -->
|
||||||
|
<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"
|
||||||
|
style="display: none;">
|
||||||
|
</div>
|
||||||
|
<!-- Modal -->
|
||||||
|
<div x-show="open"
|
||||||
|
x-transition:enter="transition ease-out duration-200"
|
||||||
|
x-transition:enter-start="opacity-0 scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 scale-100"
|
||||||
|
x-transition:leave="transition ease-in duration-150"
|
||||||
|
x-transition:leave-start="opacity-100 scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 scale-95"
|
||||||
|
@click.away="open = false"
|
||||||
|
@keydown.escape.window="open = false"
|
||||||
|
class="fixed inset-0 z-50 flex items-center justify-center p-4"
|
||||||
|
style="display: none;">
|
||||||
|
<div class="relative w-full max-w-md bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl p-6">
|
||||||
|
<button @click="open = false" class="absolute top-4 right-4 text-base/50 hover:text-base transition-colors">
|
||||||
|
<x-ri-close-line class="size-5" />
|
||||||
|
</button>
|
||||||
|
<div class="pr-8">
|
||||||
|
<div class="flex items-center gap-2 mb-4">
|
||||||
|
<x-ri-information-line class="size-6 text-primary" />
|
||||||
|
<h3 class="text-lg font-semibold">Port <span x-text="log.source_port"></span></h3>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-base/90" x-text="window.getPortDescription(parseInt(log.source_port) || 0)"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Destination IP with location lookup -->
|
||||||
|
<td class="py-3 px-4 text-sm">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<span class="font-mono text-xs" 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 }" x-cloak>
|
||||||
|
<button type="button" @click="
|
||||||
|
if (!ipData && !loading) {
|
||||||
|
loading = true;
|
||||||
|
open = true;
|
||||||
|
window.fetchIPLocation(log.destination).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-primary transition-colors" title="View IP Location">
|
||||||
|
<x-ri-map-pin-line class="size-3" />
|
||||||
|
</button>
|
||||||
|
<!-- Modal Backdrop -->
|
||||||
|
<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"
|
||||||
|
style="display: none;">
|
||||||
|
</div>
|
||||||
|
<!-- Modal -->
|
||||||
|
<div x-show="open"
|
||||||
|
x-transition:enter="transition ease-out duration-200"
|
||||||
|
x-transition:enter-start="opacity-0 scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 scale-100"
|
||||||
|
x-transition:leave="transition ease-in duration-150"
|
||||||
|
x-transition:leave-start="opacity-100 scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 scale-95"
|
||||||
|
@click.away="open = false"
|
||||||
|
@keydown.escape.window="open = false"
|
||||||
|
class="fixed inset-0 z-50 flex items-center justify-center p-4"
|
||||||
|
style="display: none;">
|
||||||
|
<div class="relative w-full max-w-md bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl p-6">
|
||||||
|
<button @click="open = false" class="absolute top-4 right-4 text-base/50 hover:text-base transition-colors">
|
||||||
|
<x-ri-close-line class="size-5" />
|
||||||
|
</button>
|
||||||
|
<div class="pr-8">
|
||||||
|
<div class="flex items-center gap-2 mb-4">
|
||||||
|
<x-ri-map-pin-line class="size-6 text-primary" />
|
||||||
|
<h3 class="text-lg font-semibold">IP Location</h3>
|
||||||
|
</div>
|
||||||
|
<template x-if="loading">
|
||||||
|
<div class="flex items-center justify-center py-8">
|
||||||
|
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template x-if="!loading && ipData">
|
||||||
|
<div>
|
||||||
|
<template x-if="ipData.status === 'success'">
|
||||||
|
<div class="space-y-3 text-sm">
|
||||||
|
<div><span class="font-semibold">IP:</span> <span class="font-mono" x-text="log.destination"></span></div>
|
||||||
|
<div><span class="font-semibold">Country:</span> <span x-text="ipData.country"></span></div>
|
||||||
|
<div><span class="font-semibold">Region:</span> <span x-text="ipData.regionName"></span></div>
|
||||||
|
<div><span class="font-semibold">City:</span> <span x-text="ipData.city"></span></div>
|
||||||
|
<div><span class="font-semibold">ISP:</span> <span x-text="ipData.isp"></span></div>
|
||||||
|
<div><span class="font-semibold">Organization:</span> <span x-text="ipData.org"></span></div>
|
||||||
|
<div><span class="font-semibold">Timezone:</span> <span x-text="ipData.timezone"></span></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template x-if="ipData.status !== 'success'">
|
||||||
|
<p class="text-sm text-warning" x-text="ipData.message || 'Failed to load location data'"></p>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Destination Port with description -->
|
||||||
|
<td class="py-3 px-4 text-sm">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<span x-text="log.destination_port || '-' "></span>
|
||||||
|
<template x-if="log.destination_port && log.destination_port !== '-'">
|
||||||
|
<div class="relative" x-data="{ open: false }" x-cloak>
|
||||||
|
<button type="button" @click="open = true" class="text-base/50 hover:text-primary transition-colors">
|
||||||
|
<x-ri-information-line class="size-3" />
|
||||||
|
</button>
|
||||||
|
<!-- Modal Backdrop -->
|
||||||
|
<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"
|
||||||
|
style="display: none;">
|
||||||
|
</div>
|
||||||
|
<!-- Modal -->
|
||||||
|
<div x-show="open"
|
||||||
|
x-transition:enter="transition ease-out duration-200"
|
||||||
|
x-transition:enter-start="opacity-0 scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 scale-100"
|
||||||
|
x-transition:leave="transition ease-in duration-150"
|
||||||
|
x-transition:leave-start="opacity-100 scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 scale-95"
|
||||||
|
@click.away="open = false"
|
||||||
|
@keydown.escape.window="open = false"
|
||||||
|
class="fixed inset-0 z-50 flex items-center justify-center p-4"
|
||||||
|
style="display: none;">
|
||||||
|
<div class="relative w-full max-w-md bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl p-6">
|
||||||
|
<button @click="open = false" class="absolute top-4 right-4 text-base/50 hover:text-base transition-colors">
|
||||||
|
<x-ri-close-line class="size-5" />
|
||||||
|
</button>
|
||||||
|
<div class="pr-8">
|
||||||
|
<div class="flex items-center gap-2 mb-4">
|
||||||
|
<x-ri-information-line class="size-6 text-primary" />
|
||||||
|
<h3 class="text-lg font-semibold">Port <span x-text="log.destination_port"></span></h3>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-base/90" x-text="window.getPortDescription(parseInt(log.destination_port) || 0)"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -854,4 +1186,198 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@script
|
||||||
|
<script>
|
||||||
|
// Check if an IP address is public (not private/localhost)
|
||||||
|
window.isPublicIP = function(ip) {
|
||||||
|
if (!ip || ip === '—' || ip === '-' || typeof ip !== 'string') return false;
|
||||||
|
|
||||||
|
ip = ip.trim();
|
||||||
|
|
||||||
|
// Check for localhost
|
||||||
|
if (ip === 'localhost' || ip === '127.0.0.1' || ip === '::1') return false;
|
||||||
|
|
||||||
|
// Check for IPv4 private ranges
|
||||||
|
const ipv4PrivateRanges = [
|
||||||
|
/^10\./, // 10.0.0.0/8
|
||||||
|
/^172\.(1[6-9]|2[0-9]|3[0-1])\./, // 172.16.0.0/12
|
||||||
|
/^192\.168\./, // 192.168.0.0/16
|
||||||
|
/^169\.254\./, // 169.254.0.0/16 (link-local)
|
||||||
|
/^127\./, // 127.0.0.0/8
|
||||||
|
/^0\./ // 0.0.0.0/8
|
||||||
|
];
|
||||||
|
|
||||||
|
// Check if it's IPv4
|
||||||
|
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
||||||
|
if (ipv4Regex.test(ip)) {
|
||||||
|
// Check against private ranges
|
||||||
|
if (ipv4PrivateRanges.some(range => range.test(ip))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's IPv6
|
||||||
|
if (ip.includes(':')) {
|
||||||
|
// Check for IPv6 private ranges
|
||||||
|
// 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
|
||||||
|
window.fetchIPLocation = async function(ip) {
|
||||||
|
if (!window.isPublicIP(ip)) {
|
||||||
|
return { status: 'fail', message: 'Not a public IP address' };
|
||||||
|
}
|
||||||
|
|
||||||
|
ip = ip.trim();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use Livewire to call server-side method
|
||||||
|
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 formatDescription = (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 formatDescription('Unknown protocol', '', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
const protocolUpper = protocol.toUpperCase();
|
||||||
|
const protocolDescriptions = {
|
||||||
|
'TCP': formatDescription(
|
||||||
|
'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': formatDescription(
|
||||||
|
'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': formatDescription(
|
||||||
|
'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': formatDescription(
|
||||||
|
'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] || formatDescription(
|
||||||
|
`The protocol "${protocol}" is a network protocol identifier that defines the rules and format for data communication between network devices.`,
|
||||||
|
'Network protocols specify how data is formatted, transmitted, and received over a network.',
|
||||||
|
''
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Port descriptions mapping
|
||||||
|
window.getPortDescription = function(port) {
|
||||||
|
const portDescriptions = {
|
||||||
|
20: 'FTP Data Transfer - File Transfer Protocol data connection',
|
||||||
|
21: 'FTP Control - File Transfer Protocol control connection',
|
||||||
|
22: 'SSH - Secure Shell, used for secure remote login and command execution',
|
||||||
|
23: 'Telnet - Unencrypted remote terminal access (insecure)',
|
||||||
|
25: 'SMTP - Simple Mail Transfer Protocol for sending email',
|
||||||
|
53: 'DNS - Domain Name System for resolving domain names to IP addresses',
|
||||||
|
67: 'DHCP Server - Dynamic Host Configuration Protocol server',
|
||||||
|
68: 'DHCP Client - Dynamic Host Configuration Protocol client',
|
||||||
|
69: 'TFTP - Trivial File Transfer Protocol',
|
||||||
|
80: 'HTTP - Hypertext Transfer Protocol for web traffic',
|
||||||
|
88: 'Kerberos - Network authentication protocol',
|
||||||
|
110: 'POP3 - Post Office Protocol version 3 for receiving email',
|
||||||
|
119: 'NNTP - Network News Transfer Protocol',
|
||||||
|
123: 'NTP - Network Time Protocol for time synchronization',
|
||||||
|
135: 'MSRPC - Microsoft Remote Procedure Call',
|
||||||
|
139: 'NetBIOS - Network Basic Input/Output System',
|
||||||
|
143: 'IMAP - Internet Message Access Protocol for email',
|
||||||
|
161: 'SNMP - Simple Network Management Protocol',
|
||||||
|
162: 'SNMP Trap - Simple Network Management Protocol trap',
|
||||||
|
389: 'LDAP - Lightweight Directory Access Protocol',
|
||||||
|
443: 'HTTPS - Hypertext Transfer Protocol Secure (encrypted web traffic)',
|
||||||
|
445: 'SMB - Server Message Block for file sharing (Windows)',
|
||||||
|
465: 'SMTPS - SMTP over SSL/TLS',
|
||||||
|
514: 'Syslog - System logging protocol',
|
||||||
|
515: 'LPD/LPR - Line Printer Daemon/Line Printer Remote',
|
||||||
|
587: 'SMTP Submission - Mail submission port',
|
||||||
|
636: 'LDAPS - LDAP over SSL/TLS',
|
||||||
|
993: 'IMAPS - IMAP over SSL/TLS',
|
||||||
|
995: 'POP3S - POP3 over SSL/TLS',
|
||||||
|
1433: 'MSSQL - Microsoft SQL Server database',
|
||||||
|
1521: 'Oracle Database - Oracle database listener',
|
||||||
|
3306: 'MySQL - MySQL database server',
|
||||||
|
3389: 'RDP - Remote Desktop Protocol (Windows)',
|
||||||
|
5432: 'PostgreSQL - PostgreSQL database server',
|
||||||
|
5900: 'VNC - Virtual Network Computing remote desktop',
|
||||||
|
6379: 'Redis - Redis in-memory data store',
|
||||||
|
8080: 'HTTP Alternate - Alternative HTTP port, often used for web proxies',
|
||||||
|
8443: 'HTTPS Alternate - Alternative HTTPS port',
|
||||||
|
27017: 'MongoDB - MongoDB database server',
|
||||||
|
27018: 'MongoDB Shard - MongoDB sharding port',
|
||||||
|
27019: 'MongoDB Config - MongoDB configuration server',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check for common port ranges
|
||||||
|
if (port >= 1024 && port <= 49151) {
|
||||||
|
return 'Registered/User Port - Port in the registered range, commonly used by applications';
|
||||||
|
} else if (port >= 49152 && port <= 65535) {
|
||||||
|
return 'Dynamic/Private Port - Ephemeral port range used for temporary connections';
|
||||||
|
}
|
||||||
|
|
||||||
|
return portDescriptions[port] || 'Unknown Port - No standard service associated with this port number';
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
@endscript
|
||||||
|
|
||||||
</x-proxmox::layout>
|
</x-proxmox::layout>
|
||||||
|
|||||||
Reference in New Issue
Block a user