feat: add action and protocol filters to firewall logs view, enhancing user experience by allowing more granular log searches and improved data representation

This commit is contained in:
2025-12-03 16:59:55 -05:00
parent 536d584785
commit 0d6fe220c9

View File

@@ -330,6 +330,8 @@
sortDirection: 'desc',
currentPage: 1,
perPage: 10,
actionFilter: 'all',
protocolFilter: 'all',
initialize(initialLogs) {
console.log('Initializing logs, received:', initialLogs ? initialLogs.length : 0, 'entries');
this.logs = (initialLogs || []).map((log, index) => ({
@@ -345,12 +347,69 @@
this.sortField = field;
this.sortDirection = 'asc';
}
this.currentPage = 1; // Reset to first page when sorting
this.currentPage = 1;
},
setActionFilter(filter) {
this.actionFilter = filter;
this.currentPage = 1;
},
setProtocolFilter(filter) {
this.protocolFilter = filter;
this.currentPage = 1;
},
get actionCounts() {
const counts = { all: this.logs.length, blocked: 0, allowed: 0 };
this.logs.forEach(log => {
const action = (log.action || '').toUpperCase();
if (action === 'DROP' || action === 'REJECT' || action.includes('DROP') || action.includes('REJECT')) {
counts.blocked++;
} else if (action === 'ACCEPT' || action.includes('ACCEPT')) {
counts.allowed++;
}
});
return counts;
},
get protocolCounts() {
const counts = { all: this.logs.length, tcp: 0, udp: 0, icmp: 0, other: 0 };
this.logs.forEach(log => {
const proto = (log.protocol || '').toUpperCase();
if (proto === 'TCP') counts.tcp++;
else if (proto === 'UDP') counts.udp++;
else if (proto === 'ICMP' || proto === 'ICMPV6') counts.icmp++;
else if (proto) counts.other++;
});
return counts;
},
get filteredLogs() {
const term = this.searchTerm.trim().toLowerCase();
let entries = this.logs;
// Apply action filter
if (this.actionFilter === 'blocked') {
entries = entries.filter(log => {
const action = (log.action || '').toUpperCase();
return action === 'DROP' || action === 'REJECT' || action.includes('DROP') || action.includes('REJECT');
});
} else if (this.actionFilter === 'allowed') {
entries = entries.filter(log => {
const action = (log.action || '').toUpperCase();
return action === 'ACCEPT' || action.includes('ACCEPT');
});
}
// Apply protocol filter
if (this.protocolFilter !== 'all') {
entries = entries.filter(log => {
const proto = (log.protocol || '').toUpperCase();
if (this.protocolFilter === 'tcp') return proto === 'TCP';
if (this.protocolFilter === 'udp') return proto === 'UDP';
if (this.protocolFilter === 'icmp') return proto === 'ICMP' || proto === 'ICMPV6';
if (this.protocolFilter === 'other') return proto && proto !== 'TCP' && proto !== 'UDP' && proto !== 'ICMP' && proto !== 'ICMPV6';
return true;
});
}
// Apply search filter
if (term) {
entries = entries.filter((log) => {
return Object.values(log).some((value) => {
@@ -453,29 +512,111 @@
x-effect="if (currentPage > totalPages && totalPages > 0) currentPage = totalPages"
class="space-y-4"
>
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-3 mb-4">
<div class="relative w-full md:w-72">
<x-ri-search-line class="size-4 absolute left-3 top-1/2 -translate-y-1/2 text-base/40" />
<input
type="text"
x-model="searchTerm"
@input="currentPage = 1"
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"
/>
<!-- Filter Tabs -->
<div class="flex flex-col gap-4 mb-4">
<!-- Action Filter -->
<div class="flex flex-wrap items-center gap-2">
<span class="text-xs font-semibold text-base/60 uppercase tracking-wider mr-1">Policy:</span>
<button
type="button"
@click="setActionFilter('all')"
:class="actionFilter === 'all' ? 'bg-primary text-white border-primary' : 'bg-background border-neutral text-base/70 hover:border-primary/50 hover:text-base'"
class="px-3 py-1.5 text-xs font-medium rounded-lg border transition-all flex items-center gap-1.5">
<x-ri-list-check class="size-3.5" />
All
<span class="px-1.5 py-0.5 rounded text-[10px] font-bold" :class="actionFilter === 'all' ? 'bg-white/20' : 'bg-base/10'" x-text="actionCounts.all"></span>
</button>
<button
type="button"
@click="setActionFilter('blocked')"
:class="actionFilter === 'blocked' ? 'bg-error text-white border-error' : 'bg-background border-neutral text-base/70 hover:border-error/50 hover:text-error'"
class="px-3 py-1.5 text-xs font-medium rounded-lg border transition-all flex items-center gap-1.5">
<x-ri-shield-cross-line class="size-3.5" />
Blocked
<span class="px-1.5 py-0.5 rounded text-[10px] font-bold" :class="actionFilter === 'blocked' ? 'bg-white/20' : 'bg-error/10 text-error'" x-text="actionCounts.blocked"></span>
</button>
<button
type="button"
@click="setActionFilter('allowed')"
:class="actionFilter === 'allowed' ? 'bg-success text-white border-success' : 'bg-background border-neutral text-base/70 hover:border-success/50 hover:text-success'"
class="px-3 py-1.5 text-xs font-medium rounded-lg border transition-all flex items-center gap-1.5">
<x-ri-shield-check-line class="size-3.5" />
Allowed
<span class="px-1.5 py-0.5 rounded text-[10px] font-bold" :class="actionFilter === 'allowed' ? 'bg-white/20' : 'bg-success/10 text-success'" x-text="actionCounts.allowed"></span>
</button>
</div>
<div class="flex items-center gap-4 text-sm">
<div class="px-3 py-1.5 bg-background-secondary border border-neutral/30 rounded-lg">
<span class="text-base/70">Showing </span>
<span class="font-semibold text-primary" x-text="paginatedLogs.length"></span>
<span class="text-base/70"> of </span>
<span class="font-semibold" x-text="filteredLogs.length"></span>
<!-- Protocol Filter -->
<div class="flex flex-wrap items-center gap-2">
<span class="text-xs font-semibold text-base/60 uppercase tracking-wider mr-1">Protocol:</span>
<button
type="button"
@click="setProtocolFilter('all')"
:class="protocolFilter === 'all' ? 'bg-secondary text-white border-secondary' : 'bg-background border-neutral text-base/70 hover:border-secondary/50'"
class="px-3 py-1.5 text-xs font-medium rounded-lg border transition-all flex items-center gap-1.5">
All
<span class="px-1.5 py-0.5 rounded text-[10px] font-bold" :class="protocolFilter === 'all' ? 'bg-white/20' : 'bg-base/10'" x-text="protocolCounts.all"></span>
</button>
<button
type="button"
@click="setProtocolFilter('tcp')"
:class="protocolFilter === 'tcp' ? 'bg-info text-white border-info' : 'bg-background border-neutral text-base/70 hover:border-info/50'"
class="px-3 py-1.5 text-xs font-medium rounded-lg border transition-all flex items-center gap-1.5">
TCP
<span class="px-1.5 py-0.5 rounded text-[10px] font-bold" :class="protocolFilter === 'tcp' ? 'bg-white/20' : 'bg-info/10 text-info'" x-text="protocolCounts.tcp"></span>
</button>
<button
type="button"
@click="setProtocolFilter('udp')"
:class="protocolFilter === 'udp' ? 'bg-warning text-white border-warning' : 'bg-background border-neutral text-base/70 hover:border-warning/50'"
class="px-3 py-1.5 text-xs font-medium rounded-lg border transition-all flex items-center gap-1.5">
UDP
<span class="px-1.5 py-0.5 rounded text-[10px] font-bold" :class="protocolFilter === 'udp' ? 'bg-white/20' : 'bg-warning/10 text-warning'" x-text="protocolCounts.udp"></span>
</button>
<button
type="button"
@click="setProtocolFilter('icmp')"
:class="protocolFilter === 'icmp' ? 'bg-purple-500 text-white border-purple-500' : 'bg-background border-neutral text-base/70 hover:border-purple-500/50'"
class="px-3 py-1.5 text-xs font-medium rounded-lg border transition-all flex items-center gap-1.5">
ICMP
<span class="px-1.5 py-0.5 rounded text-[10px] font-bold" :class="protocolFilter === 'icmp' ? 'bg-white/20' : 'bg-purple-500/10 text-purple-500'" x-text="protocolCounts.icmp"></span>
</button>
<button
type="button"
@click="setProtocolFilter('other')"
x-show="protocolCounts.other > 0"
:class="protocolFilter === 'other' ? 'bg-base/80 text-white border-base/80' : 'bg-background border-neutral text-base/70 hover:border-base/50'"
class="px-3 py-1.5 text-xs font-medium rounded-lg border transition-all flex items-center gap-1.5">
Other
<span class="px-1.5 py-0.5 rounded text-[10px] font-bold" :class="protocolFilter === 'other' ? 'bg-white/20' : 'bg-base/10'" x-text="protocolCounts.other"></span>
</button>
</div>
<!-- Search and Stats Row -->
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-3 pt-2 border-t border-neutral/30">
<div class="relative w-full md:w-72">
<x-ri-search-line class="size-4 absolute left-3 top-1/2 -translate-y-1/2 text-base/40" />
<input
type="text"
x-model="searchTerm"
@input="currentPage = 1"
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"
/>
</div>
<div class="px-3 py-1.5 bg-background-secondary border border-neutral/30 rounded-lg">
<span class="text-base/70">Page </span>
<span class="font-semibold text-primary" x-text="currentPage"></span>
<span class="text-base/70"> / </span>
<span class="font-semibold" x-text="totalPages || 1"></span>
<div class="flex items-center gap-4 text-sm">
<div class="px-3 py-1.5 bg-background-secondary border border-neutral/30 rounded-lg">
<span class="text-base/70">Showing </span>
<span class="font-semibold text-primary" x-text="paginatedLogs.length"></span>
<span class="text-base/70"> of </span>
<span class="font-semibold" x-text="filteredLogs.length"></span>
</div>
<div class="px-3 py-1.5 bg-background-secondary border border-neutral/30 rounded-lg">
<span class="text-base/70">Page </span>
<span class="font-semibold text-primary" x-text="currentPage"></span>
<span class="text-base/70"> / </span>
<span class="font-semibold" x-text="totalPages || 1"></span>
</div>
</div>
</div>
</div>