@@ -4,7 +4,7 @@ import { useEvents } from '@/composables/events'
44import { onUnmounted , computed , useTemplateRef , onMounted , reactive , watch , ref , type ComponentPublicInstance } from ' vue'
55import { usePrettyDates } from ' @/composables/usePrettyDate'
66import { usePrettyHttp } from ' @/composables/http'
7- import { Modal } from ' bootstrap'
7+ import { Modal , Tooltip } from ' bootstrap'
88import { useService } from ' @/composables/services'
99import RegexInput from ' @/components/RegexInput.vue'
1010
@@ -48,10 +48,12 @@ interface Filter {
4848 response: {
4949 headers: FilterItem
5050 }
51+ clientIP: FilterItem
5152}
5253interface FilterItem {
5354 checkbox: boolean
5455 value: any
56+ title? : string
5557}
5658interface MultiFilterItem {
5759 checkbox: boolean
@@ -67,7 +69,11 @@ const filter = reactive<Filter>({
6769 },
6870 response: {
6971 headers: { checkbox: false , value: [{ name: ' ' , value: ' ' }]}
70- }
72+ },
73+ clientIP: { checkbox: false , value: ' ' , title: ` <b>IP Filter Options:</b><br>
74+ • Multiple entries (comma-separated): 192.168.0.1,10.0.0.1<br>
75+ • Negation: -127.0.0.1<br>
76+ • CIDR notation: 192.168.0.0/24 ` }
7177})
7278
7379onMounted (() => {
@@ -82,6 +88,10 @@ onMounted(() => {
8288 }
8389 }
8490 }
91+ const tooltipTriggerList = document .querySelectorAll (' [data-bs-toggle="tooltip"]' )
92+ tooltipTriggerList .forEach (el => new Tooltip (el , {
93+ trigger: ' hover'
94+ }))
8595})
8696
8797function goToRequest(event : ServiceEvent ){
@@ -169,6 +179,38 @@ const events = computed<ServiceEvent[]>(() => {
169179 return true
170180 })
171181 }
182+ if (filter .clientIP .checkbox && filter .clientIP .value .length > 0 ) {
183+ const values = (filter .clientIP .value as string ).split (' ,' ).map (x => x .trim ()).filter (x => x .length > 0 )
184+ result = result .filter (x => {
185+ const data = x .data as HttpEventData
186+
187+ let matched = false ;
188+
189+ for (let value of values ) {
190+ let not = false
191+ if (value [0 ] === ' -' ) {
192+ not = true
193+ value = value .substring (1 )
194+ }
195+ let isMatch = false
196+ if (value .includes (' /' )) {
197+ isMatch = cidrMatch (data .clientIP , value )
198+ } else {
199+ isMatch = data .clientIP === value
200+ }
201+
202+ if (not && isMatch ) {
203+ // Negated value matches → reject immediately
204+ return false
205+ }
206+ if (! not && isMatch ) {
207+ // Normal value matches → mark as matched
208+ matched = true ;
209+ }
210+ }
211+ return matched || values .every (v => v [0 ] === ' -' )
212+ })
213+ }
172214
173215 return result
174216})
@@ -276,6 +318,9 @@ const activeFiltersCount = computed(() => {
276318 if (filter .response .headers .checkbox && filter .response .headers .value .length > 1 ) {
277319 counter ++ ;
278320 }
321+ if (filter .clientIP .checkbox && filter .clientIP .value .length > 0 ) {
322+ counter ++
323+ }
279324 return counter ;
280325})
281326function getFilterCacheKey() {
@@ -417,6 +462,30 @@ const responseHeaderFilter = computed(() => {
417462 }
418463 return result
419464})
465+ function ipToInt(ip : string ): number {
466+ return ip
467+ .split (' .' )
468+ .map ((octet ) => parseInt (octet , 10 ))
469+ .reduce ((acc , octet ) => (acc << 8 ) + octet , 0 );
470+ }
471+
472+ function cidrMatch(ip : string , cidr : string ): boolean {
473+ const parts: string [] = cidr .split (' /' );
474+ if (parts .length !== 2 ) {
475+ return false
476+ }
477+
478+ const [range, bitsStr] = parts ;
479+ const bits: number = parseInt (bitsStr ! , 10 );
480+
481+ if (isNaN (bits ) || bits < 0 || bits > 32 ) {
482+ return false
483+ }
484+
485+ const mask: number = ~ (2 ** (32 - bits ) - 1 );
486+
487+ return (ipToInt (ip ) & mask ) === (ipToInt (range ! ) & mask );
488+ }
420489 </script >
421490
422491<template >
@@ -619,6 +688,23 @@ const responseHeaderFilter = computed(() => {
619688 </div >
620689 </div >
621690
691+ <!-- Client IP -->
692+ <div class =" row" >
693+ <div class =" col-4" >
694+ <div class =" form-check" data-bs-toggle =" tooltip" data-bs-delay =' {"show": 200, "hide": 100}' :title =" filter.clientIP.title" data-bs-offset =" [-150, 0]" data-bs-html =" true" >
695+ <input class =" form-check-input" type =" checkbox" v-model =" filter.clientIP.checkbox" id =" clientIP" >
696+ <label class =" form-check-label" for =" clientIP" >Client IP</label >
697+ </div >
698+ </div >
699+ <div class =" col" v-show =" filter.clientIP.checkbox" >
700+ <div class =" col ps-0 pe-1" data-bs-toggle =" tooltip" data-bs-delay =' {"show": 200, "hide": 100}' :title =" filter.clientIP.title" data-bs-html =" true" >
701+ <div class =" row me-0" >
702+ <input type =" text" class =" form-control form-control-sm" id =" clientIP" v-model =" filter.clientIP.value" >
703+ </div >
704+ </div >
705+ </div >
706+ </div >
707+
622708 </div >
623709 </div >
624710 </div >
0 commit comments