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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user