Merge branch 'main' into multi-tenant
This commit is contained in:
@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC"
|
|||||||
VITE_APP_CODE = "OMC"
|
VITE_APP_CODE = "OMC"
|
||||||
|
|
||||||
# 应用版本
|
# 应用版本
|
||||||
VITE_APP_VERSION = "2.240920"
|
VITE_APP_VERSION = "2.240927"
|
||||||
|
|
||||||
# 接口基础URL地址-不带/后缀
|
# 接口基础URL地址-不带/后缀
|
||||||
VITE_API_BASE_URL = "/omc-api"
|
VITE_API_BASE_URL = "/omc-api"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC"
|
|||||||
VITE_APP_CODE = "OMC"
|
VITE_APP_CODE = "OMC"
|
||||||
|
|
||||||
# 应用版本
|
# 应用版本
|
||||||
VITE_APP_VERSION = "2.240920"
|
VITE_APP_VERSION = "2.240927"
|
||||||
|
|
||||||
# 接口基础URL地址-不带/后缀
|
# 接口基础URL地址-不带/后缀
|
||||||
VITE_API_BASE_URL = "/omc-api"
|
VITE_API_BASE_URL = "/omc-api"
|
||||||
|
|||||||
@@ -8,14 +8,8 @@
|
|||||||
## 测试环境
|
## 测试环境
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Jenkins: http://192.168.2.166:3185/
|
|
||||||
Nginx: http://192.168.2.166:3188/#/index
|
Nginx: http://192.168.2.166:3188/#/index
|
||||||
后端暴露端口: http://192.168.2.166:33030
|
后端暴露端口: http://192.168.2.166:33030
|
||||||
|
|
||||||
|
|
||||||
新网管:192.168.5.13
|
|
||||||
旧网管:192.168.5.14
|
|
||||||
登录账户:manager/manager
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 程序命令
|
## 程序命令
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// load the Wiregasm library
|
// load the Wiregasm library
|
||||||
importScripts(
|
importScripts(
|
||||||
'/wiregasm/wiregasm_new.js',
|
'/wiregasm/wiregasm_new.js', // self-compilation es5
|
||||||
'/wiregasm/wiregasm.js'
|
'/wiregasm/wiregasm_load.js'
|
||||||
// 'https://cdn.jsdelivr.net/npm/@goodtools/wiregasm/dist/wiregasm.js'
|
// 'https://cdn.jsdelivr.net/npm/@goodtools/wiregasm/dist/wiregasm.js'
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -63,6 +63,9 @@ function replacer(key, value) {
|
|||||||
this.onmessage = ev => {
|
this.onmessage = ev => {
|
||||||
const data = ev.data;
|
const data = ev.data;
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
|
case 'close':
|
||||||
|
wg.destroy();
|
||||||
|
break;
|
||||||
case 'columns':
|
case 'columns':
|
||||||
const columns = wg.columns();
|
const columns = wg.columns();
|
||||||
if (Array.isArray(columns)) {
|
if (Array.isArray(columns)) {
|
||||||
|
|||||||
64
src/api/trace/packet.ts
Normal file
64
src/api/trace/packet.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { request } from '@/plugins/http-fetch';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 信令跟踪网卡设备列表
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function packetDevices() {
|
||||||
|
return request({
|
||||||
|
url: '/trace/packet/devices',
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 信令跟踪开始
|
||||||
|
* @param data 对象
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function packetStart(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: '/trace/packet/start',
|
||||||
|
method: 'post',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 信令跟踪结束
|
||||||
|
* @param data 对象
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function packetStop(taskNo: string) {
|
||||||
|
return request({
|
||||||
|
url: '/trace/packet/stop',
|
||||||
|
method: 'post',
|
||||||
|
data: { taskNo },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 信令跟踪过滤
|
||||||
|
* @param data 对象
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function packetFilter(taskNo: string, expr: string) {
|
||||||
|
return request({
|
||||||
|
url: '/trace/packet/filter',
|
||||||
|
method: 'put',
|
||||||
|
data: { taskNo, expr },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 信令跟踪续期保活
|
||||||
|
* @param data 对象
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function packetKeep(taskNo: string, duration: number = 120) {
|
||||||
|
return request({
|
||||||
|
url: '/trace/packet/keep-alive',
|
||||||
|
method: 'put',
|
||||||
|
data: { taskNo, duration },
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -65,6 +65,21 @@ export async function delTraceTask(ids: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跟踪任务文件
|
||||||
|
* @param query 对象
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function filePullTask(traceId: string) {
|
||||||
|
return request({
|
||||||
|
url: '/trace/task/filePull',
|
||||||
|
method: 'get',
|
||||||
|
params: { traceId },
|
||||||
|
responseType: 'blob',
|
||||||
|
timeout: 60_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取网元跟踪接口列表
|
* 获取网元跟踪接口列表
|
||||||
* @returns object
|
* @returns object
|
||||||
|
|||||||
@@ -740,11 +740,11 @@ export default {
|
|||||||
upgrade: "Upgrade To New Version",
|
upgrade: "Upgrade To New Version",
|
||||||
upgradeTip: "Confirmed to upgrade to the new version?",
|
upgradeTip: "Confirmed to upgrade to the new version?",
|
||||||
upgradeTipEmpty: "There are currently no new versions available",
|
upgradeTipEmpty: "There are currently no new versions available",
|
||||||
upgradeTipEqual: "Current version is the same as the new version",
|
upgradeTipEqual: "The current version is the same as the new version, confirmed to update?",
|
||||||
rollback: 'Switch to previous version',
|
rollback: 'Switch to previous version',
|
||||||
rollbackTip: "Confirm switching to the previous version?",
|
rollbackTip: "Confirm switching to the previous version?",
|
||||||
rollbackTipEmpty: "There is currently no previous version available",
|
rollbackTipEmpty: "There is currently no previous version available",
|
||||||
rollbackTipEqual: 'The current version is the same as the previous version',
|
rollbackTipEqual: 'The current version is the same as the previous version, are you sure you want to make the switch?',
|
||||||
version: "Current Version",
|
version: "Current Version",
|
||||||
preVersion: "Previous Version",
|
preVersion: "Previous Version",
|
||||||
newVersion: "New Version",
|
newVersion: "New Version",
|
||||||
@@ -1135,9 +1135,7 @@ export default {
|
|||||||
stopNotRun: "{title} not running",
|
stopNotRun: "{title} not running",
|
||||||
},
|
},
|
||||||
task: {
|
task: {
|
||||||
neTypePlease: 'Query network element type',
|
traceId: 'Tracing No',
|
||||||
neType: 'NE Type',
|
|
||||||
neID: 'NE ID',
|
|
||||||
trackType: 'Tracing Type',
|
trackType: 'Tracing Type',
|
||||||
trackTypePlease: 'Please select a tracing type',
|
trackTypePlease: 'Please select a tracing type',
|
||||||
creater: 'Created by',
|
creater: 'Created by',
|
||||||
@@ -1174,6 +1172,7 @@ export default {
|
|||||||
delTaskTip: 'Are you sure to delete the data item with record ID {id} ?',
|
delTaskTip: 'Are you sure to delete the data item with record ID {id} ?',
|
||||||
stopTask: 'Successful cessation of tasks {id}',
|
stopTask: 'Successful cessation of tasks {id}',
|
||||||
stopTaskTip: 'Confirm stopping the task with record ID {id} ?',
|
stopTaskTip: 'Confirm stopping the task with record ID {id} ?',
|
||||||
|
pcapView: "Tracking Data Analysis",
|
||||||
traceFile: "Tracking File",
|
traceFile: "Tracking File",
|
||||||
errMsg: "Error Message",
|
errMsg: "Error Message",
|
||||||
imsiORmsisdn: "imsi or msisdn is null, cannot start task",
|
imsiORmsisdn: "imsi or msisdn is null, cannot start task",
|
||||||
|
|||||||
@@ -740,11 +740,11 @@ export default {
|
|||||||
upgrade: "升级到新版本",
|
upgrade: "升级到新版本",
|
||||||
upgradeTip: "确认要升级到新版本吗?",
|
upgradeTip: "确认要升级到新版本吗?",
|
||||||
upgradeTipEmpty: "当前没有可用的新版本",
|
upgradeTipEmpty: "当前没有可用的新版本",
|
||||||
upgradeTipEqual: "当前版本与新版本相同",
|
upgradeTipEqual: "当前版本与新版本相同,确认要进行更新吗?",
|
||||||
rollback: '切换到上一个版本',
|
rollback: '切换到上一个版本',
|
||||||
rollbackTip: "确认切换到上一个版本吗?",
|
rollbackTip: "确认切换到上一个版本吗?",
|
||||||
rollbackTipEmpty: "目前没有可用的上一个版本",
|
rollbackTipEmpty: "目前没有可用的上一个版本",
|
||||||
rollbackTipEqual: '当前版本与之前版本相同',
|
rollbackTipEqual: '当前版本与之前版本相同,确认要进行切换吗?',
|
||||||
version: "当前版本",
|
version: "当前版本",
|
||||||
preVersion: "上一个版本",
|
preVersion: "上一个版本",
|
||||||
newVersion: "新版本",
|
newVersion: "新版本",
|
||||||
@@ -1135,9 +1135,7 @@ export default {
|
|||||||
stopNotRun: "{title} 任务未运行",
|
stopNotRun: "{title} 任务未运行",
|
||||||
},
|
},
|
||||||
task: {
|
task: {
|
||||||
neTypePlease: '请选择网元类型',
|
traceId: '跟踪编号',
|
||||||
neType: '网元类型',
|
|
||||||
neID: '网元内部标识',
|
|
||||||
trackType: '跟踪类型',
|
trackType: '跟踪类型',
|
||||||
trackTypePlease: '请选择跟踪类型',
|
trackTypePlease: '请选择跟踪类型',
|
||||||
creater: '创建人',
|
creater: '创建人',
|
||||||
@@ -1174,6 +1172,7 @@ export default {
|
|||||||
delTaskTip: '确认删除记录编号为 {id} 的数据项?',
|
delTaskTip: '确认删除记录编号为 {id} 的数据项?',
|
||||||
stopTask: '成功停止任务 {id}',
|
stopTask: '成功停止任务 {id}',
|
||||||
stopTaskTip: '确认停止记录编号为 {id} 的任务?',
|
stopTaskTip: '确认停止记录编号为 {id} 的任务?',
|
||||||
|
pcapView: "跟踪数据分析",
|
||||||
traceFile: "跟踪文件",
|
traceFile: "跟踪文件",
|
||||||
errMsg: "错误信息",
|
errMsg: "错误信息",
|
||||||
imsiORmsisdn: "imsi 或 msisdn 是空值,不能开始任务",
|
imsiORmsisdn: "imsi 或 msisdn 是空值,不能开始任务",
|
||||||
|
|||||||
@@ -37,7 +37,10 @@ export function parseStrLineToHump(str: string): string {
|
|||||||
*/
|
*/
|
||||||
export function parseStrHumpToLine(str: string): string {
|
export function parseStrHumpToLine(str: string): string {
|
||||||
if (!str) return str;
|
if (!str) return str;
|
||||||
return str.replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, '');
|
return str
|
||||||
|
.replace(/([A-Z])/g, '_$1')
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/^_/, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,9 +66,6 @@ export function parseObjHumpToLine(obj: any): any {
|
|||||||
});
|
});
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
if (typeof obj === 'string') {
|
|
||||||
return parseStrHumpToLine(obj);
|
|
||||||
}
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,9 +92,6 @@ export function parseObjLineToHump(obj: any): any {
|
|||||||
});
|
});
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
if (typeof obj === 'string') {
|
|
||||||
return parseStrLineToHump(obj);
|
|
||||||
}
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -174,12 +174,12 @@ let tableColumns: ColumnsType = [
|
|||||||
dataIndex: 'cdrJSON',
|
dataIndex: 'cdrJSON',
|
||||||
key: 'cause',
|
key: 'cause',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
width: 120,
|
width: 200,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('views.dashboard.cdr.time'),
|
title: t('views.dashboard.cdr.time'),
|
||||||
dataIndex: 'cdrJSON',
|
dataIndex: 'cdrJSON',
|
||||||
align: 'center',
|
align: 'left',
|
||||||
width: 150,
|
width: 150,
|
||||||
customRender(opt) {
|
customRender(opt) {
|
||||||
const cdrJSON = opt.value;
|
const cdrJSON = opt.value;
|
||||||
@@ -713,6 +713,7 @@ onBeforeUnmount(() => {
|
|||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'cause'">
|
<template v-if="column.key === 'cause'">
|
||||||
<span v-if="record.cdrJSON.result === 0">
|
<span v-if="record.cdrJSON.result === 0">
|
||||||
|
{{ t('views.dashboard.cdr.resultFail') }},
|
||||||
<DictTag
|
<DictTag
|
||||||
:options="dict.cdrCauseCode"
|
:options="dict.cdrCauseCode"
|
||||||
:value="record.cdrJSON.cause"
|
:value="record.cdrJSON.cause"
|
||||||
@@ -774,6 +775,7 @@ onBeforeUnmount(() => {
|
|||||||
<div>
|
<div>
|
||||||
<span>{{ t('views.dashboard.cdr.result') }}: </span>
|
<span>{{ t('views.dashboard.cdr.result') }}: </span>
|
||||||
<span v-if="record.cdrJSON.result === 0">
|
<span v-if="record.cdrJSON.result === 0">
|
||||||
|
{{ t('views.dashboard.cdr.resultFail') }},
|
||||||
<DictTag
|
<DictTag
|
||||||
:options="dict.cdrCauseCode"
|
:options="dict.cdrCauseCode"
|
||||||
:value="record.cdrJSON.cause"
|
:value="record.cdrJSON.cause"
|
||||||
|
|||||||
@@ -237,7 +237,9 @@ function fnModalEditOk(from: Record<string, any>) {
|
|||||||
item.neName = from.neName;
|
item.neName = from.neName;
|
||||||
item.ip = from.ip;
|
item.ip = from.ip;
|
||||||
item.port = from.port;
|
item.port = from.port;
|
||||||
|
if (item.status !== '2') {
|
||||||
item.status = res.data.online ? '1' : '0';
|
item.status = res.data.online ? '1' : '0';
|
||||||
|
}
|
||||||
Object.assign(item.serverState, res.data);
|
Object.assign(item.serverState, res.data);
|
||||||
const resouresUsage = parseResouresUsage(item.serverState);
|
const resouresUsage = parseResouresUsage(item.serverState);
|
||||||
Reflect.set(item, 'resoures', resouresUsage);
|
Reflect.set(item, 'resoures', resouresUsage);
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ function fnStepPrev() {
|
|||||||
title: t('common.tipTitle'),
|
title: t('common.tipTitle'),
|
||||||
content: t('views.ne.neQuickSetup.stepPrevTip'),
|
content: t('views.ne.neQuickSetup.stepPrevTip'),
|
||||||
onOk() {
|
onOk() {
|
||||||
fnRestStepState();
|
fnRestStepState(t);
|
||||||
fnToStepName('Start');
|
fnToStepName('Start');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,18 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Modal, TableColumnsType, message } from 'ant-design-vue/lib';
|
import { Modal, message } from 'ant-design-vue/lib';
|
||||||
import { defineAsyncComponent, onMounted, reactive, ref, toRaw } from 'vue';
|
import { defineAsyncComponent, onMounted, reactive, toRaw } from 'vue';
|
||||||
import { fnToStepName, stepState } from '../hooks/useStep';
|
import { fnToStepName, stepState } from '../hooks/useStep';
|
||||||
import useI18n from '@/hooks/useI18n';
|
import useI18n from '@/hooks/useI18n';
|
||||||
import { listNeVersion, operateNeVersion } from '@/api/ne/neVersion';
|
import { operateNeVersion } from '@/api/ne/neVersion';
|
||||||
import {
|
import {
|
||||||
RESULT_CODE_ERROR,
|
RESULT_CODE_ERROR,
|
||||||
RESULT_CODE_SUCCESS,
|
RESULT_CODE_SUCCESS,
|
||||||
} from '@/constants/result-constants';
|
} from '@/constants/result-constants';
|
||||||
import {
|
import { listNeSoftware, newNeVersion } from '@/api/ne/neSoftware';
|
||||||
addNeSoftware,
|
|
||||||
listNeSoftware,
|
|
||||||
newNeVersion,
|
|
||||||
} from '@/api/ne/neSoftware';
|
|
||||||
import { parseDateToStr } from '@/utils/date-utils';
|
import { parseDateToStr } from '@/utils/date-utils';
|
||||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ function fnStepEnd() {
|
|||||||
title: t('common.tipTitle'),
|
title: t('common.tipTitle'),
|
||||||
content: t('views.ne.neQuickSetup.licenseEndTip'),
|
content: t('views.ne.neQuickSetup.licenseEndTip'),
|
||||||
onOk() {
|
onOk() {
|
||||||
fnRestStepState();
|
fnRestStepState(t);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export function fnRestStepState(t?: any) {
|
|||||||
stepState.current = -1;
|
stepState.current = -1;
|
||||||
stepState.neHost = {};
|
stepState.neHost = {};
|
||||||
stepState.neInfo = {};
|
stepState.neInfo = {};
|
||||||
|
|
||||||
// 多语言翻译
|
// 多语言翻译
|
||||||
if (t) {
|
if (t) {
|
||||||
stepState.steps = [
|
stepState.steps = [
|
||||||
@@ -104,8 +105,5 @@ export function useStep({ t }: any) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
fnRestStepState(t);
|
|
||||||
});
|
|
||||||
return { currentComponent };
|
return { currentComponent };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
fnRestStepState(t);
|
||||||
fnReloadData();
|
fnReloadData();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -294,8 +294,7 @@ function fnRecordVersion(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (row.newVersion === row.version) {
|
if (row.newVersion === row.version) {
|
||||||
message.warning(t('views.ne.neVersion.upgradeTipEqual'), 3);
|
contentTip = t('views.ne.neVersion.upgradeTipEqual');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (action === 'rollback') {
|
if (action === 'rollback') {
|
||||||
@@ -305,8 +304,7 @@ function fnRecordVersion(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (row.preVersion === row.version) {
|
if (row.preVersion === row.version) {
|
||||||
message.warning(t('views.ne.neVersion.rollbackTipEqual'), 3);
|
contentTip = t('views.ne.neVersion.rollbackTipEqual');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ const modalStateFrom = Form.useForm(
|
|||||||
neType: [
|
neType: [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: t('views.traceManage.task.neTypePlease'),
|
message: t('views.ne.common.neTypePlease'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
expression: [
|
expression: [
|
||||||
@@ -466,14 +466,14 @@ onMounted(() => {
|
|||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :lg="6" :md="12" :xs="24">
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
:label="t('views.traceManage.task.neType')"
|
:label="t('views.ne.common.neType')"
|
||||||
name="neType "
|
name="neType "
|
||||||
>
|
>
|
||||||
<a-auto-complete
|
<a-auto-complete
|
||||||
v-model:value="queryParams.neType"
|
v-model:value="queryParams.neType"
|
||||||
:options="neCascaderOptions"
|
:options="neCascaderOptions"
|
||||||
allow-clear
|
allow-clear
|
||||||
:placeholder="t('views.traceManage.task.neTypePlease')"
|
:placeholder="t('views.ne.common.neTypePlease')"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
@@ -624,7 +624,7 @@ onMounted(() => {
|
|||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :lg="12" :md="12" :xs="24">
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
:label="t('views.traceManage.task.neType')"
|
:label="t('views.ne.common.neType')"
|
||||||
name="neType"
|
name="neType"
|
||||||
v-bind="modalStateFrom.validateInfos.neType"
|
v-bind="modalStateFrom.validateInfos.neType"
|
||||||
>
|
>
|
||||||
@@ -633,7 +633,7 @@ onMounted(() => {
|
|||||||
:options="neCascaderOptions"
|
:options="neCascaderOptions"
|
||||||
@change="fnSelectPerformanceInit"
|
@change="fnSelectPerformanceInit"
|
||||||
:allow-clear="false"
|
:allow-clear="false"
|
||||||
:placeholder="t('views.traceManage.task.neTypePlease')"
|
:placeholder="t('views.ne.common.neTypePlease')"
|
||||||
>
|
>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -647,7 +647,11 @@ onMounted(() => {
|
|||||||
name="title"
|
name="title"
|
||||||
v-bind="modalStateFrom.validateInfos.title"
|
v-bind="modalStateFrom.validateInfos.title"
|
||||||
>
|
>
|
||||||
<a-input v-model:value="modalState.from.title" allow-clear>
|
<a-input
|
||||||
|
v-model:value="modalState.from.title"
|
||||||
|
:maxlength="255"
|
||||||
|
allow-clear
|
||||||
|
>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
@@ -657,7 +661,11 @@ onMounted(() => {
|
|||||||
name="kpiId"
|
name="kpiId"
|
||||||
v-bind="modalStateFrom.validateInfos.kpiId"
|
v-bind="modalStateFrom.validateInfos.kpiId"
|
||||||
>
|
>
|
||||||
<a-input v-model:value="modalState.from.kpiId" allow-clear>
|
<a-input
|
||||||
|
v-model:value="modalState.from.kpiId"
|
||||||
|
:maxlength="16"
|
||||||
|
allow-clear
|
||||||
|
>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
@@ -669,7 +677,11 @@ onMounted(() => {
|
|||||||
:label-col="{ span: 3 }"
|
:label-col="{ span: 3 }"
|
||||||
v-bind="modalStateFrom.validateInfos.expression"
|
v-bind="modalStateFrom.validateInfos.expression"
|
||||||
>
|
>
|
||||||
<a-input v-model:value="modalState.from.expression" allow-clear>
|
<a-input
|
||||||
|
v-model:value="modalState.from.expression"
|
||||||
|
:maxlength="1024"
|
||||||
|
allow-clear
|
||||||
|
>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
|
|||||||
@@ -649,7 +649,7 @@ onBeforeUnmount(() => {
|
|||||||
<a-col :lg="6" :md="12" :xs="24">
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
name="neType"
|
name="neType"
|
||||||
:label="t('views.traceManage.task.neType')"
|
:label="t('views.ne.common.neType')"
|
||||||
>
|
>
|
||||||
<a-cascader
|
<a-cascader
|
||||||
v-model:value="state.neType"
|
v-model:value="state.neType"
|
||||||
|
|||||||
@@ -661,7 +661,7 @@ onBeforeUnmount(() => {
|
|||||||
<a-col :lg="6" :md="12" :xs="24">
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
name="neType"
|
name="neType"
|
||||||
:label="t('views.traceManage.task.neType')"
|
:label="t('views.ne.common.neType')"
|
||||||
>
|
>
|
||||||
<a-cascader
|
<a-cascader
|
||||||
v-model:value="state.neType"
|
v-model:value="state.neType"
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ const modalStateFrom = Form.useForm(
|
|||||||
neType: [
|
neType: [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: t('views.traceManage.task.neTypePlease'),
|
message: t('views.ne.common.neTypePlease'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -513,14 +513,14 @@ onMounted(() => {
|
|||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :lg="6" :md="12" :xs="24">
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
:label="t('views.traceManage.task.neType')"
|
:label="t('views.ne.common.neType')"
|
||||||
name="neType "
|
name="neType "
|
||||||
>
|
>
|
||||||
<a-auto-complete
|
<a-auto-complete
|
||||||
v-model:value="queryParams.neType"
|
v-model:value="queryParams.neType"
|
||||||
:options="useNeInfoStore().getNeSelectOtions"
|
:options="useNeInfoStore().getNeSelectOtions"
|
||||||
allow-clear
|
allow-clear
|
||||||
:placeholder="t('views.traceManage.task.neTypePlease')"
|
:placeholder="t('views.ne.common.neTypePlease')"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
@@ -686,7 +686,7 @@ onMounted(() => {
|
|||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :lg="12" :md="12" :xs="24">
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
:label="t('views.traceManage.task.neType')"
|
:label="t('views.ne.common.neType')"
|
||||||
name="neType"
|
name="neType"
|
||||||
v-bind="modalStateFrom.validateInfos.neType"
|
v-bind="modalStateFrom.validateInfos.neType"
|
||||||
>
|
>
|
||||||
@@ -695,7 +695,7 @@ onMounted(() => {
|
|||||||
:options="useNeInfoStore().getNeSelectOtions"
|
:options="useNeInfoStore().getNeSelectOtions"
|
||||||
@change="fnSelectPerformanceInit"
|
@change="fnSelectPerformanceInit"
|
||||||
:allow-clear="false"
|
:allow-clear="false"
|
||||||
:placeholder="t('views.traceManage.task.neTypePlease')"
|
:placeholder="t('views.ne.common.neTypePlease')"
|
||||||
>
|
>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|||||||
@@ -308,7 +308,7 @@ const modalStateFrom = Form.useForm(
|
|||||||
neId: [
|
neId: [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: t('views.traceManage.task.neTypePlease'),
|
message: t('views.ne.common.neTypePlease'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
granulOption: [
|
granulOption: [
|
||||||
@@ -725,14 +725,14 @@ onMounted(() => {
|
|||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :lg="6" :md="12" :xs="24">
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
:label="t('views.traceManage.task.neType')"
|
:label="t('views.ne.common.neType')"
|
||||||
name="neType "
|
name="neType "
|
||||||
>
|
>
|
||||||
<a-auto-complete
|
<a-auto-complete
|
||||||
v-model:value="queryParams.neType"
|
v-model:value="queryParams.neType"
|
||||||
:options="neInfoStore.getNeSelectOtions"
|
:options="neInfoStore.getNeSelectOtions"
|
||||||
allow-clear
|
allow-clear
|
||||||
:placeholder="t('views.traceManage.task.neTypePlease')"
|
:placeholder="t('views.ne.common.neTypePlease')"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
@@ -891,7 +891,7 @@ onMounted(() => {
|
|||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :lg="12" :md="12" :xs="24">
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
:label="t('views.traceManage.task.neType')"
|
:label="t('views.ne.common.neType')"
|
||||||
name="neType"
|
name="neType"
|
||||||
>
|
>
|
||||||
<a-cascader
|
<a-cascader
|
||||||
@@ -1007,7 +1007,7 @@ onMounted(() => {
|
|||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :lg="12" :md="12" :xs="24">
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
:label="t('views.traceManage.task.neType')"
|
:label="t('views.ne.common.neType')"
|
||||||
name="neType"
|
name="neType"
|
||||||
v-bind="modalStateFrom.validateInfos.neId"
|
v-bind="modalStateFrom.validateInfos.neId"
|
||||||
>
|
>
|
||||||
@@ -1016,7 +1016,7 @@ onMounted(() => {
|
|||||||
:options="neInfoStore.getNeCascaderOptions"
|
:options="neInfoStore.getNeCascaderOptions"
|
||||||
@change="fnNeChange"
|
@change="fnNeChange"
|
||||||
:allow-clear="false"
|
:allow-clear="false"
|
||||||
:placeholder="t('views.traceManage.task.neTypePlease')"
|
:placeholder="t('views.ne.common.neTypePlease')"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|||||||
@@ -202,15 +202,15 @@ function fnModalVisible(row: Record<string, any>) {
|
|||||||
const rawData = convertToReadableFormat(hexString);
|
const rawData = convertToReadableFormat(hexString);
|
||||||
modalState.from.rawData = rawData;
|
modalState.from.rawData = rawData;
|
||||||
// RAW解析HTML
|
// RAW解析HTML
|
||||||
getTraceRawInfo(row.id).then(res => {
|
// getTraceRawInfo(row.id).then(res => {
|
||||||
if (res.code === RESULT_CODE_SUCCESS) {
|
// if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
const htmlString = rawDataHTMLScript(res.msg);
|
// const htmlString = rawDataHTMLScript(res.msg);
|
||||||
modalState.from.rawDataHTML = htmlString;
|
// modalState.from.rawDataHTML = htmlString;
|
||||||
modalState.from.downBtn = true;
|
// modalState.from.downBtn = true;
|
||||||
} else {
|
// } else {
|
||||||
modalState.from.rawDataHTML = t('views.traceManage.analysis.noData');
|
// modalState.from.rawDataHTML = t('views.traceManage.analysis.noData');
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
modalState.title = t('views.traceManage.analysis.taskTitle', {
|
modalState.title = t('views.traceManage.analysis.taskTitle', {
|
||||||
num: row.imsi,
|
num: row.imsi,
|
||||||
});
|
});
|
||||||
@@ -495,7 +495,7 @@ onMounted(() => {
|
|||||||
<a-col class="txt" :span="10">{{ v.asciiText }}</a-col>
|
<a-col class="txt" :span="10">{{ v.asciiText }}</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-divider />
|
<a-divider />
|
||||||
<div class="raw-title">
|
<!-- <div class="raw-title">
|
||||||
{{ t('views.traceManage.analysis.signalDetail') }}
|
{{ t('views.traceManage.analysis.signalDetail') }}
|
||||||
<a-button
|
<a-button
|
||||||
type="dashed"
|
type="dashed"
|
||||||
@@ -508,8 +508,8 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
{{ t('views.traceManage.analysis.taskDownText') }}
|
{{ t('views.traceManage.analysis.taskDownText') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div> -->
|
||||||
<div class="raw-html" v-html="modalState.from.rawDataHTML"></div>
|
<!-- <div class="raw-html" v-html="modalState.from.rawDataHTML"></div> -->
|
||||||
</ProModal>
|
</ProModal>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
335
src/views/traceManage/task/analyze.vue
Normal file
335
src/views/traceManage/task/analyze.vue
Normal file
@@ -0,0 +1,335 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onBeforeUnmount, ref, watch } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import { PageContainer } from 'antdv-pro-layout';
|
||||||
|
import { message, Modal } from 'ant-design-vue/lib';
|
||||||
|
import DissectionTree from '../tshark/components/DissectionTree.vue';
|
||||||
|
import DissectionDump from '../tshark/components/DissectionDump.vue';
|
||||||
|
import PacketTable from '../tshark/components/PacketTable.vue';
|
||||||
|
import { usePCAP, NO_SELECTION } from '../tshark/hooks/usePCAP';
|
||||||
|
import {
|
||||||
|
RESULT_CODE_ERROR,
|
||||||
|
RESULT_CODE_SUCCESS,
|
||||||
|
} from '@/constants/result-constants';
|
||||||
|
import { filePullTask } from '@/api/trace/task';
|
||||||
|
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import useTabsStore from '@/store/modules/tabs';
|
||||||
|
import saveAs from 'file-saver';
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const tabsStore = useTabsStore();
|
||||||
|
const ws = new WS();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const {
|
||||||
|
state,
|
||||||
|
handleSelectedTreeEntry,
|
||||||
|
handleSelectedFindSelection,
|
||||||
|
handleSelectedFrame,
|
||||||
|
handleScrollBottom,
|
||||||
|
handleFilterFrames,
|
||||||
|
handleLoadFile,
|
||||||
|
} = usePCAP();
|
||||||
|
|
||||||
|
/**跟踪编号 */
|
||||||
|
const traceId = ref<string>(route.query.traceId as string);
|
||||||
|
|
||||||
|
/**关闭跳转 */
|
||||||
|
function fnClose() {
|
||||||
|
const to = tabsStore.tabClose(route.path);
|
||||||
|
if (to) {
|
||||||
|
router.push(to);
|
||||||
|
} else {
|
||||||
|
router.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**下载触发等待 */
|
||||||
|
let downLoading = ref<boolean>(false);
|
||||||
|
|
||||||
|
/**信息文件下载 */
|
||||||
|
function fnDownloadFile() {
|
||||||
|
if (downLoading.value) return;
|
||||||
|
const fileName = `trace_${traceId.value}.pcap`
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.logManage.neFile.downTip', { fileName }),
|
||||||
|
onOk() {
|
||||||
|
downLoading.value = true;
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
filePullTask(traceId.value)
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('common.msgSuccess', {
|
||||||
|
msg: t('common.downloadText'),
|
||||||
|
}),
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
saveAs(res.data, `${fileName}`);
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: t('views.logManage.neFile.downTipErr'),
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
downLoading.value = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**获取PCAP文件 */
|
||||||
|
function fnFilePCAP() {
|
||||||
|
filePullTask(traceId.value).then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
handleLoadFile(res.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**接收数据后回调 */
|
||||||
|
function wsMessage(res: Record<string, any>) {
|
||||||
|
const { code, requestId, data } = res;
|
||||||
|
if (code === RESULT_CODE_ERROR) {
|
||||||
|
console.warn(res.msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 建联时发送请求
|
||||||
|
if (!requestId && data.clientId) {
|
||||||
|
fnFilePCAP();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订阅组信息
|
||||||
|
if (!data?.groupId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.groupId === `2_${traceId.value}`) {
|
||||||
|
fnFilePCAP();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**建立WS连接 */
|
||||||
|
function fnWS() {
|
||||||
|
const options: OptionsType = {
|
||||||
|
url: '/ws',
|
||||||
|
params: {
|
||||||
|
/**订阅通道组
|
||||||
|
*
|
||||||
|
* 跟踪任务PCAP文件 (GroupID:2_traceId)
|
||||||
|
*/
|
||||||
|
subGroupID: `2_${traceId.value}`,
|
||||||
|
},
|
||||||
|
onmessage: wsMessage,
|
||||||
|
onerror: (ev: any) => {
|
||||||
|
// 接收数据后回调
|
||||||
|
console.error(ev);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
//建立连接
|
||||||
|
ws.connect(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => state.initialized,
|
||||||
|
v => {
|
||||||
|
v && fnWS();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
ws.close();
|
||||||
|
});
|
||||||
|
</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-button type="default" @click.prevent="fnClose()">
|
||||||
|
<template #icon><CloseOutlined /></template>
|
||||||
|
{{ t('common.close') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
:loading="downLoading"
|
||||||
|
@click.prevent="fnDownloadFile()"
|
||||||
|
>
|
||||||
|
<template #icon><DownloadOutlined /></template>
|
||||||
|
{{ t('common.downloadText') }}
|
||||||
|
</a-button>
|
||||||
|
<span>
|
||||||
|
{{ t('views.traceManage.task.traceId') }}:
|
||||||
|
<strong>{{ traceId }}</strong>
|
||||||
|
</span>
|
||||||
|
</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>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 :gutter="20">
|
||||||
|
<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>
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, onMounted, toRaw, ref } from 'vue';
|
import { reactive, onMounted, toRaw, ref } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { PageContainer } from 'antdv-pro-layout';
|
import { PageContainer } from 'antdv-pro-layout';
|
||||||
import { Form, message, Modal } from 'ant-design-vue/lib';
|
import { Form, message, Modal } from 'ant-design-vue/lib';
|
||||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||||
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
||||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||||
import { parseDateToStr } from '@/utils/date-utils';
|
import { parseDateToStr } from '@/utils/date-utils';
|
||||||
|
import { MENU_PATH_INLINE } from '@/constants/menu-constants';
|
||||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
import useI18n from '@/hooks/useI18n';
|
import useI18n from '@/hooks/useI18n';
|
||||||
import useNeInfoStore from '@/store/modules/neinfo';
|
import useNeInfoStore from '@/store/modules/neinfo';
|
||||||
@@ -23,6 +25,8 @@ import { parseObjHumpToLine } from '@/utils/parse-utils';
|
|||||||
const neInfoStore = useNeInfoStore();
|
const neInfoStore = useNeInfoStore();
|
||||||
const { getDict } = useDictStore();
|
const { getDict } = useDictStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
/**字典数据 */
|
/**字典数据 */
|
||||||
let dict: {
|
let dict: {
|
||||||
@@ -94,34 +98,34 @@ let tableState: TabeStateType = reactive({
|
|||||||
/**表格字段列 */
|
/**表格字段列 */
|
||||||
let tableColumns: ColumnsType = [
|
let tableColumns: ColumnsType = [
|
||||||
{
|
{
|
||||||
title: t('common.rowId'),
|
title: t('views.ne.common.neType'),
|
||||||
dataIndex: 'id',
|
|
||||||
align: 'center',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('views.traceManage.task.neType'),
|
|
||||||
dataIndex: 'neType',
|
dataIndex: 'neType',
|
||||||
align: 'center',
|
align: 'left',
|
||||||
sorter: {
|
sorter: {
|
||||||
compare: (a, b) => 1,
|
compare: (a, b) => 1,
|
||||||
multiple: 1,
|
multiple: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('views.traceManage.task.neID'),
|
title: t('views.ne.common.neId'),
|
||||||
dataIndex: 'neId',
|
dataIndex: 'neId',
|
||||||
align: 'center',
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.traceManage.task.traceId'),
|
||||||
|
dataIndex: 'traceId',
|
||||||
|
align: 'left',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('views.traceManage.task.trackType'),
|
title: t('views.traceManage.task.trackType'),
|
||||||
dataIndex: 'traceType',
|
dataIndex: 'traceType',
|
||||||
key: 'traceType',
|
key: 'traceType',
|
||||||
align: 'center',
|
align: 'left',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('views.traceManage.task.startTime'),
|
title: t('views.traceManage.task.startTime'),
|
||||||
dataIndex: 'startTime',
|
dataIndex: 'startTime',
|
||||||
align: 'center',
|
align: 'left',
|
||||||
customRender(opt) {
|
customRender(opt) {
|
||||||
if (!opt.value) return '';
|
if (!opt.value) return '';
|
||||||
return parseDateToStr(opt.value);
|
return parseDateToStr(opt.value);
|
||||||
@@ -131,7 +135,7 @@ let tableColumns: ColumnsType = [
|
|||||||
{
|
{
|
||||||
title: t('views.traceManage.task.endTime'),
|
title: t('views.traceManage.task.endTime'),
|
||||||
dataIndex: 'endTime',
|
dataIndex: 'endTime',
|
||||||
align: 'center',
|
align: 'left',
|
||||||
customRender(opt) {
|
customRender(opt) {
|
||||||
if (!opt.value) return '';
|
if (!opt.value) return '';
|
||||||
return parseDateToStr(opt.value);
|
return parseDateToStr(opt.value);
|
||||||
@@ -140,7 +144,7 @@ let tableColumns: ColumnsType = [
|
|||||||
{
|
{
|
||||||
title: t('common.operate'),
|
title: t('common.operate'),
|
||||||
key: 'id',
|
key: 'id',
|
||||||
align: 'center',
|
align: 'left',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -312,6 +316,7 @@ let modalState: ModalStateType = reactive({
|
|||||||
id: '',
|
id: '',
|
||||||
neType: '',
|
neType: '',
|
||||||
neId: '',
|
neId: '',
|
||||||
|
traceId: '',
|
||||||
traceType: '3',
|
traceType: '3',
|
||||||
startTime: undefined,
|
startTime: undefined,
|
||||||
endTime: undefined,
|
endTime: undefined,
|
||||||
@@ -342,7 +347,7 @@ const modalStateFrom = Form.useForm(
|
|||||||
neId: [
|
neId: [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: t('views.traceManage.task.neTypePlease'),
|
message: t('views.ne.common.neTypePlease'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
endTime: [
|
endTime: [
|
||||||
@@ -545,6 +550,16 @@ function fnModalCancel() {
|
|||||||
modalState.neTypeInterface = [];
|
modalState.neTypeInterface = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**跳转PCAP文件详情页面 */
|
||||||
|
function fnRecordPCAPView(row: Record<string, any>) {
|
||||||
|
router.push({
|
||||||
|
path: `${route.path}${MENU_PATH_INLINE}/analyze`,
|
||||||
|
query: {
|
||||||
|
traceId: row.traceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 初始字典数据
|
// 初始字典数据
|
||||||
Promise.allSettled([getDict('trace_type')]).then(resArr => {
|
Promise.allSettled([getDict('trace_type')]).then(resArr => {
|
||||||
@@ -600,15 +615,12 @@ onMounted(() => {
|
|||||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :lg="6" :md="12" :xs="24">
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
<a-form-item
|
<a-form-item :label="t('views.ne.common.neType')" name="neType ">
|
||||||
:label="t('views.traceManage.task.neType')"
|
|
||||||
name="neType "
|
|
||||||
>
|
|
||||||
<a-auto-complete
|
<a-auto-complete
|
||||||
v-model:value="queryParams.neType"
|
v-model:value="queryParams.neType"
|
||||||
:options="neCascaderOptions"
|
:options="neCascaderOptions"
|
||||||
allow-clear
|
allow-clear
|
||||||
:placeholder="t('views.traceManage.task.neTypePlease')"
|
:placeholder="t('views.ne.common.neTypePlease')"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
@@ -751,7 +763,7 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
<template v-if="column.key === 'id'">
|
<template v-if="column.key === 'id'">
|
||||||
<a-space :size="8" align="center">
|
<a-space :size="8" align="center">
|
||||||
<a-tooltip>
|
<a-tooltip placement="topRight">
|
||||||
<template #title>{{ t('common.editText') }}</template>
|
<template #title>{{ t('common.editText') }}</template>
|
||||||
<a-button
|
<a-button
|
||||||
type="link"
|
type="link"
|
||||||
@@ -760,7 +772,15 @@ onMounted(() => {
|
|||||||
<template #icon><FormOutlined /></template>
|
<template #icon><FormOutlined /></template>
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip placement="topRight">
|
||||||
|
<template #title>{{
|
||||||
|
t('views.traceManage.task.pcapView')
|
||||||
|
}}</template>
|
||||||
|
<a-button type="link" @click.prevent="fnRecordPCAPView(record)">
|
||||||
|
<template #icon><ContainerOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip placement="topRight">
|
||||||
<template #title>{{ t('common.deleteText') }}</template>
|
<template #title>{{ t('common.deleteText') }}</template>
|
||||||
<a-button
|
<a-button
|
||||||
type="link"
|
type="link"
|
||||||
@@ -798,7 +818,7 @@ onMounted(() => {
|
|||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :lg="12" :md="12" :xs="24">
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
:label="t('views.traceManage.task.neType')"
|
:label="t('views.ne.common.neType')"
|
||||||
:label-col="{ span: 8 }"
|
:label-col="{ span: 8 }"
|
||||||
name="neType"
|
name="neType"
|
||||||
v-bind="modalStateFrom.validateInfos.neId"
|
v-bind="modalStateFrom.validateInfos.neId"
|
||||||
@@ -808,7 +828,7 @@ onMounted(() => {
|
|||||||
:options="neCascaderOptions"
|
:options="neCascaderOptions"
|
||||||
@change="fnNeChange"
|
@change="fnNeChange"
|
||||||
:allow-clear="false"
|
:allow-clear="false"
|
||||||
:placeholder="t('views.traceManage.task.neTypePlease')"
|
:placeholder="t('views.ne.common.neTypePlease')"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, ref, computed, unref, onUpdated, watchEffect } from 'vue';
|
import { reactive, ref, computed, unref, watchEffect } from 'vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
/**列表高度 */
|
/**列表高度 */
|
||||||
@@ -122,8 +122,6 @@ const onScroll = (e: any) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onUpdated(() => {});
|
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
clientData.value.forEach((_, index) => {
|
clientData.value.forEach((_, index) => {
|
||||||
const currentIndex = state.start + index;
|
const currentIndex = state.start + index;
|
||||||
@@ -136,10 +134,6 @@ watchEffect(() => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const tableState = reactive({
|
|
||||||
selected: false,
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -167,13 +161,13 @@ const tableState = reactive({
|
|||||||
item.number === props.selectedFrame
|
item.number === props.selectedFrame
|
||||||
? 'blue'
|
? 'blue'
|
||||||
: item.bg
|
: item.bg
|
||||||
? `#${item.bg.toString(16).padStart(6, '0')}`
|
? `#${Number(item.bg).toString(16).padStart(6, '0')}`
|
||||||
: '',
|
: '',
|
||||||
color:
|
color:
|
||||||
item.number === props.selectedFrame
|
item.number === props.selectedFrame
|
||||||
? 'white'
|
? 'white'
|
||||||
: item.fg
|
: item.fg
|
||||||
? `#${item.fg.toString(16).padStart(6, '0')}`
|
? `#${Number(item.fg).toString(16).padStart(6, '0')}`
|
||||||
: '',
|
: '',
|
||||||
}"
|
}"
|
||||||
@click="onSelectedFrame(item.number)"
|
@click="onSelectedFrame(item.number)"
|
||||||
|
|||||||
317
src/views/traceManage/tshark/hooks/usePCAP.ts
Normal file
317
src/views/traceManage/tshark/hooks/usePCAP.ts
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
import { onBeforeUnmount, onMounted, reactive } from 'vue';
|
||||||
|
import { scriptUrl } from '@/assets/js/wiregasm_worker';
|
||||||
|
import { WK, OptionsType } from '@/plugins/wk-worker';
|
||||||
|
const wk = new WK();
|
||||||
|
|
||||||
|
export const NO_SELECTION = { id: '', idx: 0, start: 0, length: 0 };
|
||||||
|
|
||||||
|
type StateType = {
|
||||||
|
/**初始化 */
|
||||||
|
initialized: boolean;
|
||||||
|
/**pcap信息 */
|
||||||
|
summary: {
|
||||||
|
filename: string;
|
||||||
|
file_type: string;
|
||||||
|
file_length: number;
|
||||||
|
file_encap_type: string;
|
||||||
|
packet_count: number;
|
||||||
|
start_time: number;
|
||||||
|
stop_time: number;
|
||||||
|
elapsed_time: number;
|
||||||
|
};
|
||||||
|
/**字段 */
|
||||||
|
columns: string[];
|
||||||
|
/**过滤条件 */
|
||||||
|
filter: string;
|
||||||
|
/**过滤条件错误信息 */
|
||||||
|
filterError: string | null;
|
||||||
|
/**当前过滤条件 */
|
||||||
|
currentFilter: string;
|
||||||
|
/**当前选中的帧编号 */
|
||||||
|
selectedFrame: number;
|
||||||
|
/**当前选中的帧数据 */
|
||||||
|
selectedPacket: { tree: any[]; data_sources: any[] };
|
||||||
|
/**pcap包帧数据 */
|
||||||
|
packetFrameData: Map<string, any> | null;
|
||||||
|
/**当前选中的帧数据-空占位 */
|
||||||
|
selectedTreeEntry: typeof NO_SELECTION;
|
||||||
|
/**选择帧的Dump数据标签 */
|
||||||
|
selectedDataSourceIndex: number;
|
||||||
|
/**处理完成状态 */
|
||||||
|
finishedProcessing: boolean;
|
||||||
|
/**pcap包帧数,匹配帧数 */
|
||||||
|
totalFrames: number;
|
||||||
|
/**pcap包帧数据 */
|
||||||
|
packetFrames: any[];
|
||||||
|
/**加载帧数 */
|
||||||
|
nextPageSize: number;
|
||||||
|
/**加载页数 */
|
||||||
|
nextPageNum: number;
|
||||||
|
/**加载下一页 */
|
||||||
|
nextPageLoad: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function usePCAP() {
|
||||||
|
const state = reactive<StateType>({
|
||||||
|
initialized: false,
|
||||||
|
summary: {
|
||||||
|
filename: '',
|
||||||
|
file_type: 'Wireshark/tcpdump/... - pcap',
|
||||||
|
file_length: 0,
|
||||||
|
file_encap_type: 'Ethernet',
|
||||||
|
packet_count: 0,
|
||||||
|
start_time: 0,
|
||||||
|
stop_time: 0,
|
||||||
|
elapsed_time: 0,
|
||||||
|
},
|
||||||
|
columns: [],
|
||||||
|
filter: '',
|
||||||
|
filterError: null,
|
||||||
|
currentFilter: '',
|
||||||
|
selectedFrame: 1,
|
||||||
|
/**当前选中的帧数据 */
|
||||||
|
selectedPacket: { tree: [], data_sources: [] },
|
||||||
|
packetFrameData: null, // 注意:Map 需要额外处理
|
||||||
|
selectedTreeEntry: NO_SELECTION, // NO_SELECTION 需要定义
|
||||||
|
/**选择帧的Dump数据标签 */
|
||||||
|
selectedDataSourceIndex: 0,
|
||||||
|
/**处理完成状态 */
|
||||||
|
finishedProcessing: false,
|
||||||
|
totalFrames: 0,
|
||||||
|
packetFrames: [],
|
||||||
|
nextPageNum: 1,
|
||||||
|
nextPageSize: 40,
|
||||||
|
nextPageLoad: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 清除帧数据和报文信息状态
|
||||||
|
function handleStateReset() {
|
||||||
|
// 加载pcap包的数据
|
||||||
|
state.nextPageNum = 1;
|
||||||
|
// 选择帧的数据
|
||||||
|
state.selectedFrame = 0;
|
||||||
|
state.selectedPacket = { tree: [], data_sources: [] };
|
||||||
|
state.packetFrameData = null;
|
||||||
|
state.selectedTreeEntry = NO_SELECTION;
|
||||||
|
state.selectedDataSourceIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**解析帧数据为简单结构 */
|
||||||
|
function parseFrameData(id: string, node: Record<string, any>) {
|
||||||
|
let map = new Map();
|
||||||
|
|
||||||
|
if (node.tree && node.tree.length > 0) {
|
||||||
|
for (let i = 0; i < node.tree.length; i++) {
|
||||||
|
const subMap = parseFrameData(`${id}-${i}`, node.tree[i]);
|
||||||
|
subMap.forEach((value, key) => {
|
||||||
|
map.set(key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (node.length > 0) {
|
||||||
|
map.set(id, {
|
||||||
|
id: id,
|
||||||
|
idx: node.data_source_idx,
|
||||||
|
start: node.start,
|
||||||
|
length: node.length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**帧数据点击选中 */
|
||||||
|
function handleSelectedTreeEntry(e: any) {
|
||||||
|
console.log('fnSelectedTreeEntry', e);
|
||||||
|
state.selectedTreeEntry = e;
|
||||||
|
}
|
||||||
|
/**报文数据点击选中 */
|
||||||
|
function handleSelectedFindSelection(src_idx: number, pos: number) {
|
||||||
|
console.log('fnSelectedFindSelection', pos);
|
||||||
|
if (state.packetFrameData == null) return;
|
||||||
|
// find the smallest one
|
||||||
|
let current = null;
|
||||||
|
for (let [k, pp] of state.packetFrameData) {
|
||||||
|
if (pp.idx !== src_idx) continue;
|
||||||
|
|
||||||
|
if (pos >= pp.start && pos <= pp.start + pp.length) {
|
||||||
|
if (
|
||||||
|
current != null &&
|
||||||
|
state.packetFrameData.get(current).length > pp.length
|
||||||
|
) {
|
||||||
|
current = k;
|
||||||
|
} else {
|
||||||
|
current = k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (current != null) {
|
||||||
|
state.selectedTreeEntry = state.packetFrameData.get(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**包数据表点击选中 */
|
||||||
|
function handleSelectedFrame(no: number) {
|
||||||
|
console.log('fnSelectedFrame', no, state.totalFrames);
|
||||||
|
state.selectedFrame = no;
|
||||||
|
wk.send({ type: 'select', number: state.selectedFrame });
|
||||||
|
}
|
||||||
|
/**包数据表滚动底部加载 */
|
||||||
|
function handleScrollBottom() {
|
||||||
|
const totalFetched = state.packetFrames.length;
|
||||||
|
console.log('fnScrollBottom', totalFetched);
|
||||||
|
if (!state.nextPageLoad && totalFetched < state.totalFrames) {
|
||||||
|
state.nextPageLoad = true;
|
||||||
|
state.nextPageNum++;
|
||||||
|
loaldFrames(state.filter, state.nextPageNum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**包数据表过滤 */
|
||||||
|
function handleFilterFrames() {
|
||||||
|
console.log('fnFilterFinish', state.filter);
|
||||||
|
wk.send({ type: 'check-filter', filter: state.filter });
|
||||||
|
}
|
||||||
|
/**包数据表加载 */
|
||||||
|
function loaldFrames(filter: string, page: number = 1) {
|
||||||
|
if (!(state.initialized && state.finishedProcessing)) return;
|
||||||
|
const limit = state.nextPageSize;
|
||||||
|
wk.send({
|
||||||
|
type: 'frames',
|
||||||
|
filter: filter,
|
||||||
|
skip: (page - 1) * limit,
|
||||||
|
limit: limit,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**加载包文件 */
|
||||||
|
function handleLoadFile(file: File | Blob) {
|
||||||
|
state.summary = {
|
||||||
|
filename: '',
|
||||||
|
file_type: 'Wireshark/tcpdump/... - pcap',
|
||||||
|
file_length: 0,
|
||||||
|
file_encap_type: 'Ethernet',
|
||||||
|
packet_count: 0,
|
||||||
|
start_time: 0,
|
||||||
|
stop_time: 0,
|
||||||
|
elapsed_time: 0,
|
||||||
|
};
|
||||||
|
state.finishedProcessing = false;
|
||||||
|
|
||||||
|
wk.send({ type: 'process', file: file });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**本地示例文件 */
|
||||||
|
async function handleLoadExample() {
|
||||||
|
const name = 'test_ethernet.pcap';
|
||||||
|
const res = await fetch('/wiregasm/test_ethernet.pcap');
|
||||||
|
const body = await res.arrayBuffer();
|
||||||
|
|
||||||
|
state.summary = {
|
||||||
|
filename: '',
|
||||||
|
file_type: 'Wireshark/tcpdump/... - pcap',
|
||||||
|
file_length: 0,
|
||||||
|
file_encap_type: 'Ethernet',
|
||||||
|
packet_count: 0,
|
||||||
|
start_time: 0,
|
||||||
|
stop_time: 0,
|
||||||
|
elapsed_time: 0,
|
||||||
|
};
|
||||||
|
state.finishedProcessing = false;
|
||||||
|
|
||||||
|
wk.send({ type: 'process-data', name: name, data: body });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**接收数据后回调 */
|
||||||
|
function wkMessage(res: Record<string, any>) {
|
||||||
|
switch (res.type) {
|
||||||
|
case 'status':
|
||||||
|
console.info(res.status);
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
console.warn(res.error);
|
||||||
|
break;
|
||||||
|
case 'init':
|
||||||
|
wk.send({ type: 'columns' });
|
||||||
|
state.initialized = true;
|
||||||
|
break;
|
||||||
|
case 'columns':
|
||||||
|
state.columns = res.data;
|
||||||
|
break;
|
||||||
|
case 'frames':
|
||||||
|
// console.log(res.data);
|
||||||
|
const { matched, frames } = res.data;
|
||||||
|
state.totalFrames = matched;
|
||||||
|
|
||||||
|
if (state.nextPageNum == 1) {
|
||||||
|
state.packetFrames = frames;
|
||||||
|
// 有匹配的选择第一个
|
||||||
|
if (frames.length > 0) {
|
||||||
|
state.selectedFrame = frames[0].number;
|
||||||
|
handleSelectedFrame(state.selectedFrame);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.packetFrames = state.packetFrames.concat(frames);
|
||||||
|
state.nextPageLoad = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'selected':
|
||||||
|
state.selectedPacket = res.data;
|
||||||
|
state.packetFrameData = parseFrameData('root', res.data);
|
||||||
|
state.selectedTreeEntry = NO_SELECTION;
|
||||||
|
state.selectedDataSourceIndex = 0;
|
||||||
|
break;
|
||||||
|
case 'processed':
|
||||||
|
// setStatus(`Error: non-zero return code (${e.data.code})`);
|
||||||
|
state.finishedProcessing = true;
|
||||||
|
if (res.data.code === 0) {
|
||||||
|
state.summary = res.data.summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载数据
|
||||||
|
handleStateReset();
|
||||||
|
loaldFrames(state.filter);
|
||||||
|
break;
|
||||||
|
case 'filter':
|
||||||
|
const filterRes = res.data;
|
||||||
|
if (filterRes.ok) {
|
||||||
|
state.currentFilter = state.filter;
|
||||||
|
state.filterError = null;
|
||||||
|
// 加载数据
|
||||||
|
handleStateReset();
|
||||||
|
loaldFrames(state.filter);
|
||||||
|
} else {
|
||||||
|
state.filterError = filterRes.error;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn(res);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 建立链接
|
||||||
|
const options: OptionsType = {
|
||||||
|
url: scriptUrl,
|
||||||
|
onmessage: wkMessage,
|
||||||
|
onerror: (ev: any) => {
|
||||||
|
console.error(ev);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
wk.connect(options);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
wk.send({ type: 'close' }) && wk.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
state,
|
||||||
|
handleSelectedTreeEntry,
|
||||||
|
handleSelectedFindSelection,
|
||||||
|
handleSelectedFrame,
|
||||||
|
handleScrollBottom,
|
||||||
|
handleFilterFrames,
|
||||||
|
handleLoadExample,
|
||||||
|
handleLoadFile,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export default usePCAP;
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, onMounted } from 'vue';
|
|
||||||
import { message, Upload } from 'ant-design-vue/lib';
|
import { message, Upload } from 'ant-design-vue/lib';
|
||||||
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
|
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
|
||||||
import { FileType } from 'ant-design-vue/lib/upload/interface';
|
import { FileType } from 'ant-design-vue/lib/upload/interface';
|
||||||
@@ -7,210 +6,20 @@ import { PageContainer } from 'antdv-pro-layout';
|
|||||||
import DissectionTree from './components/DissectionTree.vue';
|
import DissectionTree from './components/DissectionTree.vue';
|
||||||
import DissectionDump from './components/DissectionDump.vue';
|
import DissectionDump from './components/DissectionDump.vue';
|
||||||
import PacketTable from './components/PacketTable.vue';
|
import PacketTable from './components/PacketTable.vue';
|
||||||
import { scriptUrl } from '@/assets/js/wiregasm_worker';
|
import { usePCAP, NO_SELECTION } from './hooks/usePCAP';
|
||||||
import { parseSizeFromFile } from '@/utils/parse-utils';
|
import { parseSizeFromFile } from '@/utils/parse-utils';
|
||||||
import { WK, OptionsType } from '@/plugins/wk-worker';
|
|
||||||
import useI18n from '@/hooks/useI18n';
|
import useI18n from '@/hooks/useI18n';
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const wk = new WK();
|
const {
|
||||||
|
state,
|
||||||
const NO_SELECTION = { id: '', idx: 0, start: 0, length: 0 };
|
handleSelectedTreeEntry,
|
||||||
|
handleSelectedFindSelection,
|
||||||
type StateType = {
|
handleSelectedFrame,
|
||||||
/**初始化 */
|
handleScrollBottom,
|
||||||
initialized: boolean;
|
handleFilterFrames,
|
||||||
/**pcap信息 */
|
handleLoadExample,
|
||||||
summary: {
|
handleLoadFile,
|
||||||
filename: string;
|
} = usePCAP();
|
||||||
file_type: string;
|
|
||||||
file_length: number;
|
|
||||||
file_encap_type: string;
|
|
||||||
packet_count: number;
|
|
||||||
start_time: number;
|
|
||||||
stop_time: number;
|
|
||||||
elapsed_time: number;
|
|
||||||
};
|
|
||||||
/**字段 */
|
|
||||||
columns: string[];
|
|
||||||
/**过滤条件 */
|
|
||||||
filter: string;
|
|
||||||
/**过滤条件错误信息 */
|
|
||||||
filterError: string | null;
|
|
||||||
/**当前过滤条件 */
|
|
||||||
currentFilter: string;
|
|
||||||
/**当前选中的帧编号 */
|
|
||||||
selectedFrame: number;
|
|
||||||
/**当前选中的帧数据 */
|
|
||||||
selectedPacket: { tree: any[]; data_sources: any[] };
|
|
||||||
/**pcap包帧数据 */
|
|
||||||
packetFrameData: Map<string, any> | null;
|
|
||||||
/**当前选中的帧数据-空占位 */
|
|
||||||
selectedTreeEntry: typeof NO_SELECTION;
|
|
||||||
/**选择帧的Dump数据标签 */
|
|
||||||
selectedDataSourceIndex: number;
|
|
||||||
/**处理完成状态 */
|
|
||||||
finishedProcessing: boolean;
|
|
||||||
/**pcap包帧数,匹配帧数 */
|
|
||||||
totalFrames: number;
|
|
||||||
/**pcap包帧数据 */
|
|
||||||
packetFrames: any[];
|
|
||||||
/**加载帧数 */
|
|
||||||
nextPageSize: number;
|
|
||||||
/**加载页数 */
|
|
||||||
nextPageNum: number;
|
|
||||||
/**加载下一页 */
|
|
||||||
nextPageLoad: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const state = reactive<StateType>({
|
|
||||||
initialized: false,
|
|
||||||
summary: {
|
|
||||||
filename: '',
|
|
||||||
file_type: 'Wireshark/tcpdump/... - pcap',
|
|
||||||
file_length: 0,
|
|
||||||
file_encap_type: 'Ethernet',
|
|
||||||
packet_count: 0,
|
|
||||||
start_time: 0,
|
|
||||||
stop_time: 0,
|
|
||||||
elapsed_time: 0,
|
|
||||||
},
|
|
||||||
columns: [],
|
|
||||||
filter: '',
|
|
||||||
filterError: null,
|
|
||||||
currentFilter: '',
|
|
||||||
selectedFrame: 1,
|
|
||||||
/**当前选中的帧数据 */
|
|
||||||
selectedPacket: { tree: [], data_sources: [] },
|
|
||||||
packetFrameData: null, // 注意:Map 需要额外处理
|
|
||||||
selectedTreeEntry: NO_SELECTION, // NO_SELECTION 需要定义
|
|
||||||
/**选择帧的Dump数据标签 */
|
|
||||||
selectedDataSourceIndex: 0,
|
|
||||||
/**处理完成状态 */
|
|
||||||
finishedProcessing: false,
|
|
||||||
totalFrames: 0,
|
|
||||||
packetFrames: [],
|
|
||||||
nextPageNum: 1,
|
|
||||||
nextPageSize: 40,
|
|
||||||
nextPageLoad: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 清除帧数据和报文信息状态
|
|
||||||
function fnStateReset() {
|
|
||||||
// 加载pcap包的数据
|
|
||||||
state.nextPageNum = 1;
|
|
||||||
// 选择帧的数据
|
|
||||||
state.selectedFrame = 0;
|
|
||||||
state.selectedPacket = { tree: [], data_sources: [] };
|
|
||||||
state.packetFrameData = null;
|
|
||||||
state.selectedTreeEntry = NO_SELECTION;
|
|
||||||
state.selectedDataSourceIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**解析帧数据为简单结构 */
|
|
||||||
function parseFrameData(id: string, node: Record<string, any>) {
|
|
||||||
let map = new Map();
|
|
||||||
|
|
||||||
if (node.tree && node.tree.length > 0) {
|
|
||||||
for (let i = 0; i < node.tree.length; i++) {
|
|
||||||
const subMap = parseFrameData(`${id}-${i}`, node.tree[i]);
|
|
||||||
subMap.forEach((value, key) => {
|
|
||||||
map.set(key, value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (node.length > 0) {
|
|
||||||
map.set(id, {
|
|
||||||
id: id,
|
|
||||||
idx: node.data_source_idx,
|
|
||||||
start: node.start,
|
|
||||||
length: node.length,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**帧数据点击选中 */
|
|
||||||
function fnSelectedTreeEntry(e: any) {
|
|
||||||
console.log('fnSelectedTreeEntry', e);
|
|
||||||
state.selectedTreeEntry = e;
|
|
||||||
}
|
|
||||||
/**报文数据点击选中 */
|
|
||||||
function fnSelectedFindSelection(src_idx: number, pos: number) {
|
|
||||||
console.log('fnSelectedFindSelection', pos);
|
|
||||||
if (state.packetFrameData == null) return;
|
|
||||||
// find the smallest one
|
|
||||||
let current = null;
|
|
||||||
for (let [k, pp] of state.packetFrameData) {
|
|
||||||
if (pp.idx !== src_idx) continue;
|
|
||||||
|
|
||||||
if (pos >= pp.start && pos <= pp.start + pp.length) {
|
|
||||||
if (
|
|
||||||
current != null &&
|
|
||||||
state.packetFrameData.get(current).length > pp.length
|
|
||||||
) {
|
|
||||||
current = k;
|
|
||||||
} else {
|
|
||||||
current = k;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (current != null) {
|
|
||||||
state.selectedTreeEntry = state.packetFrameData.get(current);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**包数据表点击选中 */
|
|
||||||
function fnSelectedFrame(no: number) {
|
|
||||||
console.log('fnSelectedFrame', no, state.totalFrames);
|
|
||||||
state.selectedFrame = no;
|
|
||||||
wk.send({ type: 'select', number: state.selectedFrame });
|
|
||||||
}
|
|
||||||
/**包数据表滚动底部加载 */
|
|
||||||
function fnScrollBottom() {
|
|
||||||
const totalFetched = state.packetFrames.length;
|
|
||||||
console.log('fnScrollBottom', totalFetched);
|
|
||||||
if (!state.nextPageLoad && totalFetched < state.totalFrames) {
|
|
||||||
state.nextPageLoad = true;
|
|
||||||
state.nextPageNum++;
|
|
||||||
fnLoaldFrames(state.filter, state.nextPageNum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**包数据表过滤 */
|
|
||||||
function fnFilterFrames() {
|
|
||||||
console.log('fnFilterFinish', state.filter);
|
|
||||||
wk.send({ type: 'check-filter', filter: state.filter });
|
|
||||||
}
|
|
||||||
/**包数据表记载 */
|
|
||||||
function fnLoaldFrames(filter: string, page: number = 1) {
|
|
||||||
if (!(state.initialized && state.finishedProcessing)) return;
|
|
||||||
const limit = state.nextPageSize;
|
|
||||||
wk.send({
|
|
||||||
type: 'frames',
|
|
||||||
filter: filter,
|
|
||||||
skip: (page - 1) * limit,
|
|
||||||
limit: limit,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**本地示例文件 */
|
|
||||||
async function fnLoadExample() {
|
|
||||||
const name = 'test_ethernet.pcap';
|
|
||||||
const res = await fetch('/wiregasm/test_ethernet.pcap');
|
|
||||||
const body = await res.arrayBuffer();
|
|
||||||
|
|
||||||
state.summary = {
|
|
||||||
filename: '',
|
|
||||||
file_type: 'Wireshark/tcpdump/... - pcap',
|
|
||||||
file_length: 0,
|
|
||||||
file_encap_type: 'Ethernet',
|
|
||||||
packet_count: 0,
|
|
||||||
start_time: 0,
|
|
||||||
stop_time: 0,
|
|
||||||
elapsed_time: 0,
|
|
||||||
};
|
|
||||||
state.finishedProcessing = false;
|
|
||||||
|
|
||||||
wk.send({ type: 'process-data', name: name, data: body });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**上传前检查或转换压缩 */
|
/**上传前检查或转换压缩 */
|
||||||
function fnBeforeUpload(file: FileType) {
|
function fnBeforeUpload(file: FileType) {
|
||||||
@@ -229,100 +38,8 @@ function fnBeforeUpload(file: FileType) {
|
|||||||
|
|
||||||
/**表单上传文件 */
|
/**表单上传文件 */
|
||||||
function fnUpload(up: UploadRequestOption) {
|
function fnUpload(up: UploadRequestOption) {
|
||||||
state.summary = {
|
handleLoadFile(up.file as File);
|
||||||
filename: '',
|
|
||||||
file_type: 'Wireshark/tcpdump/... - pcap',
|
|
||||||
file_length: 0,
|
|
||||||
file_encap_type: 'Ethernet',
|
|
||||||
packet_count: 0,
|
|
||||||
start_time: 0,
|
|
||||||
stop_time: 0,
|
|
||||||
elapsed_time: 0,
|
|
||||||
};
|
|
||||||
state.finishedProcessing = false;
|
|
||||||
|
|
||||||
wk.send({ type: 'process', file: up.file });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**接收数据后回调 */
|
|
||||||
function wkMessage(res: Record<string, any>) {
|
|
||||||
switch (res.type) {
|
|
||||||
case 'status':
|
|
||||||
console.info(res.status);
|
|
||||||
break;
|
|
||||||
case 'error':
|
|
||||||
console.warn(res.error);
|
|
||||||
break;
|
|
||||||
case 'init':
|
|
||||||
wk.send({ type: 'columns' });
|
|
||||||
state.initialized = true;
|
|
||||||
break;
|
|
||||||
case 'columns':
|
|
||||||
state.columns = res.data;
|
|
||||||
break;
|
|
||||||
case 'frames':
|
|
||||||
// console.log(res.data);
|
|
||||||
const { matched, frames } = res.data;
|
|
||||||
state.totalFrames = matched;
|
|
||||||
|
|
||||||
if (state.nextPageNum == 1) {
|
|
||||||
state.packetFrames = frames;
|
|
||||||
// 有匹配的选择第一个
|
|
||||||
if (frames.length > 0) {
|
|
||||||
state.selectedFrame = frames[0].number;
|
|
||||||
fnSelectedFrame(state.selectedFrame);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
state.packetFrames = state.packetFrames.concat(frames);
|
|
||||||
state.nextPageLoad = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'selected':
|
|
||||||
state.selectedPacket = res.data;
|
|
||||||
state.packetFrameData = parseFrameData('root', res.data);
|
|
||||||
state.selectedTreeEntry = NO_SELECTION;
|
|
||||||
state.selectedDataSourceIndex = 0;
|
|
||||||
break;
|
|
||||||
case 'processed':
|
|
||||||
// setStatus(`Error: non-zero return code (${e.data.code})`);
|
|
||||||
state.finishedProcessing = true;
|
|
||||||
if (res.data.code === 0) {
|
|
||||||
state.summary = res.data.summary;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载数据
|
|
||||||
fnStateReset();
|
|
||||||
fnLoaldFrames(state.filter);
|
|
||||||
break;
|
|
||||||
case 'filter':
|
|
||||||
const filterRes = res.data;
|
|
||||||
if (filterRes.ok) {
|
|
||||||
state.currentFilter = state.filter;
|
|
||||||
state.filterError = null;
|
|
||||||
// 加载数据
|
|
||||||
fnStateReset();
|
|
||||||
fnLoaldFrames(state.filter);
|
|
||||||
} else {
|
|
||||||
state.filterError = filterRes.error;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log(res);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
// 建立链接
|
|
||||||
const options: OptionsType = {
|
|
||||||
url: scriptUrl,
|
|
||||||
onmessage: wkMessage,
|
|
||||||
onerror: (ev: any) => {
|
|
||||||
console.error(ev);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
wk.connect(options);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -345,7 +62,7 @@ onMounted(() => {
|
|||||||
>
|
>
|
||||||
<a-button type="primary"> Upload </a-button>
|
<a-button type="primary"> Upload </a-button>
|
||||||
</a-upload>
|
</a-upload>
|
||||||
<a-button @click="fnLoadExample">Example</a-button>
|
<a-button @click="handleLoadExample()">Example</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
|
|
||||||
<div class="toolbar-info">
|
<div class="toolbar-info">
|
||||||
@@ -396,7 +113,7 @@ onMounted(() => {
|
|||||||
placeholder="display filter, example: tcp"
|
placeholder="display filter, example: tcp"
|
||||||
:allow-clear="true"
|
:allow-clear="true"
|
||||||
style="width: calc(100% - 100px)"
|
style="width: calc(100% - 100px)"
|
||||||
@pressEnter="fnFilterFrames"
|
@pressEnter="handleFilterFrames"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<FilterOutlined />
|
<FilterOutlined />
|
||||||
@@ -406,7 +123,7 @@ onMounted(() => {
|
|||||||
type="primary"
|
type="primary"
|
||||||
html-type="submit"
|
html-type="submit"
|
||||||
style="width: 100px"
|
style="width: 100px"
|
||||||
@click="fnFilterFrames"
|
@click="handleFilterFrames"
|
||||||
>
|
>
|
||||||
Filter
|
Filter
|
||||||
</a-button>
|
</a-button>
|
||||||
@@ -422,8 +139,8 @@ onMounted(() => {
|
|||||||
:columns="state.columns"
|
:columns="state.columns"
|
||||||
:data="state.packetFrames"
|
:data="state.packetFrames"
|
||||||
:selectedFrame="state.selectedFrame"
|
:selectedFrame="state.selectedFrame"
|
||||||
:onSelectedFrame="fnSelectedFrame"
|
:onSelectedFrame="handleSelectedFrame"
|
||||||
:onScrollBottom="fnScrollBottom"
|
:onScrollBottom="handleScrollBottom"
|
||||||
></PacketTable>
|
></PacketTable>
|
||||||
|
|
||||||
<a-row :gutter="20">
|
<a-row :gutter="20">
|
||||||
@@ -431,7 +148,7 @@ onMounted(() => {
|
|||||||
<!-- 帧数据 -->
|
<!-- 帧数据 -->
|
||||||
<DissectionTree
|
<DissectionTree
|
||||||
id="root"
|
id="root"
|
||||||
:select="fnSelectedTreeEntry"
|
:select="handleSelectedTreeEntry"
|
||||||
:selected="state.selectedTreeEntry"
|
:selected="state.selectedTreeEntry"
|
||||||
:tree="state.selectedPacket.tree"
|
:tree="state.selectedPacket.tree"
|
||||||
/>
|
/>
|
||||||
@@ -451,7 +168,7 @@ onMounted(() => {
|
|||||||
>
|
>
|
||||||
<DissectionDump
|
<DissectionDump
|
||||||
:base64="v.data"
|
:base64="v.data"
|
||||||
:select="(pos:number)=>fnSelectedFindSelection(idx, pos)"
|
:select="(pos:number)=>handleSelectedFindSelection(idx, pos)"
|
||||||
:selected="
|
:selected="
|
||||||
idx === state.selectedTreeEntry.idx
|
idx === state.selectedTreeEntry.idx
|
||||||
? state.selectedTreeEntry
|
? state.selectedTreeEntry
|
||||||
|
|||||||
@@ -1,16 +1,513 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, onMounted, toRaw } from 'vue';
|
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue';
|
||||||
import { PageContainer } from 'antdv-pro-layout';
|
import { PageContainer } from 'antdv-pro-layout';
|
||||||
|
import { message, Modal } from 'ant-design-vue/lib';
|
||||||
|
import DissectionTree from '../tshark/components/DissectionTree.vue';
|
||||||
|
import DissectionDump from '../tshark/components/DissectionDump.vue';
|
||||||
|
import PacketTable from '../tshark/components/PacketTable.vue';
|
||||||
|
import {
|
||||||
|
RESULT_CODE_ERROR,
|
||||||
|
RESULT_CODE_SUCCESS,
|
||||||
|
} from '@/constants/result-constants';
|
||||||
|
import { filePullTask } from '@/api/trace/task';
|
||||||
|
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import saveAs from 'file-saver';
|
||||||
|
import {
|
||||||
|
packetDevices,
|
||||||
|
packetStart,
|
||||||
|
packetStop,
|
||||||
|
packetFilter,
|
||||||
|
} from '@/api/trace/packet';
|
||||||
|
import { s } from 'vite/dist/node/types.d-aGj9QkWt';
|
||||||
|
const ws = new WS();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
onMounted(() => {});
|
const NO_SELECTION = { id: '', idx: 0, start: 0, length: 0 };
|
||||||
|
|
||||||
|
type StateType = {
|
||||||
|
/**网卡设备列表 */
|
||||||
|
devices: { id: string; label: string; children: any[] }[];
|
||||||
|
/**初始化 */
|
||||||
|
initialized: boolean;
|
||||||
|
/**任务 */
|
||||||
|
task: {
|
||||||
|
taskNo: string;
|
||||||
|
device: string;
|
||||||
|
filter: string;
|
||||||
|
outputPCAP: boolean;
|
||||||
|
};
|
||||||
|
/**字段 */
|
||||||
|
columns: string[];
|
||||||
|
|
||||||
|
/**过滤条件 */
|
||||||
|
filter: string;
|
||||||
|
/**过滤条件错误信息 */
|
||||||
|
filterError: string | null;
|
||||||
|
|
||||||
|
/**当前选中的帧编号 */
|
||||||
|
selectedFrame: number;
|
||||||
|
/**当前选中的帧数据 */
|
||||||
|
packetFrame: { tree: any[]; data_sources: any[] };
|
||||||
|
/**pcap包帧数据 */
|
||||||
|
packetFrameTreeMap: Map<string, any> | null;
|
||||||
|
/**当前选中的帧数据 */
|
||||||
|
selectedTree: typeof NO_SELECTION;
|
||||||
|
/**选择帧的Dump数据标签 */
|
||||||
|
selectedDataSourceIndex: number;
|
||||||
|
|
||||||
|
/**包总数 */
|
||||||
|
totalPackets: number;
|
||||||
|
/**包数据 */
|
||||||
|
packetList: any[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = reactive<StateType>({
|
||||||
|
devices: [],
|
||||||
|
initialized: false,
|
||||||
|
task: {
|
||||||
|
taskNo: 'laYlTbq',
|
||||||
|
device: '192.168.5.58',
|
||||||
|
filter: 'tcp and (port 33030 or 8080)',
|
||||||
|
outputPCAP: false,
|
||||||
|
},
|
||||||
|
columns: [
|
||||||
|
'No.',
|
||||||
|
'Time',
|
||||||
|
'Source',
|
||||||
|
'Destination',
|
||||||
|
'Protocol',
|
||||||
|
'Length',
|
||||||
|
'Info',
|
||||||
|
],
|
||||||
|
filter: 'tcp and (port 33030 or 8080)',
|
||||||
|
filterError: null,
|
||||||
|
selectedFrame: 1,
|
||||||
|
/**当前选中的帧数据 */
|
||||||
|
packetFrame: { tree: [], data_sources: [] },
|
||||||
|
packetFrameTreeMap: null, // 注意:Map 需要额外处理
|
||||||
|
selectedTree: NO_SELECTION, // NO_SELECTION 需要定义
|
||||||
|
/**选择帧的Dump数据标签 */
|
||||||
|
selectedDataSourceIndex: 0,
|
||||||
|
// 包数据
|
||||||
|
totalPackets: 0,
|
||||||
|
packetList: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**清除帧数据和报文信息状态 */
|
||||||
|
function fnReset() {
|
||||||
|
state.initialized = false;
|
||||||
|
// 选择帧的数据
|
||||||
|
state.selectedFrame = 0;
|
||||||
|
state.packetFrame = { tree: [], data_sources: [] };
|
||||||
|
state.packetFrameTreeMap = null;
|
||||||
|
state.selectedTree = NO_SELECTION;
|
||||||
|
state.selectedDataSourceIndex = 0;
|
||||||
|
// 过滤条件
|
||||||
|
state.filter = 'tcp and (port 33030 or 8080)';
|
||||||
|
state.filterError = null;
|
||||||
|
// 包数据
|
||||||
|
state.totalPackets = 0;
|
||||||
|
state.packetList = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**解析帧数据为简单结构 */
|
||||||
|
function parseFrameTree(id: string, node: Record<string, any>) {
|
||||||
|
let map = new Map();
|
||||||
|
|
||||||
|
if (node.tree && node.tree.length > 0) {
|
||||||
|
for (let i = 0; i < node.tree.length; i++) {
|
||||||
|
const subMap = parseFrameTree(`${id}-${i}`, node.tree[i]);
|
||||||
|
subMap.forEach((value, key) => {
|
||||||
|
map.set(key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (node.length > 0) {
|
||||||
|
map.set(id, {
|
||||||
|
id: id,
|
||||||
|
idx: node.data_source_idx,
|
||||||
|
start: node.start,
|
||||||
|
length: node.length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**帧数据点击选中 */
|
||||||
|
function handleSelectedTreeEntry(e: any) {
|
||||||
|
console.log('fnSelectedTreeEntry', e);
|
||||||
|
state.selectedTree = e;
|
||||||
|
}
|
||||||
|
/**报文数据点击选中 */
|
||||||
|
function handleSelectedFindSelection(src_idx: number, pos: number) {
|
||||||
|
console.log('fnSelectedFindSelection', pos);
|
||||||
|
if (state.packetFrameTreeMap == null) return;
|
||||||
|
// find the smallest one
|
||||||
|
let current = null;
|
||||||
|
for (let [k, pp] of state.packetFrameTreeMap) {
|
||||||
|
if (pp.idx !== src_idx) continue;
|
||||||
|
|
||||||
|
if (pos >= pp.start && pos <= pp.start + pp.length) {
|
||||||
|
if (
|
||||||
|
current != null &&
|
||||||
|
state.packetFrameTreeMap.get(current).length > pp.length
|
||||||
|
) {
|
||||||
|
current = k;
|
||||||
|
} else {
|
||||||
|
current = k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (current != null) {
|
||||||
|
state.selectedTree = state.packetFrameTreeMap.get(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**包数据表点击选中 */
|
||||||
|
function handleSelectedFrame(num: number) {
|
||||||
|
console.log('fnSelectedFrame', num, state.totalPackets);
|
||||||
|
const packet = state.packetList.find((v: any) => v.number === num);
|
||||||
|
if (!packet) return;
|
||||||
|
const packetFrame = packet.frame;
|
||||||
|
state.selectedFrame = packet.number;
|
||||||
|
state.packetFrame = packetFrame;
|
||||||
|
state.packetFrameTreeMap = parseFrameTree('root', packetFrame);
|
||||||
|
state.selectedTree = NO_SELECTION;
|
||||||
|
state.selectedDataSourceIndex = 0;
|
||||||
|
}
|
||||||
|
/**包数据表滚动底部加载 */
|
||||||
|
function handleScrollBottom(index: any) {
|
||||||
|
console.log('handleScrollBottom', index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**开始跟踪 */
|
||||||
|
function fnStart() {
|
||||||
|
// state.task.taskNo = 'laYlTbq';
|
||||||
|
state.task.taskNo = Number(Date.now()).toString(16);
|
||||||
|
state.task.outputPCAP = false;
|
||||||
|
packetStart(state.task).then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
fnReset();
|
||||||
|
fnWS();
|
||||||
|
} else {
|
||||||
|
message.error(t('common.operateErr'), 3);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**停止跟踪 */
|
||||||
|
function fnStop() {
|
||||||
|
packetStop(state.task.taskNo).then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
ws.close();
|
||||||
|
state.initialized = false;
|
||||||
|
state.filter = '';
|
||||||
|
state.filterError = null;
|
||||||
|
} else {
|
||||||
|
message.warning(res.msg, 3);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**跟踪数据表过滤 */
|
||||||
|
function handleFilterFrames() {
|
||||||
|
packetFilter(state.task.taskNo, state.filter).then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
state.task.filter = state.filter;
|
||||||
|
} else {
|
||||||
|
state.filterError = res.msg;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**开始跟踪 */
|
||||||
|
function fnDevice(v: string) {
|
||||||
|
state.task.device = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**下载触发等待 */
|
||||||
|
let downLoading = ref<boolean>(false);
|
||||||
|
|
||||||
|
/**信息文件下载 */
|
||||||
|
function fnDownloadPCAP() {
|
||||||
|
if (downLoading.value) return;
|
||||||
|
const fileName = `trace_packet_${state.task.taskNo}.pcap`;
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.logManage.neFile.downTip', { fileName }),
|
||||||
|
onOk() {
|
||||||
|
downLoading.value = true;
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
filePullTask(state.task.taskNo)
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('common.msgSuccess', {
|
||||||
|
msg: t('common.downloadText'),
|
||||||
|
}),
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
saveAs(res.data, `${fileName}`);
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: t('views.logManage.neFile.downTipErr'),
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
downLoading.value = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**接收数据后回调 */
|
||||||
|
function wsMessage(res: Record<string, any>) {
|
||||||
|
const { code, requestId, data } = res;
|
||||||
|
if (code === RESULT_CODE_ERROR) {
|
||||||
|
console.warn(res.msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 建联时发送请求
|
||||||
|
if (!requestId && data.clientId) {
|
||||||
|
state.initialized = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订阅组信息
|
||||||
|
if (!data?.groupId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.groupId === `4_${state.task.taskNo}`) {
|
||||||
|
const packetData = data.data;
|
||||||
|
state.totalPackets = packetData.number;
|
||||||
|
state.packetList.push(packetData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**建立WS连接 */
|
||||||
|
function fnWS() {
|
||||||
|
const options: OptionsType = {
|
||||||
|
url: '/ws',
|
||||||
|
params: {
|
||||||
|
/**订阅通道组
|
||||||
|
*
|
||||||
|
* 信令跟踪Packet (GroupID:4_taskNo)
|
||||||
|
*/
|
||||||
|
subGroupID: `4_${state.task.taskNo}`,
|
||||||
|
},
|
||||||
|
onmessage: wsMessage,
|
||||||
|
onerror: (ev: any) => {
|
||||||
|
// 接收数据后回调
|
||||||
|
console.error(ev);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
//建立连接
|
||||||
|
ws.connect(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
packetDevices().then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
state.devices = res.data;
|
||||||
|
if (res.data.length === 0) return;
|
||||||
|
state.task.device = res.data[0].id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
ws.close();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
<a-card :bordered="false" :body-style="{ padding: '12px' }">
|
||||||
<h1>JS</h1>
|
<div class="toolbar">
|
||||||
|
<a-space :size="8" class="toolbar-oper">
|
||||||
|
<a-dropdown-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="state.initialized"
|
||||||
|
@click="fnStart"
|
||||||
|
>
|
||||||
|
<PlayCircleOutlined />
|
||||||
|
Start Trace
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu
|
||||||
|
@click="({ key }:any) => fnDevice(key)"
|
||||||
|
:selectedKeys="[state.task.device]"
|
||||||
|
>
|
||||||
|
<a-menu-item v-for="v in state.devices" :key="v.id">
|
||||||
|
<a-popover placement="rightTop" trigger="hover">
|
||||||
|
<template #content>
|
||||||
|
<div v-for="c in v.children">{{ c.id }}</div>
|
||||||
|
</template>
|
||||||
|
<template #title>
|
||||||
|
<span>IP Address</span>
|
||||||
|
</template>
|
||||||
|
<div>{{ v.label }}</div>
|
||||||
|
</a-popover>
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
<template #icon><DownOutlined /></template>
|
||||||
|
</a-dropdown-button>
|
||||||
|
|
||||||
|
<a-button danger @click.prevent="fnStop()" v-if="state.initialized">
|
||||||
|
<template #icon><CloseCircleOutlined /></template>
|
||||||
|
Stop Trace
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
:loading="downLoading"
|
||||||
|
@click.prevent="fnDownloadPCAP()"
|
||||||
|
>
|
||||||
|
<template #icon><DownloadOutlined /></template>
|
||||||
|
{{ t('common.downloadText') }}
|
||||||
|
</a-button>
|
||||||
|
<a-tag
|
||||||
|
color="green"
|
||||||
|
v-show="!!state.task.filter && state.initialized"
|
||||||
|
>
|
||||||
|
{{ state.task.filter }}
|
||||||
|
</a-tag>
|
||||||
|
</a-space>
|
||||||
|
|
||||||
|
<a-space :size="8" class="toolbar-info">
|
||||||
|
<span>
|
||||||
|
{{ t('views.traceManage.task.traceId') }}:
|
||||||
|
<strong>{{ state.task.taskNo }}</strong>
|
||||||
|
</span>
|
||||||
|
<span> Packets: {{ state.totalPackets }} </span>
|
||||||
|
</a-space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 包数据表过滤 -->
|
||||||
|
<a-input-group compact v-show="state.initialized">
|
||||||
|
<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.packetList"
|
||||||
|
:selectedFrame="state.selectedFrame"
|
||||||
|
:onSelectedFrame="handleSelectedFrame"
|
||||||
|
:onScrollBottom="handleScrollBottom"
|
||||||
|
></PacketTable>
|
||||||
|
|
||||||
|
<a-row :gutter="20">
|
||||||
|
<a-col :lg="12" :md="12" :xs="24" class="tree">
|
||||||
|
<!-- 帧数据 -->
|
||||||
|
<DissectionTree
|
||||||
|
id="root"
|
||||||
|
:select="handleSelectedTreeEntry"
|
||||||
|
: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>
|
</a-card>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
<style scoped>
|
||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.toolbar-oper {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.toolbar-info {
|
||||||
|
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>
|
||||||
|
|||||||
Reference in New Issue
Block a user