Files
fe.ems.vue3/src/views/traceManage/tshark/index.vue
2024-10-28 11:02:39 +08:00

231 lines
6.3 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 useI18n from '@/hooks/useI18n';
const { t } = useI18n();
const {
state,
handleSelectedTreeEntry,
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' }"
>
<div class="toolbar">
<a-space :size="8" class="toolbar-oper">
<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>
<div class="toolbar-info">
<a-tag color="green" v-show="!!state.currentFilter">
{{ state.currentFilter }}
</a-tag>
<span> Matched Frame: {{ state.totalFrames }} </span>
</div>
<!-- 包信息 -->
<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>Duration:</span>
<span>{{ Math.round(state.summary.elapsed_time) }}s</span>
</div>
</div>
</template>
<InfoCircleOutlined />
</a-popover>
</div>
<!-- 包数据表过滤 -->
<a-input-group compact>
<a-input
v-model:value="state.filter"
placeholder="display filter, example: tcp"
:allow-clear="true"
style="width: calc(100% - 100px)"
@pressEnter="handleFilterFrames"
>
<template #prefix>
<FilterOutlined />
</template>
</a-input>
<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>
<a-col :lg="12" :md="12" :xs="24" class="tree">
<!-- 帧数据 -->
<DissectionTree
id="root"
:select="handleSelectedTreeEntry"
:selected="state.selectedTreeEntry"
:tree="state.selectedPacket.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.selectedPacket.data_sources"
style="overflow: auto"
>
<DissectionDump
:base64="v.data"
:select="(pos:number)=>handleSelectedFindSelection(idx, pos)"
:selected="
idx === state.selectedTreeEntry.idx
? state.selectedTreeEntry
: NO_SELECTION
"
/>
</a-tab-pane>
</a-tabs>
</a-col>
</a-row>
</a-card>
</PageContainer>
</template>
<style scoped>
.toolbar {
display: flex;
align-items: center;
margin-bottom: 12px;
}
.toolbar-info {
flex: 1;
text-align: right;
padding-right: 8px;
}
.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>