259 lines
7.5 KiB
Vue
259 lines
7.5 KiB
Vue
<script setup lang="ts">
|
|
import { message, Upload } from 'ant-design-vue/es';
|
|
import { UploadRequestOption } from 'ant-design-vue/es/vc-upload/interface';
|
|
import { FileType } from 'ant-design-vue/es/upload/interface';
|
|
import { PageContainer } from 'antdv-pro-layout';
|
|
import DissectionTree from './components/DissectionTree.vue';
|
|
import DissectionDump from './components/DissectionDump.vue';
|
|
import PacketTable from './components/PacketTable.vue';
|
|
import { usePCAP, NO_SELECTION } from './hooks/usePCAP';
|
|
import { parseSizeFromFile } from '@/utils/parse-utils';
|
|
import { parseDateToStr } from '@/utils/date-utils';
|
|
import useI18n from '@/hooks/useI18n';
|
|
const { t } = useI18n();
|
|
const {
|
|
state,
|
|
handleSelectedTree,
|
|
handleSelectedFindSelection,
|
|
handleSelectedFrame,
|
|
handleScrollBottom,
|
|
handleFilterFrames,
|
|
handleLoadExample,
|
|
handleLoadFile,
|
|
} = usePCAP();
|
|
|
|
/**上传前检查或转换压缩 */
|
|
function fnBeforeUpload(file: FileType) {
|
|
const fileName = file.name;
|
|
const suff = fileName.substring(fileName.lastIndexOf('.'));
|
|
const allowList = ['.pcap', '.cap', '.pcapng', '.pcap0'];
|
|
if (!allowList.includes(suff)) {
|
|
const msg = `${t('components.UploadModal.onlyAllow')} ${allowList.join(
|
|
','
|
|
)}`;
|
|
message.error(msg, 3);
|
|
return Upload.LIST_IGNORE;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**表单上传文件 */
|
|
function fnUpload(up: UploadRequestOption) {
|
|
handleLoadFile(up.file as File);
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<PageContainer>
|
|
<a-card
|
|
:bordered="false"
|
|
:loading="!state.initialized"
|
|
:body-style="{ padding: '12px' }"
|
|
>
|
|
<!-- 插槽-卡片左侧侧 -->
|
|
<template #title>
|
|
<a-space :size="8" align="center">
|
|
<a-upload
|
|
name="file"
|
|
list-type="picture"
|
|
:max-count="1"
|
|
accept=".pcap,.cap,.pcapng,.pcap0"
|
|
:show-upload-list="false"
|
|
:before-upload="fnBeforeUpload"
|
|
:custom-request="fnUpload"
|
|
>
|
|
<a-button type="primary"> Upload </a-button>
|
|
</a-upload>
|
|
<a-button @click="handleLoadExample()">Example</a-button>
|
|
</a-space>
|
|
</template>
|
|
<!-- 插槽-卡片右侧 -->
|
|
<template #extra>
|
|
<a-space :size="8" align="center">
|
|
<a-tag color="green" v-show="!!state.currentFilter">
|
|
{{ state.currentFilter }}
|
|
</a-tag>
|
|
<span> Matched Frame: {{ state.totalFrames }} </span>
|
|
<!-- 包信息 -->
|
|
<a-popover
|
|
trigger="click"
|
|
placement="bottomLeft"
|
|
v-if="state.summary.filename"
|
|
>
|
|
<template #content>
|
|
<div class="summary">
|
|
<div class="summary-item">
|
|
<span>Type:</span>
|
|
<span>{{ state.summary.file_type }}</span>
|
|
</div>
|
|
<div class="summary-item">
|
|
<span>Size:</span>
|
|
<span>{{
|
|
parseSizeFromFile(state.summary.file_length)
|
|
}}</span>
|
|
</div>
|
|
<div class="summary-item">
|
|
<span>Encapsulation:</span>
|
|
<span>{{ state.summary.file_encap_type }}</span>
|
|
</div>
|
|
<div class="summary-item">
|
|
<span>Packets:</span>
|
|
<span>{{ state.summary.packet_count }}</span>
|
|
</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">
|
|
<span>Duration:</span>
|
|
<span>{{ Math.round(state.summary.elapsed_time) }}s</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<InfoCircleOutlined />
|
|
</a-popover>
|
|
</a-space>
|
|
</template>
|
|
|
|
<!-- 包数据表过滤 -->
|
|
<a-input-group compact>
|
|
<a-auto-complete
|
|
v-model:value="state.filter"
|
|
:options="[
|
|
{ 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)"
|
|
:allow-clear="true"
|
|
>
|
|
<a-input
|
|
placeholder="display filter, example: tcp"
|
|
@pressEnter="handleFilterFrames"
|
|
>
|
|
<template #prefix>
|
|
<FilterOutlined />
|
|
</template>
|
|
</a-input>
|
|
</a-auto-complete>
|
|
<a-button
|
|
type="primary"
|
|
html-type="submit"
|
|
style="width: 100px"
|
|
@click="handleFilterFrames"
|
|
>
|
|
Filter
|
|
</a-button>
|
|
</a-input-group>
|
|
<a-alert
|
|
:message="state.filterError"
|
|
type="error"
|
|
v-if="state.filterError != null"
|
|
/>
|
|
|
|
<!-- 包数据表 -->
|
|
<PacketTable
|
|
:columns="state.columns"
|
|
:data="state.packetFrames"
|
|
:selectedFrame="state.selectedFrame"
|
|
:onSelectedFrame="handleSelectedFrame"
|
|
:onScrollBottom="handleScrollBottom"
|
|
></PacketTable>
|
|
|
|
<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">
|
|
<!-- 帧数据 -->
|
|
<DissectionTree
|
|
id="root"
|
|
:select="handleSelectedTree"
|
|
:selected="state.selectedTree"
|
|
:tree="state.packetFrame.tree"
|
|
/>
|
|
</a-col>
|
|
<a-col :lg="12" :md="12" :xs="24" class="dump">
|
|
<!-- 报文数据 -->
|
|
<a-tabs
|
|
v-model:activeKey="state.selectedDataSourceIndex"
|
|
:tab-bar-gutter="16"
|
|
:tab-bar-style="{ marginBottom: '8px' }"
|
|
>
|
|
<a-tab-pane
|
|
:key="idx"
|
|
:tab="v.name"
|
|
v-for="(v, idx) in state.packetFrame.data_sources"
|
|
style="overflow: auto"
|
|
>
|
|
<DissectionDump
|
|
:base64="v.data"
|
|
:select="(pos:number)=>handleSelectedFindSelection(idx, pos)"
|
|
:selected="
|
|
idx === state.selectedTree.idx
|
|
? state.selectedTree
|
|
: NO_SELECTION
|
|
"
|
|
/>
|
|
</a-tab-pane>
|
|
</a-tabs>
|
|
</a-col>
|
|
</a-row>
|
|
</a-card>
|
|
</PageContainer>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.summary {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.summary-item > span:first-child {
|
|
font-weight: 600;
|
|
margin-right: 6px;
|
|
}
|
|
|
|
.tree {
|
|
font-size: 0.8125rem;
|
|
line-height: 1.5rem;
|
|
padding-bottom: 0.75rem;
|
|
padding-top: 0.75rem;
|
|
white-space: nowrap;
|
|
overflow-y: auto;
|
|
user-select: none;
|
|
height: 100%;
|
|
}
|
|
.tree > ul.tree {
|
|
min-height: 15rem;
|
|
}
|
|
|
|
.dump {
|
|
padding-bottom: 0.75rem;
|
|
padding-top: 0.75rem;
|
|
overflow-y: auto;
|
|
height: 100%;
|
|
}
|
|
.dump .ant-tabs-tabpane {
|
|
min-height: calc(15rem - 56px);
|
|
}
|
|
</style>
|