feat: enhance firewall log filtering with multi-select options for action and protocol, improving user experience and interaction through dynamic button states and counts

This commit is contained in:
2025-12-03 17:21:03 -05:00
parent 0a5a8c3f05
commit b6c79ac26d

View File

@@ -128,30 +128,75 @@
color: #ffffff;
}
.firewall-filter-btn.active-blocked {
background-color: #ef4444;
border-color: #ef4444;
/* DROP - Red */
.firewall-filter-btn.active-drop {
background-color: #dc2626;
border-color: #dc2626;
color: white;
box-shadow: 0 2px 8px rgba(220, 38, 38, 0.3);
}
.firewall-filter-btn.active-blocked .firewall-filter-btn-count {
background-color: rgba(255, 255, 255, 0.2);
.firewall-filter-btn.active-drop .firewall-filter-btn-count {
background-color: rgba(255, 255, 255, 0.25);
}
.firewall-filter-btn:not(.active-blocked) .firewall-filter-btn-count.count-blocked {
background-color: rgba(239, 68, 68, 0.1);
color: #ef4444;
.firewall-filter-btn:not(.active-drop) .firewall-filter-btn-count.count-drop {
background-color: rgba(220, 38, 38, 0.1);
color: #dc2626;
}
.firewall-filter-btn.active-allowed {
background-color: #22c55e;
border-color: #22c55e;
/* REJECT - Orange-Red */
.firewall-filter-btn.active-reject {
background-color: #ea580c;
border-color: #ea580c;
color: white;
box-shadow: 0 2px 8px rgba(234, 88, 12, 0.3);
}
.firewall-filter-btn.active-allowed .firewall-filter-btn-count {
background-color: rgba(255, 255, 255, 0.2);
.firewall-filter-btn.active-reject .firewall-filter-btn-count {
background-color: rgba(255, 255, 255, 0.25);
}
.firewall-filter-btn:not(.active-allowed) .firewall-filter-btn-count.count-allowed {
background-color: rgba(34, 197, 94, 0.1);
color: #22c55e;
.firewall-filter-btn:not(.active-reject) .firewall-filter-btn-count.count-reject {
background-color: rgba(234, 88, 12, 0.1);
color: #ea580c;
}
/* ACCEPT - Green */
.firewall-filter-btn.active-accept {
background-color: #16a34a;
border-color: #16a34a;
color: white;
box-shadow: 0 2px 8px rgba(22, 163, 74, 0.3);
}
.firewall-filter-btn.active-accept .firewall-filter-btn-count {
background-color: rgba(255, 255, 255, 0.25);
}
.firewall-filter-btn:not(.active-accept) .firewall-filter-btn-count.count-accept {
background-color: rgba(22, 163, 74, 0.1);
color: #16a34a;
}
/* LOG - Cyan */
.firewall-filter-btn.active-log {
background-color: #0891b2;
border-color: #0891b2;
color: white;
box-shadow: 0 2px 8px rgba(8, 145, 178, 0.3);
}
.firewall-filter-btn.active-log .firewall-filter-btn-count {
background-color: rgba(255, 255, 255, 0.25);
}
.firewall-filter-btn:not(.active-log) .firewall-filter-btn-count.count-log {
background-color: rgba(8, 145, 178, 0.1);
color: #0891b2;
}
/* Action Other - Gray */
.firewall-filter-btn.active-action-other {
background-color: #525252;
border-color: #525252;
color: white;
box-shadow: 0 2px 8px rgba(82, 82, 82, 0.3);
}
.firewall-filter-btn.active-action-other .firewall-filter-btn-count {
background-color: rgba(255, 255, 255, 0.25);
}
/* Active states for Protocol filters */
@@ -205,13 +250,90 @@
color: #a855f7;
}
.firewall-filter-btn.active-other {
background-color: rgba(var(--color-base), 0.7);
border-color: rgba(var(--color-base), 0.7);
/* GRE - Teal */
.firewall-filter-btn.active-gre {
background-color: #0d9488;
border-color: #0d9488;
color: white;
box-shadow: 0 2px 8px rgba(13, 148, 136, 0.3);
}
.firewall-filter-btn.active-other .firewall-filter-btn-count {
background-color: rgba(255, 255, 255, 0.2);
.firewall-filter-btn.active-gre .firewall-filter-btn-count {
background-color: rgba(255, 255, 255, 0.25);
}
.firewall-filter-btn:not(.active-gre) .firewall-filter-btn-count.count-gre {
background-color: rgba(13, 148, 136, 0.1);
color: #0d9488;
}
/* ESP - Indigo */
.firewall-filter-btn.active-esp {
background-color: #4f46e5;
border-color: #4f46e5;
color: white;
box-shadow: 0 2px 8px rgba(79, 70, 229, 0.3);
}
.firewall-filter-btn.active-esp .firewall-filter-btn-count {
background-color: rgba(255, 255, 255, 0.25);
}
.firewall-filter-btn:not(.active-esp) .firewall-filter-btn-count.count-esp {
background-color: rgba(79, 70, 229, 0.1);
color: #4f46e5;
}
/* AH - Violet */
.firewall-filter-btn.active-ah {
background-color: #7c3aed;
border-color: #7c3aed;
color: white;
box-shadow: 0 2px 8px rgba(124, 58, 237, 0.3);
}
.firewall-filter-btn.active-ah .firewall-filter-btn-count {
background-color: rgba(255, 255, 255, 0.25);
}
.firewall-filter-btn:not(.active-ah) .firewall-filter-btn-count.count-ah {
background-color: rgba(124, 58, 237, 0.1);
color: #7c3aed;
}
/* SCTP - Pink */
.firewall-filter-btn.active-sctp {
background-color: #db2777;
border-color: #db2777;
color: white;
box-shadow: 0 2px 8px rgba(219, 39, 119, 0.3);
}
.firewall-filter-btn.active-sctp .firewall-filter-btn-count {
background-color: rgba(255, 255, 255, 0.25);
}
.firewall-filter-btn:not(.active-sctp) .firewall-filter-btn-count.count-sctp {
background-color: rgba(219, 39, 119, 0.1);
color: #db2777;
}
/* IGMP - Lime */
.firewall-filter-btn.active-igmp {
background-color: #65a30d;
border-color: #65a30d;
color: white;
box-shadow: 0 2px 8px rgba(101, 163, 13, 0.3);
}
.firewall-filter-btn.active-igmp .firewall-filter-btn-count {
background-color: rgba(255, 255, 255, 0.25);
}
.firewall-filter-btn:not(.active-igmp) .firewall-filter-btn-count.count-igmp {
background-color: rgba(101, 163, 13, 0.1);
color: #65a30d;
}
/* Protocol Other - Dark Gray */
.firewall-filter-btn.active-proto-other {
background-color: #525252;
border-color: #525252;
color: white;
box-shadow: 0 2px 8px rgba(82, 82, 82, 0.3);
}
.firewall-filter-btn.active-proto-other .firewall-filter-btn-count {
background-color: rgba(255, 255, 255, 0.25);
}
.firewall-filter-row {
@@ -478,8 +600,8 @@
sortDirection: 'desc',
currentPage: 1,
perPage: 10,
actionFilter: 'all',
protocolFilter: 'all',
actionFilters: [],
protocolFilters: [],
initialize(initialLogs) {
console.log('Initializing logs, received:', initialLogs ? initialLogs.length : 0, 'entries');
this.logs = (initialLogs || []).map((log, index) => ({
@@ -497,33 +619,68 @@
}
this.currentPage = 1;
},
setActionFilter(filter) {
this.actionFilter = filter;
toggleActionFilter(filter) {
const idx = this.actionFilters.indexOf(filter);
if (idx === -1) {
this.actionFilters.push(filter);
} else {
this.actionFilters.splice(idx, 1);
}
this.currentPage = 1;
},
setProtocolFilter(filter) {
this.protocolFilter = filter;
toggleProtocolFilter(filter) {
const idx = this.protocolFilters.indexOf(filter);
if (idx === -1) {
this.protocolFilters.push(filter);
} else {
this.protocolFilters.splice(idx, 1);
}
this.currentPage = 1;
},
clearActionFilters() {
this.actionFilters = [];
this.currentPage = 1;
},
clearProtocolFilters() {
this.protocolFilters = [];
this.currentPage = 1;
},
isActionActive(filter) {
return this.actionFilters.includes(filter);
},
isProtocolActive(filter) {
return this.protocolFilters.includes(filter);
},
get actionCounts() {
const counts = { all: this.logs.length, blocked: 0, allowed: 0 };
const counts = { all: this.logs.length, drop: 0, reject: 0, accept: 0, log: 0, other: 0 };
this.logs.forEach(log => {
const action = (log.action || '').toUpperCase();
if (action === 'DROP' || action === 'REJECT' || action.includes('DROP') || action.includes('REJECT')) {
counts.blocked++;
if (action === 'DROP' || action.includes('DROP')) {
counts.drop++;
} else if (action === 'REJECT' || action.includes('REJECT')) {
counts.reject++;
} else if (action === 'ACCEPT' || action.includes('ACCEPT')) {
counts.allowed++;
counts.accept++;
} else if (action === 'LOG' || action.includes('LOG')) {
counts.log++;
} else if (action) {
counts.other++;
}
});
return counts;
},
get protocolCounts() {
const counts = { all: this.logs.length, tcp: 0, udp: 0, icmp: 0, other: 0 };
const counts = { all: this.logs.length, tcp: 0, udp: 0, icmp: 0, gre: 0, esp: 0, ah: 0, sctp: 0, igmp: 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 === 'GRE') counts.gre++;
else if (proto === 'ESP') counts.esp++;
else if (proto === 'AH') counts.ah++;
else if (proto === 'SCTP') counts.sctp++;
else if (proto === 'IGMP') counts.igmp++;
else if (proto) counts.other++;
});
return counts;
@@ -532,28 +689,37 @@
const term = this.searchTerm.trim().toLowerCase();
let entries = this.logs;
// Apply action filter
if (this.actionFilter === 'blocked') {
// Apply action filters (multi-select)
if (this.actionFilters.length > 0) {
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');
for (const filter of this.actionFilters) {
if (filter === 'drop' && (action === 'DROP' || action.includes('DROP'))) return true;
if (filter === 'reject' && (action === 'REJECT' || action.includes('REJECT'))) return true;
if (filter === 'accept' && (action === 'ACCEPT' || action.includes('ACCEPT'))) return true;
if (filter === 'log' && (action === 'LOG' || action.includes('LOG'))) return true;
if (filter === 'other' && action && !['DROP', 'REJECT', 'ACCEPT', 'LOG'].some(a => action.includes(a))) return true;
}
return false;
});
}
// Apply protocol filter
if (this.protocolFilter !== 'all') {
// Apply protocol filters (multi-select)
if (this.protocolFilters.length > 0) {
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;
for (const filter of this.protocolFilters) {
if (filter === 'tcp' && proto === 'TCP') return true;
if (filter === 'udp' && proto === 'UDP') return true;
if (filter === 'icmp' && (proto === 'ICMP' || proto === 'ICMPV6')) return true;
if (filter === 'gre' && proto === 'GRE') return true;
if (filter === 'esp' && proto === 'ESP') return true;
if (filter === 'ah' && proto === 'AH') return true;
if (filter === 'sctp' && proto === 'SCTP') return true;
if (filter === 'igmp' && proto === 'IGMP') return true;
if (filter === 'other' && proto && !['TCP', 'UDP', 'ICMP', 'ICMPV6', 'GRE', 'ESP', 'AH', 'SCTP', 'IGMP'].includes(proto)) return true;
}
return false;
});
}
@@ -662,13 +828,13 @@
>
<!-- Filter Tabs -->
<div style="display: flex; flex-direction: column; gap: 1rem; margin-bottom: 1rem;">
<!-- Action Filter -->
<!-- Action/Policy Filter (Multi-select) -->
<div class="firewall-filter-row">
<span class="firewall-filter-label">Policy:</span>
<button
type="button"
@click="setActionFilter('all')"
:class="actionFilter === 'all' ? 'active-all' : ''"
@click="clearActionFilters()"
:class="actionFilters.length === 0 ? 'active-all' : ''"
class="firewall-filter-btn">
<x-ri-list-check style="width: 14px; height: 14px;" />
All
@@ -676,64 +842,143 @@
</button>
<button
type="button"
@click="setActionFilter('blocked')"
:class="actionFilter === 'blocked' ? 'active-blocked' : ''"
@click="toggleActionFilter('drop')"
x-show="actionCounts.drop > 0"
:class="isActionActive('drop') ? 'active-drop' : ''"
class="firewall-filter-btn">
<x-ri-shield-cross-line style="width: 14px; height: 14px;" />
Blocked
<span class="firewall-filter-btn-count count-blocked" x-text="actionCounts.blocked"></span>
<x-ri-close-circle-line style="width: 14px; height: 14px;" />
DROP
<span class="firewall-filter-btn-count count-drop" x-text="actionCounts.drop"></span>
</button>
<button
type="button"
@click="setActionFilter('allowed')"
:class="actionFilter === 'allowed' ? 'active-allowed' : ''"
@click="toggleActionFilter('reject')"
x-show="actionCounts.reject > 0"
:class="isActionActive('reject') ? 'active-reject' : ''"
class="firewall-filter-btn">
<x-ri-shield-check-line style="width: 14px; height: 14px;" />
Allowed
<span class="firewall-filter-btn-count count-allowed" x-text="actionCounts.allowed"></span>
<x-ri-forbid-line style="width: 14px; height: 14px;" />
REJECT
<span class="firewall-filter-btn-count count-reject" x-text="actionCounts.reject"></span>
</button>
<button
type="button"
@click="toggleActionFilter('accept')"
x-show="actionCounts.accept > 0"
:class="isActionActive('accept') ? 'active-accept' : ''"
class="firewall-filter-btn">
<x-ri-checkbox-circle-line style="width: 14px; height: 14px;" />
ACCEPT
<span class="firewall-filter-btn-count count-accept" x-text="actionCounts.accept"></span>
</button>
<button
type="button"
@click="toggleActionFilter('log')"
x-show="actionCounts.log > 0"
:class="isActionActive('log') ? 'active-log' : ''"
class="firewall-filter-btn">
<x-ri-file-list-line style="width: 14px; height: 14px;" />
LOG
<span class="firewall-filter-btn-count count-log" x-text="actionCounts.log"></span>
</button>
<button
type="button"
@click="toggleActionFilter('other')"
x-show="actionCounts.other > 0"
:class="isActionActive('other') ? 'active-action-other' : ''"
class="firewall-filter-btn">
Other
<span class="firewall-filter-btn-count" x-text="actionCounts.other"></span>
</button>
</div>
<!-- Protocol Filter -->
<!-- Protocol Filter (Multi-select) -->
<div class="firewall-filter-row">
<span class="firewall-filter-label">Protocol:</span>
<button
type="button"
@click="setProtocolFilter('all')"
:class="protocolFilter === 'all' ? 'active-proto-all' : ''"
@click="clearProtocolFilters()"
:class="protocolFilters.length === 0 ? 'active-proto-all' : ''"
class="firewall-filter-btn">
All
<span class="firewall-filter-btn-count" x-text="protocolCounts.all"></span>
</button>
<button
type="button"
@click="setProtocolFilter('tcp')"
:class="protocolFilter === 'tcp' ? 'active-tcp' : ''"
@click="toggleProtocolFilter('tcp')"
x-show="protocolCounts.tcp > 0"
:class="isProtocolActive('tcp') ? 'active-tcp' : ''"
class="firewall-filter-btn">
TCP
<span class="firewall-filter-btn-count count-tcp" x-text="protocolCounts.tcp"></span>
</button>
<button
type="button"
@click="setProtocolFilter('udp')"
:class="protocolFilter === 'udp' ? 'active-udp' : ''"
@click="toggleProtocolFilter('udp')"
x-show="protocolCounts.udp > 0"
:class="isProtocolActive('udp') ? 'active-udp' : ''"
class="firewall-filter-btn">
UDP
<span class="firewall-filter-btn-count count-udp" x-text="protocolCounts.udp"></span>
</button>
<button
type="button"
@click="setProtocolFilter('icmp')"
:class="protocolFilter === 'icmp' ? 'active-icmp' : ''"
@click="toggleProtocolFilter('icmp')"
x-show="protocolCounts.icmp > 0"
:class="isProtocolActive('icmp') ? 'active-icmp' : ''"
class="firewall-filter-btn">
ICMP
<span class="firewall-filter-btn-count count-icmp" x-text="protocolCounts.icmp"></span>
</button>
<button
type="button"
@click="setProtocolFilter('other')"
@click="toggleProtocolFilter('gre')"
x-show="protocolCounts.gre > 0"
:class="isProtocolActive('gre') ? 'active-gre' : ''"
class="firewall-filter-btn">
GRE
<span class="firewall-filter-btn-count count-gre" x-text="protocolCounts.gre"></span>
</button>
<button
type="button"
@click="toggleProtocolFilter('esp')"
x-show="protocolCounts.esp > 0"
:class="isProtocolActive('esp') ? 'active-esp' : ''"
class="firewall-filter-btn">
ESP
<span class="firewall-filter-btn-count count-esp" x-text="protocolCounts.esp"></span>
</button>
<button
type="button"
@click="toggleProtocolFilter('ah')"
x-show="protocolCounts.ah > 0"
:class="isProtocolActive('ah') ? 'active-ah' : ''"
class="firewall-filter-btn">
AH
<span class="firewall-filter-btn-count count-ah" x-text="protocolCounts.ah"></span>
</button>
<button
type="button"
@click="toggleProtocolFilter('sctp')"
x-show="protocolCounts.sctp > 0"
:class="isProtocolActive('sctp') ? 'active-sctp' : ''"
class="firewall-filter-btn">
SCTP
<span class="firewall-filter-btn-count count-sctp" x-text="protocolCounts.sctp"></span>
</button>
<button
type="button"
@click="toggleProtocolFilter('igmp')"
x-show="protocolCounts.igmp > 0"
:class="isProtocolActive('igmp') ? 'active-igmp' : ''"
class="firewall-filter-btn">
IGMP
<span class="firewall-filter-btn-count count-igmp" x-text="protocolCounts.igmp"></span>
</button>
<button
type="button"
@click="toggleProtocolFilter('other')"
x-show="protocolCounts.other > 0"
:class="protocolFilter === 'other' ? 'active-other' : ''"
:class="isProtocolActive('other') ? 'active-proto-other' : ''"
class="firewall-filter-btn">
Other
<span class="firewall-filter-btn-count" x-text="protocolCounts.other"></span>