Skip to content

Commit fb5a764

Browse files
committed
add status code filter
1 parent 39baf47 commit fb5a764

File tree

1 file changed

+91
-9
lines changed

1 file changed

+91
-9
lines changed

webui/src/components/dashboard/http/Requests.vue

Lines changed: 91 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ interface Filter {
4747
}
4848
response: {
4949
headers: FilterItem
50+
statusCode: FilterItem
5051
}
5152
clientIP: FilterItem
5253
}
@@ -68,25 +69,26 @@ const filter = reactive<Filter>({
6869
headers: { checkbox: false, value: [{ name: '', value: '' }]}
6970
},
7071
response: {
71-
headers: { checkbox: false, value: [{ name: '', value: '' }]}
72+
headers: { checkbox: false, value: [{ name: '', value: '' }]},
73+
statusCode: { checkbox: false, value: '', title: `<b>Status Code Filter:</b><br>
74+
&bull; Multiple entries (comma-separated): 200, 404<br>
75+
&bull; Negation: prefix with '-' (e.g., -501)<br>
76+
&bull; Range: start-end (e.g., 200-300)`
77+
}
7278
},
7379
clientIP: { checkbox: false, value: '', title: `<b>IP Filter Options:</b><br>
7480
&bull; Multiple entries (comma-separated): 192.168.0.1,10.0.0.1<br>
75-
&bull; Negation: -127.0.0.1<br>
81+
&bull; Negation: prefix with '-' (e.g., -127.0.0.1)<br>
7682
&bull; CIDR notation: 192.168.0.0/24` }
77-
})
83+
}
84+
)
7885
7986
onMounted(() => {
8087
dialog = Modal.getOrCreateInstance(dialogRef.value!);
8188
const s = localStorage.getItem(`http-requests-${getFilterCacheKey()}`)
8289
if (s && s !== '') {
8390
const saved = JSON.parse(s)
84-
type Key = keyof Filter
85-
for (const key in filter) {
86-
if (saved[key] !== undefined) {
87-
filter[key as Key] = saved[key]
88-
}
89-
}
91+
mergeDeep(filter, saved)
9092
}
9193
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
9294
tooltipTriggerList.forEach(el => new Tooltip(el, {
@@ -211,6 +213,44 @@ const events = computed<ServiceEvent[]>(() => {
211213
return matched || values.every(v => v[0] === '-')
212214
})
213215
}
216+
if (filter.response.statusCode.checkbox && filter.response.statusCode.value.length > 0) {
217+
const values = (filter.response.statusCode.value as string).split(',').map(x => x.trim()).filter(x => x.length > 0)
218+
result = result.filter(x => {
219+
const data = x.data as HttpEventData
220+
221+
let matched = false;
222+
223+
for (let value of values) {
224+
let not = false
225+
if (value[0] === '-') {
226+
not = true
227+
value = value.substring(1)
228+
}
229+
let isMatch = false
230+
if (value.includes('-')) {
231+
const parts = value.split('-').map(x => x.trim())
232+
if (parts.length !== 2) {
233+
continue
234+
}
235+
const min = parseInt(parts[0]!)
236+
const max = parseInt(parts[1]!)
237+
isMatch = data.response.statusCode >= min && data.response.statusCode <= max
238+
} else {
239+
isMatch = data.response.statusCode.toString() === value
240+
}
241+
242+
if (not && isMatch) {
243+
// Negated value matches → reject immediately
244+
return false
245+
}
246+
if (!not && isMatch) {
247+
// Normal value matches → mark as matched
248+
matched = true;
249+
}
250+
}
251+
return matched || values.every(v => v[0] === '-')
252+
})
253+
}
214254
215255
return result
216256
})
@@ -318,6 +358,9 @@ const activeFiltersCount = computed(() => {
318358
if (filter.response.headers.checkbox && filter.response.headers.value.length > 1) {
319359
counter++;
320360
}
361+
if (filter.response.statusCode.checkbox && filter.response.statusCode.value.length > 0) {
362+
counter++
363+
}
321364
if (filter.clientIP.checkbox && filter.clientIP.value.length > 0) {
322365
counter++
323366
}
@@ -486,6 +529,28 @@ function cidrMatch(ip: string, cidr: string): boolean {
486529
487530
return (ipToInt(ip) & mask) === (ipToInt(range!) & mask);
488531
}
532+
function mergeDeep<T>(target: T, source: Partial<T>): T {
533+
for (const key in source) {
534+
const sourceValue = source[key];
535+
const targetValue = target[key];
536+
537+
if (
538+
sourceValue &&
539+
typeof sourceValue === 'object' &&
540+
!Array.isArray(sourceValue) &&
541+
targetValue &&
542+
typeof targetValue === 'object' &&
543+
!Array.isArray(targetValue)
544+
) {
545+
// Nested object: recurse
546+
mergeDeep(targetValue, sourceValue);
547+
} else if (sourceValue !== undefined) {
548+
// Primitive or array: overwrite
549+
target[key] = sourceValue as any;
550+
}
551+
}
552+
return target;
553+
}
489554
</script>
490555

491556
<template>
@@ -651,6 +716,23 @@ function cidrMatch(ip: string, cidr: string): boolean {
651716
</div>
652717
</div>
653718

719+
<!-- Response Status Code -->
720+
<div class="row mb-3">
721+
<div class="col-4">
722+
<div class="form-check" data-bs-toggle="tooltip" data-bs-delay='{"show": 200, "hide": 100}' :title="filter.response.statusCode.title" data-bs-offset="[-150, 0]" data-bs-html="true">
723+
<input class="form-check-input" type="checkbox" v-model="filter.response.statusCode.checkbox" id="statusCode">
724+
<label class="form-check-label" for="statusCode">Status Code</label>
725+
</div>
726+
</div>
727+
<div class="col" v-show="filter.response.statusCode.checkbox">
728+
<div class="col ps-0 pe-1" data-bs-toggle="tooltip" data-bs-delay='{"show": 200, "hide": 100}' :title="filter.response.statusCode.title" data-bs-html="true">
729+
<div class="row me-0">
730+
<input type="text" class="form-control form-control-sm" id="statusCode" v-model="filter.response.statusCode.value">
731+
</div>
732+
</div>
733+
</div>
734+
</div>
735+
654736
<!-- Response Headers -->
655737
<div class="row" :class="{ 'mb-3': responseHeaderErrors.length == 0 }">
656738
<div class="col-4">

0 commit comments

Comments
 (0)