refactor: simplify Proxmox Firewall log handling by removing unnecessary log grouping and optimizing log fetching logic
This commit is contained in:
@@ -74,9 +74,6 @@
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.firewall-log-search-input {
|
||||
padding-left: 2.75rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="space-y-6">
|
||||
@@ -328,41 +325,11 @@
|
||||
searchTerm: '',
|
||||
sortField: 'timestamp_iso',
|
||||
sortDirection: 'desc',
|
||||
initialized: false,
|
||||
seenRaws: new Set(),
|
||||
initialize(initialLogs) {
|
||||
if (this.initialized) return;
|
||||
|
||||
if (initialLogs && initialLogs.length > 0) {
|
||||
this.logs = initialLogs.map((log, index) => ({
|
||||
index,
|
||||
...log,
|
||||
}));
|
||||
// Track which logs we've seen
|
||||
this.logs.forEach(log => {
|
||||
if (log.raw) this.seenRaws.add(log.raw);
|
||||
});
|
||||
this.initialized = true;
|
||||
}
|
||||
},
|
||||
updateLogs(newLogs) {
|
||||
if (!newLogs || newLogs.length === 0) return;
|
||||
|
||||
// Filter out logs we've already seen
|
||||
const logsToAdd = newLogs.filter(log => {
|
||||
if (!log.raw) return false;
|
||||
if (this.seenRaws.has(log.raw)) return false;
|
||||
this.seenRaws.add(log.raw);
|
||||
return true;
|
||||
});
|
||||
|
||||
if (logsToAdd.length > 0) {
|
||||
// Add new logs to the beginning (most recent first)
|
||||
this.logs = [...logsToAdd, ...this.logs].map((log, index) => ({
|
||||
index,
|
||||
...log,
|
||||
}));
|
||||
}
|
||||
this.logs = (initialLogs || []).map((log, index) => ({
|
||||
index,
|
||||
...log,
|
||||
}));
|
||||
},
|
||||
sort(field) {
|
||||
if (this.sortField === field) {
|
||||
@@ -409,14 +376,6 @@
|
||||
},
|
||||
}"
|
||||
x-init="initialize(@js($firewallLogs))"
|
||||
@logs-updated.window="updateLogs($event.detail.logs || $event.detail[0]?.logs || $event.detail[0] || [])"
|
||||
x-effect="
|
||||
// Watch for changes in the backend data and merge new logs
|
||||
const backendLogs = @js($firewallLogs);
|
||||
if (initialized && backendLogs && backendLogs.length > 0) {
|
||||
updateLogs(backendLogs);
|
||||
}
|
||||
"
|
||||
class="space-y-4"
|
||||
>
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-3">
|
||||
@@ -426,7 +385,7 @@
|
||||
type="text"
|
||||
x-model="searchTerm"
|
||||
placeholder="Search logs..."
|
||||
class="firewall-log-search-input w-full pr-4 py-2 border border-neutral rounded-lg bg-background focus:outline-none focus:ring-2 focus:ring-primary/40"
|
||||
class="w-full pl-10 pr-4 py-2 border border-neutral rounded-lg bg-background focus:outline-none focus:ring-2 focus:ring-primary/40"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-xs text-base/50">
|
||||
@@ -490,543 +449,20 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="(log, index) in filteredLogs" :key="index">
|
||||
<template x-if="true">
|
||||
<template x-if="log.is_group">
|
||||
<!-- Grouped log row -->
|
||||
<template x-data="{ expanded: false }">
|
||||
<tr class="border-b border-neutral/20 hover:bg-background-secondary/50 cursor-pointer" @click="expanded = !expanded">
|
||||
<td class="py-3 px-4 text-sm">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="size-3 text-base/50 transition-transform flex-shrink-0" :class="{ 'rotate-90': expanded }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
</svg>
|
||||
<span x-text="log.timestamp_display || '-'"></span>
|
||||
</div>
|
||||
</td>
|
||||
<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>
|
||||
</td>
|
||||
<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">
|
||||
<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.stop="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.stop="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.stop="
|
||||
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.stop="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">
|
||||
<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.stop="
|
||||
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.stop="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">
|
||||
<div class="flex items-center gap-2">
|
||||
<span x-text="log.destination_port || '-'"></span>
|
||||
<span class="px-2 py-1 bg-primary/10 text-primary rounded-full text-xs font-semibold" x-text="log.group_count"></span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Expanded details row -->
|
||||
<tr x-show="expanded" x-collapse class="border-b border-neutral/20">
|
||||
<td colspan="8" class="p-0">
|
||||
<div class="bg-background/30 px-4 py-3">
|
||||
<div class="text-xs text-base/60 mb-3 font-semibold">
|
||||
<span x-text="log.group_count"></span> similar events
|
||||
</div>
|
||||
<div class="space-y-2 max-h-96 overflow-y-auto">
|
||||
<template x-for="(subLog, subIndex) in log.grouped_logs" :key="subIndex">
|
||||
<div class="bg-background-secondary border border-neutral/10 rounded-lg p-3">
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<span class="text-xs text-base/70" x-text="subLog.timestamp_display"></span>
|
||||
<span class="px-2 py-0.5 rounded text-xs font-semibold uppercase" :class="subLog.level_badge_class" x-text="subLog.level_label"></span>
|
||||
<span class="px-2 py-0.5 rounded text-xs font-semibold" :class="subLog.action_badge_class" x-text="subLog.action"></span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-1 text-xs mb-2">
|
||||
<div><span class="text-base/50">Protocol:</span> <span class="font-semibold" x-text="subLog.protocol || '-'"></span></div>
|
||||
<div><span class="text-base/50">Interface:</span> <span class="font-semibold" x-text="subLog.iface || '-'"></span></div>
|
||||
<div><span class="text-base/50">Source:</span> <span class="font-mono font-semibold" x-text="subLog.source || '-'"></span><span x-show="subLog.source_port" class="text-base/50">:<span class="font-semibold" x-text="subLog.source_port"></span></span></div>
|
||||
<div><span class="text-base/50">Destination:</span> <span class="font-mono font-semibold" x-text="subLog.destination || '-'"></span><span x-show="subLog.destination_port" class="text-base/50">:<span class="font-semibold" x-text="subLog.destination_port"></span></span></div>
|
||||
</div>
|
||||
<details class="text-xs">
|
||||
<summary class="cursor-pointer text-base/50 hover:text-base/70 transition-colors">View raw log</summary>
|
||||
<div class="mt-2 p-2 bg-background/50 rounded border border-neutral/10 font-mono text-[10px] text-base/60 break-all" x-text="subLog.raw"></div>
|
||||
</details>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</template>
|
||||
<template x-if="!log.is_group">
|
||||
<!-- Single log row -->
|
||||
<tr class="border-b border-neutral/20 hover:bg-background-secondary/50">
|
||||
<td class="py-3 px-4 text-sm" x-text="log.timestamp_display || '-' "></td>
|
||||
<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>
|
||||
</td>
|
||||
<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>
|
||||
<tr class="border-b border-neutral/20 hover:bg-background-secondary/50">
|
||||
<td class="py-3 px-4 text-sm" x-text="log.timestamp_display || '-' "></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>
|
||||
<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 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>
|
||||
<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" x-text="log.source_port || '-' "></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>
|
||||
</template>
|
||||
<td class="py-3 px-4 text-sm font-mono" x-text="log.destination || '-' "></td>
|
||||
<td class="py-3 px-4 text-sm" x-text="log.destination_port || '-' "></td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -1295,161 +731,4 @@
|
||||
@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