Skip to content

Commit 1fcefec

Browse files
committed
add filter feature to HTTP requests
1 parent 2ccbe75 commit 1fcefec

File tree

10 files changed

+260
-20
lines changed

10 files changed

+260
-20
lines changed

webui/src/assets/dashboard.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@
3232
margin-bottom: 0;
3333
color: var(--color-text);
3434
}
35+
.dashboard .card .card-body h6.card-title {
36+
font-size: 0.9rem;
37+
font-weight: 400;
38+
}
3539
.dashboard .card-text {
3640
font-size: 2.25rem;
3741
}

webui/src/assets/datatable.css

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ table.dataTable {
77
table-layout: fixed;
88
}
99
table.dataTable th {
10-
background-color: var(---datatable-background);
10+
background-color: var(--datatable-background);
1111
color: var(--datatable-header-color);
1212
}
1313
table.dataTable td {
@@ -17,8 +17,8 @@ table.dataTable td {
1717
table.dataTable th {
1818
padding: 3px 0 3px 12px;
1919
border-color: var(--datatable-border-color);
20-
border-top-width: 0px;
21-
border-bottom-width: 2px;
20+
border-top-width: 0;
21+
border-bottom-width: 3px;
2222
font-weight: 500;
2323
}
2424
table.dataTable td{

webui/src/assets/modal.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.modal-header {
22
border-color: var(--modal-header-border);
3-
padding-bottom: 15px;
4-
padding-top: 15px;
3+
padding-bottom: 10px;
4+
padding-top: 10px;
55
}
66
.modal-content {
77
color: var(--color-text);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup lang="ts">
22
import { useMetrics } from '@/composables/metrics';
33
import { usePrettyDates } from '@/composables/usePrettyDate';
4-
import { type PropType, computed, onMounted, ref, watch } from 'vue';
4+
import { type PropType, computed, onMounted, ref } from 'vue';
55
import { useRouter, useRoute } from '@/router';
66
77
const route = useRoute()

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ const route = useRoute()
7171
<http-response-card :service="service" :path="path" :operation="operation" />
7272
</div>
7373
<div class="card-group">
74-
<requests :service="service" :path="path.path" :method="operation.method.toUpperCase()" />
74+
<requests :service-name="service.name" :path="path.path" :method="operation.method.toUpperCase()" />
7575
</div>
7676
</div>
7777
</template>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ onUnmounted(() => {
7777
<http-operations-card :service="service" :path="path" />
7878
</div>
7979
<div class="card-group">
80-
<requests :service="service" :path="path.path" />
80+
<requests :service-name="service.name" :path="path.path" />
8181
</div>
8282
</div>
8383
</template>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ function endpointNotFoundMessage(msg: string | undefined) {
9494
<config-card :configs="service.configs" />
9595
</div>
9696
<div class="card-group">
97-
<requests :service="service" />
97+
<requests :service-name="service.name" />
9898
</div>
9999

100100
</div>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ onUnmounted(() => {
4444
<template>
4545
<div class="card" data-testid="http-service-list">
4646
<div class="card-body">
47-
<div class="card-title text-center">HTTP Services</div>
47+
<div class="card-title text-center">HTTP APIs</div>
4848
<table class="table dataTable selectable">
4949
<thead>
5050
<tr>

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

Lines changed: 245 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,64 @@
11
<script setup lang="ts">
22
import { useRouter } from 'vue-router'
33
import { useEvents } from '@/composables/events'
4-
import { type PropType, onUnmounted, computed } from 'vue'
4+
import { onUnmounted, computed, useTemplateRef, onMounted, reactive, watch, ref, } from 'vue'
55
import { usePrettyDates } from '@/composables/usePrettyDate'
66
import { usePrettyHttp } from '@/composables/http'
7+
import { Modal } from 'bootstrap'
8+
import { useService } from '@/composables/services'
79
810
const props = defineProps({
9-
service: { type: Object as PropType<HttpService> },
11+
serviceName: { type: String, required: false},
1012
path: { type: String, required: false},
1113
method: { type: String, required: false}
1214
})
1315
14-
const labels = []
15-
if (props.service){
16-
labels.push({ name: 'name', value: props.service!.name })
16+
const labels = ref<any[]>([])
17+
if (props.serviceName){
18+
labels.value.push({ name: 'name', value: props.serviceName })
1719
if (props.path){
18-
labels.push({ name: 'path', value: props.path })
20+
labels.value.push({ name: 'path', value: props.path })
1921
}
2022
if (props.method) {
21-
labels.push({ name: 'method', value: props.method })
23+
labels.value.push({ name: 'method', value: props.method })
2224
}
2325
}
2426
2527
const router = useRouter()
2628
const { fetch } = useEvents()
27-
const { events, close } = fetch('http', ...labels)
29+
const { fetchServices } = useService()
30+
const { events: data, close } = fetch('http', ...labels.value)
2831
const { format, duration } = usePrettyDates()
2932
const { formatStatusCode } = usePrettyHttp()
33+
const { services, close: closeServices } = fetchServices('http', true);
34+
const dialogRef = useTemplateRef('dialogRef')
35+
let dialog: Modal | undefined;
36+
type CheckboxFilter = 'Not' | 'Single' | 'Multi'
37+
interface Filter {
38+
service: FilterItem
39+
method: FilterItem & { custom?: string}
40+
}
41+
interface FilterItem {
42+
checkbox: boolean
43+
state: CheckboxFilter
44+
value: string[]
45+
}
46+
const filter = reactive<Filter>({
47+
service: { state: 'Not', checkbox: false, value: [] },
48+
method: { state: 'Not', checkbox: false, value: ['GET']}
49+
})
50+
51+
onMounted(() => {
52+
dialog = Modal.getOrCreateInstance(dialogRef.value!);
53+
const s = localStorage.getItem(`http-requests-${getFilterCacheKey()}`)
54+
console.log('load ' +props.serviceName)
55+
console.log(s)
56+
if (s && s !== '') {
57+
const saved = JSON.parse(s)
58+
Object.assign(filter, saved)
59+
console.log(filter)
60+
}
61+
})
3062
3163
function goToRequest(event: ServiceEvent){
3264
if (getSelection()?.toString()) {
@@ -42,6 +74,48 @@ function eventData(event: ServiceEvent): HttpEventData{
4274
return <HttpEventData>event.data
4375
}
4476
77+
watch(filter, () => {
78+
localStorage.setItem(`http-requests-${getFilterCacheKey()}`, JSON.stringify(filter))
79+
})
80+
81+
const events = computed<ServiceEvent[]>(() => {
82+
let result = data.value;;
83+
switch (filter.service.state) {
84+
case 'Single':
85+
result = result.filter(x => x.traits.name === filter.service.value[0]);
86+
break;
87+
case 'Multi':
88+
result = result.filter(x => x.traits.name && filter.service.value.includes(x.traits.name));
89+
}
90+
const custom = filter.method.custom?.split(' ');
91+
switch (filter.method.state) {
92+
case 'Single':
93+
if (filter.method.value[0] === 'CUSTOM') {
94+
result = result.filter(x => custom?.includes((x.data as HttpEventData).request.method));
95+
} else {
96+
result = result.filter(x => filter.method.value[0] === (x.data as HttpEventData).request.method);
97+
}
98+
break;
99+
case 'Multi':
100+
result = result.filter(x => {
101+
const method = (x.data as HttpEventData).request.method;
102+
for (const m of filter.method.value) {
103+
if (m === 'CUSTOM') {
104+
if (custom?.includes(method)) {
105+
return true;
106+
}
107+
}else {
108+
if (m === method) {
109+
return true;
110+
}
111+
}
112+
}
113+
return false;
114+
})
115+
}
116+
return result
117+
})
118+
45119
const hasDeprecatedRequests = computed(() => {
46120
if (!events.value) {
47121
return false
@@ -54,15 +128,120 @@ const hasDeprecatedRequests = computed(() => {
54128
return false
55129
})
56130
131+
const service = computed({
132+
get: function() {
133+
if (filter.service.state === 'Single') {
134+
if (filter.service.value?.length === 0) {
135+
return services.value?.[0]?.name
136+
} else {
137+
return filter.service.value[0]
138+
}
139+
}
140+
return filter.service.value
141+
},
142+
set: function(val: any) {
143+
if (filter.service.state === 'Single') {
144+
if (!val) {
145+
filter.service.value = []
146+
} else {
147+
filter.service.value = [val]
148+
}
149+
} else {
150+
filter.service.value = val
151+
}
152+
}
153+
})
154+
const methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS', 'TRACE', 'QUERY', 'CUSTOM']
155+
const method = computed({
156+
get: function() {
157+
if (filter.method.state === 'Single') {
158+
if (filter.method.value?.length === 0) {
159+
return methods[0]
160+
} else {
161+
return filter.method.value[0]
162+
}
163+
}
164+
return filter.method.value
165+
},
166+
set: function(val: any) {
167+
if (filter.method.state === 'Single') {
168+
if (!val) {
169+
filter.method.value = []
170+
} else {
171+
filter.method.value = [val]
172+
}
173+
} else {
174+
filter.method.value = val
175+
}
176+
}
177+
})
178+
57179
onUnmounted(() => {
58180
close()
181+
closeServices()
182+
})
183+
function showDialog() {
184+
dialog?.show()
185+
}
186+
function changeCheckbox(fi: FilterItem) {
187+
switch (fi.state) {
188+
case 'Not':
189+
fi.state = 'Single';
190+
fi.checkbox = true
191+
break;
192+
case 'Single':
193+
fi.state = 'Multi';
194+
fi.checkbox = true
195+
break;
196+
case 'Multi':
197+
fi.state = 'Not';
198+
fi.checkbox = false
199+
break;
200+
}
201+
}
202+
function getId(s: string) {
203+
return s.replaceAll(' ', '-').toLowerCase()
204+
}
205+
const activeFiltersCount = computed(() => {
206+
let counter = 0;
207+
if (filter.service.state !== 'Not') {
208+
counter++;
209+
}
210+
if (filter.method.state !== 'Not') {
211+
counter++;
212+
}
213+
return counter;
59214
})
215+
function getFilterCacheKey() {
216+
if (!props.serviceName) {
217+
return 'filter'
218+
}
219+
if (!props.path) {
220+
return 'filter-' + props.serviceName
221+
}
222+
return `filter-${props.serviceName}-${props.path}-${props.method}`
223+
}
60224
</script>
61225

62226
<template>
63227
<div class="card">
64228
<div class="card-body">
65-
<div class="card-title text-center">Recent Requests</div>
229+
<div class="row justify-content-end mb-1">
230+
<div class="col-4">
231+
<h6 class="card-title text-center">Recent Requests</h6>
232+
</div>
233+
<div class="col-4 d-flex justify-content-end">
234+
<button class="btn btn-outline-primary position-relative" style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .75rem;" @click="showDialog">
235+
<i class="bi bi-funnel"></i> Filter
236+
237+
<span v-if="activeFiltersCount > 0"
238+
class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
239+
{{ activeFiltersCount }}
240+
</span>
241+
</button>
242+
</div>
243+
</div>
244+
66245
<table class="table dataTable selectable" data-testid="requests">
67246
<thead>
68247
<tr>
@@ -95,10 +274,67 @@ onUnmounted(() => {
95274
</table>
96275
</div>
97276
</div>
277+
278+
<div class="modal fade" tabindex="-1" aria-hidden="true" ref="dialogRef">
279+
<div class="modal-dialog modal-dialog-scrollable">
280+
<div class="modal-content">
281+
<div class="modal-header">
282+
<h6 class="modal-title">Filter</h6>
283+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
284+
</div>
285+
<div class="modal-body">
286+
<div class="row mb-3">
287+
<div class="col">
288+
<div class="form-check">
289+
<input class="form-check-input" type="checkbox" v-model="filter.service.checkbox" id="service" @change="changeCheckbox(filter.service)">
290+
<label class="form-check-label" for="service">API Name</label>
291+
</div>
292+
</div>
293+
<div class="col">
294+
<select class="form-select form-select-sm" v-model="service" aria-label="Service" v-if="filter.service.state === 'Single'" id="service-single">
295+
<option v-for="service of services">{{ service.name }}</option>
296+
</select>
297+
<div class="form-check" v-for="s of services" v-if="filter.service.state === 'Multi'">
298+
<input class="form-check-input" name="service" :value="s.name" v-model="service" type="checkbox" :id="getId(s.name)">
299+
<label class="form-check-label" :for="getId(s.name)">{{ s.name }}</label>
300+
</div>
301+
</div>
302+
</div>
303+
304+
<div class="row mb-3">
305+
<div class="col">
306+
<div class="form-check">
307+
<input class="form-check-input" type="checkbox" v-model="filter.method.checkbox" id="method" @change="changeCheckbox(filter.method)">
308+
<label class="form-check-label" for="method">Method</label>
309+
</div>
310+
</div>
311+
<div class="col">
312+
<div class="row ms-0 me-0">
313+
<select class="form-select form-select-sm" v-model="method" aria-label="Method" v-if="filter.method.state === 'Single'" id="method-single">
314+
<option v-for="method of methods">{{ method }}</option>
315+
</select>
316+
<div class="form-check" v-for="m of methods" v-if="filter.method.state === 'Multi'">
317+
<input class="form-check-input" name="method" :value="m" v-model="method" type="checkbox" :id="getId(m)">
318+
<label class="form-check-label" :for="getId(m)">{{ m }}</label>
319+
</div>
320+
</div>
321+
<div class="row mt-2 me-1" v-if="filter.method.value.includes('CUSTOM')">
322+
<input type="text" class="form-control form-control-sm" id="method-custom" v-model="filter.method.custom" placeholder="LINK CONNECT...">
323+
</div>
324+
</div>
325+
</div>
326+
327+
</div>
328+
</div>
329+
</div>
330+
</div>
98331
</template>
99332

100333
<style scoped>
101334
.warning:empty {
102335
padding: 0;
103336
}
337+
.modal-dialog-scrollable .modal-body {
338+
min-height: calc(100vh - 200px); /* header + footer spacing */
339+
}
104340
</style>

0 commit comments

Comments
 (0)