Skip to content

Commit 13a3f2b

Browse files
committed
add client IP to HTTP log
add client IP to filter HTTP log
1 parent 628d107 commit 13a3f2b

File tree

6 files changed

+146
-20
lines changed

6 files changed

+146
-20
lines changed

api/handler_events_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,9 @@ func TestHandler_Events(t *testing.T) {
123123
},
124124
"url": "http://localhost/foo",
125125
},
126-
"response": map[string]interface{}{"body": "", "size": float64(0), "statusCode": float64(0)}},
126+
"response": map[string]interface{}{"body": "", "size": float64(0), "statusCode": float64(0)},
127+
"clientIP": "192.0.2.1",
128+
},
127129
m[0]["data"])
128130
}))
129131
},
@@ -174,7 +176,9 @@ func TestHandler_Events(t *testing.T) {
174176
},
175177
"url": "http://localhost/foo",
176178
},
177-
"response": map[string]interface{}{"body": "", "size": float64(0), "statusCode": float64(0)}},
179+
"response": map[string]interface{}{"body": "", "size": float64(0), "statusCode": float64(0)},
180+
"clientIP": "192.0.2.1",
181+
},
178182
m[0]["data"])
179183
}))
180184
},

examples/mokapi/services_http.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,8 @@ export let events = [
642642
})
643643
]
644644
}
645-
]
645+
],
646+
clientIP: '127.0.0.1'
646647
},
647648
},
648649
{
@@ -691,7 +692,8 @@ export let events = [
691692
message: 'An example script error message'
692693
}
693694
}
694-
]
695+
],
696+
clientIP: '127.0.0.1'
695697
}
696698
},
697699
{
@@ -735,7 +737,8 @@ export let events = [
735737
event: "http"
736738
}
737739
}
738-
]
740+
],
741+
clientIP: '127.0.0.1'
739742
}
740743
},
741744
{
@@ -773,7 +776,8 @@ export let events = [
773776
event: "http"
774777
}
775778
}
776-
]
779+
],
780+
clientIP: '127.0.0.1'
777781
}
778782
},
779783
{
@@ -819,6 +823,7 @@ export let events = [
819823
size: 512
820824
},
821825
duration: 133,
826+
clientIP: '192.0.1.127'
822827
}
823828
}
824829
]

webui/src/assets/dashboard.css

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,30 @@
130130
border-bottom: 4px solid;
131131
margin-bottom: -3px;
132132
border-bottom-color: var(--dashboard-nav-border-active);
133+
}
134+
135+
.tooltip .tooltip-inner {
136+
text-align: left;
137+
max-width: 500px;
138+
white-space: normal;
139+
}
140+
141+
[data-theme="light"] .tooltip .tooltip-inner {
142+
background-color: #f8f9fa;
143+
color: #212529;
144+
border: 1px solid #ced4da;
145+
box-shadow: 0 0.25rem 0.5rem rgba(0,0,0,0.1);
146+
}
147+
148+
.tooltip.bs-tooltip-top .tooltip-arrow::before {
149+
border-top-color: #f8f9fa;
150+
}
151+
.tooltip.bs-tooltip-bottom .tooltip-arrow::before {
152+
border-bottom-color: #f8f9fa;
153+
}
154+
.tooltip.bs-tooltip-start .tooltip-arrow::before {
155+
border-left-color: #f8f9fa;
156+
}
157+
.tooltip.bs-tooltip-end .tooltip-arrow::before {
158+
border-right-color: #f8f9fa;
133159
}

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

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ const operationRoute = computed(() => {
3434
<template>
3535
<div class="card">
3636
<div class="card-body">
37-
<div class="row">
38-
<div class="col header">
37+
<div class="row mb-2">
38+
<div class="col-10 header">
3939
<p class="label">URL</p>
4040
<p>
4141
<span class="badge operation" :class="eventData.request.method.toLowerCase()">{{ eventData.request.method }}</span>
@@ -46,33 +46,37 @@ const operationRoute = computed(() => {
4646
<p class="label">Time</p>
4747
<p>{{ format(event.time) }}</p>
4848
</div>
49-
<div class="col" v-if="eventData.deprecated">
50-
<p class="label">Warning</p>
51-
<p><span class="bi bi-exclamation-triangle-fill yellow"></span> Deprecated</p>
52-
</div>
5349
</div>
54-
<div class="row">
55-
<div class="col">
50+
<div class="row mb-2">
51+
<div class="col-2">
5652
<p class="label">Status</p>
5753
<p>
5854
<span class="badge status-code" :class="getClassByStatusCode(eventData.response.statusCode.toString())">
5955
{{ formatStatusCode(eventData.response.statusCode.toString()) }}
6056
</span>
6157
</p>
6258
</div>
63-
<div class="col">
59+
<div class="col-2">
60+
<p class="label">Size</p>
61+
<p>{{ formatBytes(eventData.response.size) }}</p>
62+
</div>
63+
<div class="col-2">
6464
<p class="label">Duration</p>
6565
<p>{{ duration(eventData.duration) }}</p>
6666
</div>
67+
<div class="col-2">
68+
<p class="label">Client IP</p>
69+
<p>{{ eventData.clientIP }}</p>
70+
</div>
6771
<div class="col" v-if="operationRoute">
6872
<p class="label">Specification</p>
6973
<router-link :to="operationRoute">Operation</router-link>
7074
</div>
7175
</div>
72-
<div class="row">
76+
<div class="row" v-if="eventData.deprecated">
7377
<div class="col">
74-
<p class="label">Size</p>
75-
<p>{{ formatBytes(eventData.response.size) }}</p>
78+
<p class="label">Warning</p>
79+
<p><span class="bi bi-exclamation-triangle-fill yellow"></span> Deprecated</p>
7680
</div>
7781
</div>
7882
</div>

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

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useEvents } from '@/composables/events'
44
import { onUnmounted, computed, useTemplateRef, onMounted, reactive, watch, ref, type ComponentPublicInstance } from 'vue'
55
import { usePrettyDates } from '@/composables/usePrettyDate'
66
import { usePrettyHttp } from '@/composables/http'
7-
import { Modal } from 'bootstrap'
7+
import { Modal, Tooltip } from 'bootstrap'
88
import { useService } from '@/composables/services'
99
import RegexInput from '@/components/RegexInput.vue'
1010
@@ -48,10 +48,12 @@ interface Filter {
4848
response: {
4949
headers: FilterItem
5050
}
51+
clientIP: FilterItem
5152
}
5253
interface FilterItem {
5354
checkbox: boolean
5455
value: any
56+
title?: string
5557
}
5658
interface 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+
&bull; Multiple entries (comma-separated): 192.168.0.1,10.0.0.1<br>
75+
&bull; Negation: -127.0.0.1<br>
76+
&bull; CIDR notation: 192.168.0.0/24` }
7177
})
7278
7379
onMounted(() => {
@@ -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
8797
function 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
})
281326
function 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>

webui/src/types/http.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ declare interface HttpEventData {
4141
duration: number
4242
deprecated: boolean
4343
actions: Action[]
44+
clientIP: string
4445
}
4546

4647
declare interface HttpEventRequest {

0 commit comments

Comments
 (0)