feat: PCAP文件解析功能页面优化

This commit is contained in:
TsMask
2025-04-18 15:51:24 +08:00
parent 48f8f13bf3
commit 294212620e
2 changed files with 119 additions and 91 deletions

View File

@@ -31,11 +31,11 @@ type StateType = {
/**当前选中的帧编号 */ /**当前选中的帧编号 */
selectedFrame: number; selectedFrame: number;
/**当前选中的帧数据 */ /**当前选中的帧数据 */
selectedPacket: { tree: any[]; data_sources: any[] }; packetFrame: { tree: any[]; data_sources: any[] };
/**pcap包帧数据 */ /**pcap包帧数据 */
packetFrameData: Map<string, any> | null; packetFrameTreeMap: Map<string, any> | null;
/**当前选中的帧数据-空占位 */ /**当前选中的帧数据 */
selectedTreeEntry: typeof NO_SELECTION; selectedTree: typeof NO_SELECTION;
/**选择帧的Dump数据标签 */ /**选择帧的Dump数据标签 */
selectedDataSourceIndex: number; selectedDataSourceIndex: number;
/**处理完成状态 */ /**处理完成状态 */
@@ -69,11 +69,11 @@ export function usePCAP() {
filter: '', filter: '',
filterError: null, filterError: null,
currentFilter: '', currentFilter: '',
selectedFrame: 1, selectedFrame: 0,
/**当前选中的帧数据 */ /**当前选中的帧数据 */
selectedPacket: { tree: [], data_sources: [] }, packetFrame: { tree: [], data_sources: [] },
packetFrameData: null, // 注意Map 需要额外处理 packetFrameTreeMap: null, // 注意Map 需要额外处理
selectedTreeEntry: NO_SELECTION, // NO_SELECTION 需要定义 selectedTree: NO_SELECTION, // NO_SELECTION 需要定义
/**选择帧的Dump数据标签 */ /**选择帧的Dump数据标签 */
selectedDataSourceIndex: 0, selectedDataSourceIndex: 0,
/**处理完成状态 */ /**处理完成状态 */
@@ -91,9 +91,9 @@ export function usePCAP() {
state.nextPageNum = 1; state.nextPageNum = 1;
// 选择帧的数据 // 选择帧的数据
state.selectedFrame = 0; state.selectedFrame = 0;
state.selectedPacket = { tree: [], data_sources: [] }; state.packetFrame = { tree: [], data_sources: [] };
state.packetFrameData = null; state.packetFrameTreeMap = null;
state.selectedTreeEntry = NO_SELECTION; state.selectedTree = NO_SELECTION;
state.selectedDataSourceIndex = 0; state.selectedDataSourceIndex = 0;
} }
@@ -121,23 +121,23 @@ export function usePCAP() {
} }
/**帧数据点击选中 */ /**帧数据点击选中 */
function handleSelectedTreeEntry(e: any) { function handleSelectedTree(e: any) {
console.log('fnSelectedTreeEntry', e); // console.log('fnSelectedTree', e);
state.selectedTreeEntry = e; state.selectedTree = e;
} }
/**报文数据点击选中 */ /**报文数据点击选中 */
function handleSelectedFindSelection(src_idx: number, pos: number) { function handleSelectedFindSelection(src_idx: number, pos: number) {
console.log('fnSelectedFindSelection', pos); // console.log('fnSelectedFindSelection', pos);
if (state.packetFrameData == null) return; if (state.packetFrameTreeMap == null) return;
// find the smallest one // find the smallest one
let current = null; let current = null;
for (let [k, pp] of state.packetFrameData) { for (let [k, pp] of state.packetFrameTreeMap) {
if (pp.idx !== src_idx) continue; if (pp.idx !== src_idx) continue;
if (pos >= pp.start && pos <= pp.start + pp.length) { if (pos >= pp.start && pos <= pp.start + pp.length) {
if ( if (
current != null && current != null &&
state.packetFrameData.get(current).length > pp.length state.packetFrameTreeMap.get(current).length > pp.length
) { ) {
current = k; current = k;
} else { } else {
@@ -146,19 +146,19 @@ export function usePCAP() {
} }
} }
if (current != null) { if (current != null) {
state.selectedTreeEntry = state.packetFrameData.get(current); state.selectedTree = state.packetFrameTreeMap.get(current);
} }
} }
/**包数据表点击选中 */ /**包数据表点击选中 */
function handleSelectedFrame(no: number) { function handleSelectedFrame(no: number) {
console.log('fnSelectedFrame', no, state.totalFrames); // console.log('fnSelectedFrame', no, state.totalFrames);
state.selectedFrame = no; state.selectedFrame = no;
wk.send({ type: 'select', number: state.selectedFrame }); wk.send({ type: 'select', number: state.selectedFrame });
} }
/**包数据表滚动底部加载 */ /**包数据表滚动底部加载 */
function handleScrollBottom() { function handleScrollBottom() {
const totalFetched = state.packetFrames.length; const totalFetched = state.packetFrames.length;
console.log('fnScrollBottom', totalFetched); // console.log('fnScrollBottom', totalFetched);
if (!state.nextPageLoad && totalFetched < state.totalFrames) { if (!state.nextPageLoad && totalFetched < state.totalFrames) {
state.nextPageLoad = true; state.nextPageLoad = true;
state.nextPageNum++; state.nextPageNum++;
@@ -167,7 +167,7 @@ export function usePCAP() {
} }
/**包数据表过滤 */ /**包数据表过滤 */
function handleFilterFrames() { function handleFilterFrames() {
console.log('fnFilterFinish', state.filter); // console.log('fnFilterFinish', state.filter);
wk.send({ type: 'check-filter', filter: state.filter }); wk.send({ type: 'check-filter', filter: state.filter });
} }
/**包数据表加载 */ /**包数据表加载 */
@@ -254,9 +254,9 @@ export function usePCAP() {
} }
break; break;
case 'selected': case 'selected':
state.selectedPacket = res.data; state.packetFrame = res.data;
state.packetFrameData = parseFrameData('root', res.data); state.packetFrameTreeMap = parseFrameData('root', res.data);
state.selectedTreeEntry = NO_SELECTION; state.selectedTree = NO_SELECTION;
state.selectedDataSourceIndex = 0; state.selectedDataSourceIndex = 0;
break; break;
case 'processed': case 'processed':
@@ -306,7 +306,7 @@ export function usePCAP() {
return { return {
state, state,
handleSelectedTreeEntry, handleSelectedTree,
handleSelectedFindSelection, handleSelectedFindSelection,
handleSelectedFrame, handleSelectedFrame,
handleScrollBottom, handleScrollBottom,

View File

@@ -8,11 +8,12 @@ import DissectionDump from './components/DissectionDump.vue';
import PacketTable from './components/PacketTable.vue'; import PacketTable from './components/PacketTable.vue';
import { usePCAP, NO_SELECTION } from './hooks/usePCAP'; import { usePCAP, NO_SELECTION } from './hooks/usePCAP';
import { parseSizeFromFile } from '@/utils/parse-utils'; import { parseSizeFromFile } from '@/utils/parse-utils';
import { parseDateToStr } from '@/utils/date-utils';
import useI18n from '@/hooks/useI18n'; import useI18n from '@/hooks/useI18n';
const { t } = useI18n(); const { t } = useI18n();
const { const {
state, state,
handleSelectedTreeEntry, handleSelectedTree,
handleSelectedFindSelection, handleSelectedFindSelection,
handleSelectedFrame, handleSelectedFrame,
handleScrollBottom, handleScrollBottom,
@@ -49,8 +50,9 @@ function fnUpload(up: UploadRequestOption) {
:loading="!state.initialized" :loading="!state.initialized"
:body-style="{ padding: '12px' }" :body-style="{ padding: '12px' }"
> >
<div class="toolbar"> <!-- 插槽-卡片左侧侧 -->
<a-space :size="8" class="toolbar-oper"> <template #title>
<a-space :size="8" align="center">
<a-upload <a-upload
name="file" name="file"
list-type="picture" list-type="picture"
@@ -64,14 +66,14 @@ function fnUpload(up: UploadRequestOption) {
</a-upload> </a-upload>
<a-button @click="handleLoadExample()">Example</a-button> <a-button @click="handleLoadExample()">Example</a-button>
</a-space> </a-space>
</template>
<div class="toolbar-info"> <!-- 插槽-卡片右侧 -->
<template #extra>
<a-space :size="8" align="center">
<a-tag color="green" v-show="!!state.currentFilter"> <a-tag color="green" v-show="!!state.currentFilter">
{{ state.currentFilter }} {{ state.currentFilter }}
</a-tag> </a-tag>
<span> Matched Frame: {{ state.totalFrames }} </span> <span> Matched Frame: {{ state.totalFrames }} </span>
</div>
<!-- 包信息 --> <!-- 包信息 -->
<a-popover <a-popover
trigger="click" trigger="click"
@@ -86,7 +88,9 @@ function fnUpload(up: UploadRequestOption) {
</div> </div>
<div class="summary-item"> <div class="summary-item">
<span>Size:</span> <span>Size:</span>
<span>{{ parseSizeFromFile(state.summary.file_length) }}</span> <span>{{
parseSizeFromFile(state.summary.file_length)
}}</span>
</div> </div>
<div class="summary-item"> <div class="summary-item">
<span>Encapsulation:</span> <span>Encapsulation:</span>
@@ -96,6 +100,28 @@ function fnUpload(up: UploadRequestOption) {
<span>Packets:</span> <span>Packets:</span>
<span>{{ state.summary.packet_count }}</span> <span>{{ state.summary.packet_count }}</span>
</div> </div>
<div class="summary-item">
<span>Start Time:</span>
<span>
{{
parseDateToStr(
state.summary.start_time * 1000,
'YYYY-MM-DD HH:mm:ss.SSS'
)
}}
</span>
</div>
<div class="summary-item">
<span>Stop Time:</span>
<span>
{{
parseDateToStr(
state.summary.stop_time * 1000,
'YYYY-MM-DD HH:mm:ss.SSS'
)
}}
</span>
</div>
<div class="summary-item"> <div class="summary-item">
<span>Duration:</span> <span>Duration:</span>
<span>{{ Math.round(state.summary.elapsed_time) }}s</span> <span>{{ Math.round(state.summary.elapsed_time) }}s</span>
@@ -104,21 +130,30 @@ function fnUpload(up: UploadRequestOption) {
</template> </template>
<InfoCircleOutlined /> <InfoCircleOutlined />
</a-popover> </a-popover>
</div> </a-space>
</template>
<!-- 包数据表过滤 --> <!-- 包数据表过滤 -->
<a-input-group compact> <a-input-group compact>
<a-input <a-auto-complete
v-model:value="state.filter" v-model:value="state.filter"
placeholder="display filter, example: tcp" :options="[
:allow-clear="true" { value: 'http || tcp.port == 33030 || http2' },
{ value: 'ip.src== 172.17.0.19 && ip.dst == 172.17.0.77' },
{ value: 'sip || ngap' },
]"
style="width: calc(100% - 100px)" style="width: calc(100% - 100px)"
:allow-clear="true"
>
<a-input
placeholder="display filter, example: tcp"
@pressEnter="handleFilterFrames" @pressEnter="handleFilterFrames"
> >
<template #prefix> <template #prefix>
<FilterOutlined /> <FilterOutlined />
</template> </template>
</a-input> </a-input>
</a-auto-complete>
<a-button <a-button
type="primary" type="primary"
html-type="submit" html-type="submit"
@@ -143,14 +178,18 @@ function fnUpload(up: UploadRequestOption) {
:onScrollBottom="handleScrollBottom" :onScrollBottom="handleScrollBottom"
></PacketTable> ></PacketTable>
<a-row> <a-row
:gutter="16"
style="border: 2px rgb(217 217 217) solid; border-radius: 6px"
v-show="state.selectedFrame > 0"
>
<a-col :lg="12" :md="12" :xs="24" class="tree"> <a-col :lg="12" :md="12" :xs="24" class="tree">
<!-- 帧数据 --> <!-- 帧数据 -->
<DissectionTree <DissectionTree
id="root" id="root"
:select="handleSelectedTreeEntry" :select="handleSelectedTree"
:selected="state.selectedTreeEntry" :selected="state.selectedTree"
:tree="state.selectedPacket.tree" :tree="state.packetFrame.tree"
/> />
</a-col> </a-col>
<a-col :lg="12" :md="12" :xs="24" class="dump"> <a-col :lg="12" :md="12" :xs="24" class="dump">
@@ -163,15 +202,15 @@ function fnUpload(up: UploadRequestOption) {
<a-tab-pane <a-tab-pane
:key="idx" :key="idx"
:tab="v.name" :tab="v.name"
v-for="(v, idx) in state.selectedPacket.data_sources" v-for="(v, idx) in state.packetFrame.data_sources"
style="overflow: auto" style="overflow: auto"
> >
<DissectionDump <DissectionDump
:base64="v.data" :base64="v.data"
:select="(pos:number)=>handleSelectedFindSelection(idx, pos)" :select="(pos:number)=>handleSelectedFindSelection(idx, pos)"
:selected=" :selected="
idx === state.selectedTreeEntry.idx idx === state.selectedTree.idx
? state.selectedTreeEntry ? state.selectedTree
: NO_SELECTION : NO_SELECTION
" "
/> />
@@ -184,17 +223,6 @@ function fnUpload(up: UploadRequestOption) {
</template> </template>
<style scoped> <style scoped>
.toolbar {
display: flex;
align-items: center;
margin-bottom: 12px;
}
.toolbar-info {
flex: 1;
text-align: right;
padding-right: 8px;
}
.summary { .summary {
display: flex; display: flex;
flex-direction: column; flex-direction: column;