Merge remote-tracking branch 'origin/lichang'
This commit is contained in:
@@ -30,6 +30,7 @@
|
|||||||
"dayjs": "^1.11.11",
|
"dayjs": "^1.11.11",
|
||||||
"echarts": "~5.5.0",
|
"echarts": "~5.5.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
|
"grid-layout-plus": "^1.0.5",
|
||||||
"intl-tel-input": "^23.8.1",
|
"intl-tel-input": "^23.8.1",
|
||||||
"js-base64": "^3.7.7",
|
"js-base64": "^3.7.7",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
@@ -41,7 +42,6 @@
|
|||||||
"vue-i18n": "^9.13.1",
|
"vue-i18n": "^9.13.1",
|
||||||
"vue-router": "^4.4.0",
|
"vue-router": "^4.4.0",
|
||||||
"vue3-smooth-dnd": "^0.0.6",
|
"vue3-smooth-dnd": "^0.0.6",
|
||||||
"vuedraggable": "^4.1.0",
|
|
||||||
"xlsx": "~0.18.5"
|
"xlsx": "~0.18.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
20
src/api/tool/iperf.ts
Normal file
20
src/api/tool/iperf.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { request } from '@/plugins/http-fetch';
|
||||||
|
|
||||||
|
// iperf 版本信息
|
||||||
|
export function iperfV(data: Record<string, string>) {
|
||||||
|
return request({
|
||||||
|
url: '/tool/iperf/v',
|
||||||
|
method: 'get',
|
||||||
|
params: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// iperf 软件安装
|
||||||
|
export function iperfI(data: Record<string, string>) {
|
||||||
|
return request({
|
||||||
|
url: '/tool/iperf/i',
|
||||||
|
method: 'post',
|
||||||
|
data: data,
|
||||||
|
timeout: 60_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { request } from '@/plugins/http-fetch';
|
import { request } from '@/plugins/http-fetch';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询文件列表列表
|
* 查询网元端文件列表
|
||||||
* @param query 查询参数
|
* @param query 查询参数
|
||||||
* @returns object
|
* @returns object
|
||||||
*/
|
*/
|
||||||
@@ -14,7 +14,7 @@ export function listNeFiles(query: Record<string, any>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从网元端获取文件
|
* 从网元到本地获取文件
|
||||||
* @param query 查询参数
|
* @param query 查询参数
|
||||||
* @returns object
|
* @returns object
|
||||||
*/
|
*/
|
||||||
@@ -27,3 +27,24 @@ export function getNeFile(query: Record<string, any>) {
|
|||||||
timeout: 180_000,
|
timeout: 180_000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从网元到本地获取目录压缩为ZIP
|
||||||
|
export function getNeDirZip(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: '/ne/action/pullDirZip',
|
||||||
|
method: 'get',
|
||||||
|
params: data,
|
||||||
|
responseType: 'blob',
|
||||||
|
timeout: 60_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看网元端文件内容
|
||||||
|
export function getNeViewFile(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: '/ne/action/viewFile',
|
||||||
|
method: 'get',
|
||||||
|
params: data,
|
||||||
|
timeout: 60_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
10
src/api/tool/ping.ts
Normal file
10
src/api/tool/ping.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { request } from '@/plugins/http-fetch';
|
||||||
|
|
||||||
|
// ping 网元端版本信息
|
||||||
|
export function pingV(data: Record<string, string>) {
|
||||||
|
return request({
|
||||||
|
url: '/tool/ping/v',
|
||||||
|
method: 'get',
|
||||||
|
params: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -20,17 +20,6 @@ export function dumpStop(data: Record<string, string>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 网元抓包PACP 下载
|
|
||||||
export function dumpDownload(data: Record<string, any>) {
|
|
||||||
return request({
|
|
||||||
url: '/trace/tcpdump/download',
|
|
||||||
method: 'get',
|
|
||||||
params: data,
|
|
||||||
responseType: 'blob',
|
|
||||||
timeout: 60_000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// UPF标准版内部抓包
|
// UPF标准版内部抓包
|
||||||
export function traceUPF(data: Record<string, string>) {
|
export function traceUPF(data: Record<string, string>) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ const props = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
/**ws连接地址,必传 如/ws/view */
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
/**网元类型,必传 */
|
/**网元类型,必传 */
|
||||||
neType: {
|
neType: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -33,6 +38,11 @@ const props = defineProps({
|
|||||||
type: Number,
|
type: Number,
|
||||||
default: 40,
|
default: 40,
|
||||||
},
|
},
|
||||||
|
/**ws发送requestId前缀 如ssh_id */
|
||||||
|
prefix: {
|
||||||
|
type: String,
|
||||||
|
default: 'ssh',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**终端输入DOM节点实例对象 */
|
/**终端输入DOM节点实例对象 */
|
||||||
@@ -148,13 +158,18 @@ function wsMessage(res: Record<string, any>) {
|
|||||||
if (parts.length > 0) {
|
if (parts.length > 0) {
|
||||||
let text = parts[parts.length - 1];
|
let text = parts[parts.length - 1];
|
||||||
// 找到最后输出标记
|
// 找到最后输出标记
|
||||||
const lestIndex = text.lastIndexOf('\u001b[?2004h\u001b]0;');
|
let lestIndex = text.lastIndexOf('\u001b[?2004h\u001b]0;');
|
||||||
if (lestIndex !== -1) {
|
if (lestIndex !== -1) {
|
||||||
text = text.substring(0, lestIndex);
|
text = text.substring(0, lestIndex);
|
||||||
}
|
}
|
||||||
if (text === '' || text === '\r\n' || text.startsWith("^C\r\n") ) {
|
if (text === '' || text === '\r\n' || text.startsWith('^C\r\n')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 是否还有最后输出标记
|
||||||
|
lestIndex = text.lastIndexOf('\u001b[?2004h');
|
||||||
|
if (lestIndex !== -1) {
|
||||||
|
text = text.substring(0, lestIndex);
|
||||||
|
}
|
||||||
// console.log({ parts, text });
|
// console.log({ parts, text });
|
||||||
terminal.value.write(text);
|
terminal.value.write(text);
|
||||||
return;
|
return;
|
||||||
@@ -168,7 +183,7 @@ onMounted(() => {
|
|||||||
if (props.neType && props.neId) {
|
if (props.neType && props.neId) {
|
||||||
// 建立链接
|
// 建立链接
|
||||||
const options: OptionsType = {
|
const options: OptionsType = {
|
||||||
url: '/ws/view',
|
url: props.url,
|
||||||
params: {
|
params: {
|
||||||
neType: props.neType,
|
neType: props.neType,
|
||||||
neId: props.neId,
|
neId: props.neId,
|
||||||
@@ -185,7 +200,7 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
ws.close();
|
if (ws.state() === WebSocket.OPEN) ws.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 给组件设置属性 ref="xxxTerminal"
|
// 给组件设置属性 ref="xxxTerminal"
|
||||||
@@ -200,7 +215,7 @@ defineExpose({
|
|||||||
/**发送命令 */
|
/**发送命令 */
|
||||||
send: (type: string, data: Record<string, any>) => {
|
send: (type: string, data: Record<string, any>) => {
|
||||||
ws.send({
|
ws.send({
|
||||||
requestId: `ssh_${props.id}`,
|
requestId: `${props.prefix}_${props.id}`,
|
||||||
type,
|
type,
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
@@ -208,7 +223,7 @@ defineExpose({
|
|||||||
/**模拟按下 Ctrl+C */
|
/**模拟按下 Ctrl+C */
|
||||||
ctrlC: () => {
|
ctrlC: () => {
|
||||||
ws.send({
|
ws.send({
|
||||||
requestId: `ssh_${props.id}`,
|
requestId: `${props.prefix}_${props.id}`,
|
||||||
type: 'ctrl-c',
|
type: 'ctrl-c',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -685,11 +685,12 @@ export default {
|
|||||||
addrPlease: "Please fill in the host IP address correctly",
|
addrPlease: "Please fill in the host IP address correctly",
|
||||||
port: "Port",
|
port: "Port",
|
||||||
portPlease: "Please fill in the host port number correctly",
|
portPlease: "Please fill in the host port number correctly",
|
||||||
user: "Login User",
|
user: "User",
|
||||||
userPlease: "Please fill in the host login user correctly",
|
userPlease: "Please fill in the host user correctly",
|
||||||
|
database: "DataBase",
|
||||||
authMode: "Auth Mode",
|
authMode: "Auth Mode",
|
||||||
password: "Password",
|
password: "Password",
|
||||||
passwordPlease: "Please fill in the host login password correctly",
|
passwordPlease: "Please fill in the host password correctly",
|
||||||
privateKey: "Private Key",
|
privateKey: "Private Key",
|
||||||
privateKeyPlease: "Please fill in the private key characters correctly ~/.ssh/id_rsa",
|
privateKeyPlease: "Please fill in the private key characters correctly ~/.ssh/id_rsa",
|
||||||
passPhrase: "Private Key Cipher",
|
passPhrase: "Private Key Cipher",
|
||||||
@@ -1110,8 +1111,8 @@ export default {
|
|||||||
fileUPFTip: 'UPF internal packet capture and analysis packet',
|
fileUPFTip: 'UPF internal packet capture and analysis packet',
|
||||||
textStart: "Start",
|
textStart: "Start",
|
||||||
textStop: "Stop",
|
textStop: "Stop",
|
||||||
textLog: "Log",
|
textLog: "LogFile",
|
||||||
textLogMsg: "Log Info",
|
textLogMsg: "LogFile Info",
|
||||||
textDown: "Download",
|
textDown: "Download",
|
||||||
downTip: "Are you sure you want to download the {title} capture data file?",
|
downTip: "Are you sure you want to download the {title} capture data file?",
|
||||||
downOk: "{title} file download complete",
|
downOk: "{title} file download complete",
|
||||||
|
|||||||
@@ -686,10 +686,11 @@ export default {
|
|||||||
port: "端口",
|
port: "端口",
|
||||||
portPlease: "请正确填写主机端口号",
|
portPlease: "请正确填写主机端口号",
|
||||||
user: "用户名",
|
user: "用户名",
|
||||||
userPlease: "请正确填写主机登录用户",
|
userPlease: "请正确填写主机用户",
|
||||||
|
database: "数据库",
|
||||||
authMode: "认证模式",
|
authMode: "认证模式",
|
||||||
password: "密码",
|
password: "密码",
|
||||||
passwordPlease: "请正确填写主机登录密码",
|
passwordPlease: "请正确填写主机密码",
|
||||||
privateKey: "私钥",
|
privateKey: "私钥",
|
||||||
privateKeyPlease: "请正确填写私钥字符内容 ~/.ssh/id_rsa",
|
privateKeyPlease: "请正确填写私钥字符内容 ~/.ssh/id_rsa",
|
||||||
passPhrase: "私钥密码",
|
passPhrase: "私钥密码",
|
||||||
@@ -1110,8 +1111,8 @@ export default {
|
|||||||
fileUPFTip: 'UPF内部抓包分析包',
|
fileUPFTip: 'UPF内部抓包分析包',
|
||||||
textStart: "开始",
|
textStart: "开始",
|
||||||
textStop: "停止",
|
textStop: "停止",
|
||||||
textLog: "日志",
|
textLog: "日志文件",
|
||||||
textLogMsg: "日志信息",
|
textLogMsg: "日志文件信息",
|
||||||
textDown: "下载",
|
textDown: "下载",
|
||||||
downTip: "确认要下载 {title} 抓包数据文件吗?",
|
downTip: "确认要下载 {title} 抓包数据文件吗?",
|
||||||
downOk: "{title} 文件下载完成",
|
downOk: "{title} 文件下载完成",
|
||||||
|
|||||||
@@ -187,7 +187,6 @@ function beforeRequest(options: OptionsType): OptionsType | Promise<any> {
|
|||||||
const separator = options.url.includes('?') ? '&' : '?';
|
const separator = options.url.includes('?') ? '&' : '?';
|
||||||
// 请求加密
|
// 请求加密
|
||||||
if (options.crypto) {
|
if (options.crypto) {
|
||||||
debugger;
|
|
||||||
const data = encryptAES(JSON.stringify(paramStr), APP_DATA_API_KEY);
|
const data = encryptAES(JSON.stringify(paramStr), APP_DATA_API_KEY);
|
||||||
options.url += `${separator}data=${encodeURIComponent(data)}`;
|
options.url += `${separator}data=${encodeURIComponent(data)}`;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -173,13 +173,13 @@ export function parseSizeFromKbs(sizeByte: number, timeInterval: number): any {
|
|||||||
/**
|
/**
|
||||||
* 字节数转换单位
|
* 字节数转换单位
|
||||||
* @param bits 字节Bit大小 64009540 = 512.08 MB
|
* @param bits 字节Bit大小 64009540 = 512.08 MB
|
||||||
* @returns xx B/ KB / MB / GB / TB
|
* @returns xx B / KB / MB / GB / TB / PB / EB / ZB / YB
|
||||||
*/
|
*/
|
||||||
export function parseSizeFromBits(bits: number | string): string {
|
export function parseSizeFromBits(bits: number | string): string {
|
||||||
bits = Number(bits) || 0;
|
bits = Number(bits) || 0;
|
||||||
if (bits <= 0) return '0 B';
|
if (bits <= 0) return '0 B';
|
||||||
bits = bits * 8;
|
bits = bits * 8;
|
||||||
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||||
const unitIndex = Math.floor(Math.log2(bits) / 10);
|
const unitIndex = Math.floor(Math.log2(bits) / 10);
|
||||||
const value = (bits / Math.pow(1000, unitIndex)).toFixed(2);
|
const value = (bits / Math.pow(1000, unitIndex)).toFixed(2);
|
||||||
const unti = units[unitIndex];
|
const unti = units[unitIndex];
|
||||||
|
|||||||
@@ -425,8 +425,6 @@ function fnGetList(pageNum?: number) {
|
|||||||
(queryParams.pageNum - 1) * tablePagination.pageSize &&
|
(queryParams.pageNum - 1) * tablePagination.pageSize &&
|
||||||
queryParams.pageNum !== 1
|
queryParams.pageNum !== 1
|
||||||
) {
|
) {
|
||||||
debugger;
|
|
||||||
|
|
||||||
tableState.loading = false;
|
tableState.loading = false;
|
||||||
fnGetList(queryParams.pageNum - 1);
|
fnGetList(queryParams.pageNum - 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export default function useWS() {
|
|||||||
// 普通信息
|
// 普通信息
|
||||||
switch (requestId) {
|
switch (requestId) {
|
||||||
// AMF_UE会话事件
|
// AMF_UE会话事件
|
||||||
case 'amf_1010_001':
|
case 'amf_1010':
|
||||||
if (Array.isArray(data.rows)) {
|
if (Array.isArray(data.rows)) {
|
||||||
eventListParse('amf_ue', data);
|
eventListParse('amf_ue', data);
|
||||||
}
|
}
|
||||||
@@ -85,7 +85,7 @@ export default function useWS() {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
// AMF_UE会话事件
|
// AMF_UE会话事件
|
||||||
case '1010_001':
|
case '1010':
|
||||||
if (data.data) {
|
if (data.data) {
|
||||||
queue.add(() => eventItemParseAndPush('amf_ue', data.data));
|
queue.add(() => eventItemParseAndPush('amf_ue', data.data));
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,7 @@ export default function useWS() {
|
|||||||
function userActivitySend() {
|
function userActivitySend() {
|
||||||
// AMF_UE会话事件
|
// AMF_UE会话事件
|
||||||
ws.send({
|
ws.send({
|
||||||
requestId: 'amf_1010_001',
|
requestId: 'amf_1010',
|
||||||
type: 'amf_ue',
|
type: 'amf_ue',
|
||||||
data: {
|
data: {
|
||||||
neType: 'AMF',
|
neType: 'AMF',
|
||||||
@@ -175,11 +175,11 @@ export default function useWS() {
|
|||||||
/**订阅通道组
|
/**订阅通道组
|
||||||
*
|
*
|
||||||
* 指标UPF (GroupID:12_neId)
|
* 指标UPF (GroupID:12_neId)
|
||||||
* AMF_UE会话事件(GroupID:1010_neId)
|
* AMF_UE会话事件(GroupID:1010)
|
||||||
* MME_UE会话事件(GroupID:1011_neId)
|
* MME_UE会话事件(GroupID:1011_neId)
|
||||||
* IMS_CDR会话事件(GroupID:1005_neId)
|
* IMS_CDR会话事件(GroupID:1005_neId)
|
||||||
*/
|
*/
|
||||||
subGroupID: '12_001,1010_001,1011_001,1005_001',
|
subGroupID: '12_001,1010,1011_001,1005_001',
|
||||||
},
|
},
|
||||||
onmessage: wsMessage,
|
onmessage: wsMessage,
|
||||||
onerror: (ev: any) => {
|
onerror: (ev: any) => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, onMounted, watch, ref, nextTick } from 'vue';
|
import { reactive, watch, ref } from 'vue';
|
||||||
import { ProModal } from 'antdv-pro-modal';
|
import { ProModal } from 'antdv-pro-modal';
|
||||||
import TerminalSSHView from '@/components/TerminalSSHView/index.vue';
|
import TerminalSSHView from '@/components/TerminalSSHView/index.vue';
|
||||||
import useI18n from '@/hooks/useI18n';
|
import useI18n from '@/hooks/useI18n';
|
||||||
@@ -86,19 +86,20 @@ function fnInit() {
|
|||||||
function fnReload() {
|
function fnReload() {
|
||||||
if (!viewTerminal.value) return;
|
if (!viewTerminal.value) return;
|
||||||
viewTerminal.value.ctrlC();
|
viewTerminal.value.ctrlC();
|
||||||
|
|
||||||
if (state.form.showType !== 'lines') {
|
if (state.form.showType !== 'lines') {
|
||||||
state.form.lines = 10;
|
state.form.lines = 10;
|
||||||
} else {
|
} else {
|
||||||
state.form.char = 0;
|
state.form.char = 0;
|
||||||
}
|
}
|
||||||
viewTerminal.value.clear();
|
viewTerminal.value.clear();
|
||||||
|
setTimeout(() => {
|
||||||
viewTerminal.value.send('tail', {
|
viewTerminal.value.send('tail', {
|
||||||
filePath: props.filePath,
|
filePath: props.filePath,
|
||||||
lines: state.form.lines,
|
lines: state.form.lines,
|
||||||
char: state.form.char,
|
char: state.form.char,
|
||||||
follow: state.form.follow,
|
follow: state.form.follow,
|
||||||
});
|
});
|
||||||
|
}, 1000);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -122,9 +123,11 @@ function fnReload() {
|
|||||||
<TerminalSSHView
|
<TerminalSSHView
|
||||||
ref="viewTerminal"
|
ref="viewTerminal"
|
||||||
:id="`V${Date.now()}`"
|
:id="`V${Date.now()}`"
|
||||||
style="height: calc(100% - 36px)"
|
prefix="tail"
|
||||||
|
url="/ws/view"
|
||||||
:ne-type="neType"
|
:ne-type="neType"
|
||||||
:ne-id="neId"
|
:ne-id="neId"
|
||||||
|
style="height: calc(100% - 36px)"
|
||||||
@connect="fnInit()"
|
@connect="fnInit()"
|
||||||
></TerminalSSHView>
|
></TerminalSSHView>
|
||||||
<!-- 命令控制属性 -->
|
<!-- 命令控制属性 -->
|
||||||
|
|||||||
@@ -204,11 +204,13 @@ function fnDirCD(dir: string, index?: number) {
|
|||||||
|
|
||||||
/**网元类型选择对应修改 */
|
/**网元类型选择对应修改 */
|
||||||
function fnNeChange(keys: any, _: any) {
|
function fnNeChange(keys: any, _: any) {
|
||||||
// 不是同类型时需要重新加载
|
if (!Array.isArray(keys)) return;
|
||||||
if (Array.isArray(keys) && queryParams.neType !== keys[0]) {
|
|
||||||
const neType = keys[0];
|
const neType = keys[0];
|
||||||
|
const neId = keys[1];
|
||||||
|
// 不是同类型时需要重新加载
|
||||||
|
if (queryParams.neType !== neType || queryParams.neId !== neId) {
|
||||||
queryParams.neType = neType;
|
queryParams.neType = neType;
|
||||||
queryParams.neId = keys[1];
|
queryParams.neId = neId;
|
||||||
if (neType === 'IMS') {
|
if (neType === 'IMS') {
|
||||||
nePathArr.value = ['/var/log/ims'];
|
nePathArr.value = ['/var/log/ims'];
|
||||||
queryParams.search = '';
|
queryParams.search = '';
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ let dict: {
|
|||||||
* 测试主机连接
|
* 测试主机连接
|
||||||
*/
|
*/
|
||||||
function fnHostTest(row: Record<string, any>) {
|
function fnHostTest(row: Record<string, any>) {
|
||||||
if (modalState.confirmLoading || !row.addr) return;
|
if (modalState.confirmLoading || !row.addr || !row.port) return;
|
||||||
modalState.confirmLoading = true;
|
modalState.confirmLoading = true;
|
||||||
const hide = message.loading(t('common.loading'), 0);
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
testNeHost(row)
|
testNeHost(row)
|
||||||
@@ -124,8 +124,8 @@ let modalState: ModalStateType = reactive({
|
|||||||
addr: '',
|
addr: '',
|
||||||
port: 22,
|
port: 22,
|
||||||
user: 'omcuser',
|
user: 'omcuser',
|
||||||
authMode: '0',
|
authMode: '2',
|
||||||
password: 'a9tU53r',
|
password: '',
|
||||||
privateKey: '',
|
privateKey: '',
|
||||||
passPhrase: '',
|
passPhrase: '',
|
||||||
remark: '',
|
remark: '',
|
||||||
@@ -283,11 +283,11 @@ function fnModalCancel() {
|
|||||||
|
|
||||||
/**表单修改网元类型 */
|
/**表单修改网元类型 */
|
||||||
function fnNeTypeChange(v: any) {
|
function fnNeTypeChange(v: any) {
|
||||||
const hostsLen = modalState.from.hosts.length;
|
|
||||||
// 网元默认只含22和4100
|
// 网元默认只含22和4100
|
||||||
if (hostsLen === 3 && v !== 'UPF') {
|
if (modalState.from.hosts.length === 3) {
|
||||||
modalState.from.hosts.pop();
|
modalState.from.hosts.pop();
|
||||||
}
|
}
|
||||||
|
const hostsLen = modalState.from.hosts.length;
|
||||||
// UPF标准版本可支持5002
|
// UPF标准版本可支持5002
|
||||||
if (hostsLen === 2 && v === 'UPF') {
|
if (hostsLen === 2 && v === 'UPF') {
|
||||||
modalState.from.hosts.push({
|
modalState.from.hosts.push({
|
||||||
@@ -295,11 +295,27 @@ function fnNeTypeChange(v: any) {
|
|||||||
hostType: 'telnet',
|
hostType: 'telnet',
|
||||||
groupId: '1',
|
groupId: '1',
|
||||||
title: 'Telnet_NE_5002',
|
title: 'Telnet_NE_5002',
|
||||||
addr: '',
|
addr: modalState.from.ip,
|
||||||
port: 5002,
|
port: 5002,
|
||||||
user: 'user',
|
user: 'admin',
|
||||||
authMode: '0',
|
authMode: '0',
|
||||||
password: 'user',
|
password: 'admin',
|
||||||
|
remark: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// UDM可支持6379
|
||||||
|
if (hostsLen === 2 && v === 'UDM') {
|
||||||
|
modalState.from.hosts.push({
|
||||||
|
hostId: undefined,
|
||||||
|
hostType: 'redis',
|
||||||
|
groupId: '1',
|
||||||
|
title: 'REDIS_NE_6379',
|
||||||
|
addr: modalState.from.ip,
|
||||||
|
port: 6379,
|
||||||
|
user: 'udmdb',
|
||||||
|
authMode: '0',
|
||||||
|
password: 'helloearth',
|
||||||
|
dbName: '0',
|
||||||
remark: '',
|
remark: '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -626,8 +642,13 @@ onMounted(() => {
|
|||||||
(s:any) => !(s.hostType === 'telnet' && modalState.from.neType === 'OMC')
|
(s:any) => !(s.hostType === 'telnet' && modalState.from.neType === 'OMC')
|
||||||
)"
|
)"
|
||||||
:key="host.title"
|
:key="host.title"
|
||||||
:header="`${host.hostType.toUpperCase()} ${host.port}`"
|
|
||||||
>
|
>
|
||||||
|
<template #header>
|
||||||
|
<span v-if="host.hostType === 'redis'"> DB {{ host.port }} </span>
|
||||||
|
<span v-else>
|
||||||
|
{{ `${host.hostType.toUpperCase()} ${host.port}` }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<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 :label="t('views.ne.neHost.addr')">
|
<a-form-item :label="t('views.ne.neHost.addr')">
|
||||||
@@ -654,7 +675,22 @@ onMounted(() => {
|
|||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
|
||||||
<a-row :gutter="16">
|
<a-form-item
|
||||||
|
v-if="host.hostType === 'telnet'"
|
||||||
|
:label="t('views.ne.neHost.user')"
|
||||||
|
:label-col="{ span: 3 }"
|
||||||
|
:label-wrap="true"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="host.user"
|
||||||
|
allow-clear
|
||||||
|
:maxlength="32"
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-row :gutter="16" v-if="host.hostType === 'ssh'">
|
||||||
<a-col :lg="12" :md="12" :xs="24">
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
<a-form-item :label="t('views.ne.neHost.user')">
|
<a-form-item :label="t('views.ne.neHost.user')">
|
||||||
<a-input
|
<a-input
|
||||||
@@ -672,7 +708,6 @@ onMounted(() => {
|
|||||||
v-model:value="host.authMode"
|
v-model:value="host.authMode"
|
||||||
default-value="0"
|
default-value="0"
|
||||||
:options="dict.neHostAuthMode"
|
:options="dict.neHostAuthMode"
|
||||||
:disabled="host.hostType === 'telnet'"
|
|
||||||
>
|
>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -692,7 +727,6 @@ onMounted(() => {
|
|||||||
>
|
>
|
||||||
</a-input-password>
|
</a-input-password>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<template v-if="host.authMode === '1'">
|
<template v-if="host.authMode === '1'">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
:label="t('views.ne.neHost.privateKey')"
|
:label="t('views.ne.neHost.privateKey')"
|
||||||
@@ -722,6 +756,21 @@ onMounted(() => {
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
v-if="host.hostType === 'mysql'"
|
||||||
|
:label="t('views.ne.neHost.database')"
|
||||||
|
:label-col="{ span: 3 }"
|
||||||
|
:label-wrap="true"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="host.dbName"
|
||||||
|
allow-clear
|
||||||
|
:maxlength="32"
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item
|
<a-form-item
|
||||||
:label="t('common.remark')"
|
:label="t('common.remark')"
|
||||||
:label-col="{ span: 3 }"
|
:label-col="{ span: 3 }"
|
||||||
@@ -736,6 +785,7 @@ onMounted(() => {
|
|||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 测试 -->
|
||||||
<a-form-item
|
<a-form-item
|
||||||
:label="t('views.ne.neHost.test')"
|
:label="t('views.ne.neHost.test')"
|
||||||
name="test"
|
name="test"
|
||||||
|
|||||||
@@ -229,11 +229,11 @@ function fnModalOk() {
|
|||||||
* 表单修改网元类型
|
* 表单修改网元类型
|
||||||
*/
|
*/
|
||||||
function fnNeTypeChange(v: any) {
|
function fnNeTypeChange(v: any) {
|
||||||
const hostsLen = modalState.from.hosts.length;
|
|
||||||
// 网元默认只含22和4100
|
// 网元默认只含22和4100
|
||||||
if (hostsLen === 3 && v !== 'UPF') {
|
if (modalState.from.hosts.length === 3) {
|
||||||
modalState.from.hosts.pop();
|
modalState.from.hosts.pop();
|
||||||
}
|
}
|
||||||
|
const hostsLen = modalState.from.hosts.length;
|
||||||
// UPF标准版本可支持5002
|
// UPF标准版本可支持5002
|
||||||
if (hostsLen === 2 && v === 'UPF') {
|
if (hostsLen === 2 && v === 'UPF') {
|
||||||
modalState.from.hosts.push({
|
modalState.from.hosts.push({
|
||||||
@@ -249,6 +249,22 @@ function fnNeTypeChange(v: any) {
|
|||||||
remark: '',
|
remark: '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// UDM可支持6379
|
||||||
|
if (hostsLen === 2 && v === 'UDM') {
|
||||||
|
modalState.from.hosts.push({
|
||||||
|
hostId: undefined,
|
||||||
|
hostType: 'redis',
|
||||||
|
groupId: '1',
|
||||||
|
title: 'REDIS_NE_6379',
|
||||||
|
addr: modalState.from.ip,
|
||||||
|
port: 6379,
|
||||||
|
user: 'udmdb',
|
||||||
|
authMode: '0',
|
||||||
|
password: 'helloearth',
|
||||||
|
dbName: '0',
|
||||||
|
remark: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -517,7 +517,7 @@ function fnExportList(type: string) {
|
|||||||
if (!neId) return;
|
if (!neId) return;
|
||||||
|
|
||||||
const hide = message.loading(t('common.loading'), 0);
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
exportUDMAuth({ ...queryParams, ...{ type } })
|
exportUDMAuth(Object.assign({ type: type }, queryParams))
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.code === RESULT_CODE_SUCCESS) {
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
message.success(t('common.msgSuccess', { msg: t('common.export') }), 3);
|
message.success(t('common.msgSuccess', { msg: t('common.export') }), 3);
|
||||||
@@ -555,6 +555,9 @@ function fnLoadData() {
|
|||||||
fnQueryReset();
|
fnQueryReset();
|
||||||
}, timerS * 1000);
|
}, timerS * 1000);
|
||||||
} else {
|
} else {
|
||||||
|
modalState.loadDataLoading = false;
|
||||||
|
tableState.loading = false; // 表格loading
|
||||||
|
fnQueryReset();
|
||||||
message.error({
|
message.error({
|
||||||
content: t('common.getInfoFail'),
|
content: t('common.getInfoFail'),
|
||||||
duration: 3,
|
duration: 3,
|
||||||
|
|||||||
@@ -878,7 +878,7 @@ function fnExportList(type: string) {
|
|||||||
if (!neId) return;
|
if (!neId) return;
|
||||||
const key = 'exportSub';
|
const key = 'exportSub';
|
||||||
message.loading({ content: t('common.loading'), key });
|
message.loading({ content: t('common.loading'), key });
|
||||||
exportUDMSub({ ...queryParams, ...{ type } }).then(res => {
|
exportUDMSub(Object.assign({ type: type }, queryParams)).then(res => {
|
||||||
if (res.code === RESULT_CODE_SUCCESS) {
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
message.success({
|
message.success({
|
||||||
content: t('common.msgSuccess', { msg: t('common.export') }),
|
content: t('common.msgSuccess', { msg: t('common.export') }),
|
||||||
@@ -920,6 +920,9 @@ function fnLoadData() {
|
|||||||
fnQueryReset();
|
fnQueryReset();
|
||||||
}, timerS * 1000);
|
}, timerS * 1000);
|
||||||
} else {
|
} else {
|
||||||
|
modalState.loadDataLoading = false;
|
||||||
|
tableState.loading = false; // 表格loading
|
||||||
|
fnQueryReset();
|
||||||
message.error({
|
message.error({
|
||||||
content: t('common.getInfoFail'),
|
content: t('common.getInfoFail'),
|
||||||
duration: 3,
|
duration: 3,
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import draggable from 'vuedraggable';
|
import { DragOutlined } from '@ant-design/icons-vue';
|
||||||
|
import { GridLayout, GridItem } from 'grid-layout-plus'
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import { PageContainer } from 'antdv-pro-layout';
|
import { PageContainer } from 'antdv-pro-layout';
|
||||||
import { onMounted, reactive, ref, markRaw, nextTick, onUnmounted } from 'vue';
|
import { onMounted, reactive, ref, markRaw, nextTick, onUnmounted, watch } from 'vue';
|
||||||
import {
|
import { RESULT_CODE_ERROR, RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
RESULT_CODE_ERROR,
|
|
||||||
RESULT_CODE_SUCCESS,
|
|
||||||
} from '@/constants/result-constants';
|
|
||||||
import { SizeType } from 'ant-design-vue/es/config-provider';
|
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||||
import { listKPIData, getKPITitle } from '@/api/perfManage/goldTarget';
|
import { listKPIData, getKPITitle } from '@/api/perfManage/goldTarget';
|
||||||
import useI18n from '@/hooks/useI18n';
|
import useI18n from '@/hooks/useI18n';
|
||||||
@@ -19,26 +16,160 @@ import { ColumnsType } from 'ant-design-vue/es/table';
|
|||||||
import { generateColorRGBA } from '@/utils/generate-utils';
|
import { generateColorRGBA } from '@/utils/generate-utils';
|
||||||
import { LineSeriesOption } from 'echarts/charts';
|
import { LineSeriesOption } from 'echarts/charts';
|
||||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||||
import { Switch } from 'ant-design-vue';
|
import { Select } from 'ant-design-vue';
|
||||||
|
|
||||||
const { t, currentLocale } = useI18n();
|
const { t, currentLocale } = useI18n();
|
||||||
|
|
||||||
const neInfoStore = useNeInfoStore();
|
const neInfoStore = useNeInfoStore();
|
||||||
|
|
||||||
//WebSocket连接
|
//WebSocket连接
|
||||||
//const ws = new WS();
|
|
||||||
const ws = ref<WS | null>(null);
|
const ws = ref<WS | null>(null);
|
||||||
|
|
||||||
//实时数据开关
|
|
||||||
const handleRealTimeSwitch = (checked: any, event: Event) => {
|
|
||||||
console.log('Switch toggled:', checked);
|
|
||||||
fnRealTimeSwitch(!!checked);
|
|
||||||
};
|
|
||||||
//添加实时数据开关状态
|
//添加实时数据开关状态
|
||||||
const realTimeEnabled = ref(false);
|
const realTimeEnabled = ref(false);
|
||||||
|
//实时数据开关
|
||||||
|
const handleRealTimeSwitch = (checked: any) => {
|
||||||
|
fnRealTimeSwitch(!!checked);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 定义所有可能的网元类型
|
||||||
|
const ALL_NE_TYPES = ['ims', 'amf', 'udm', 'smf', 'pcf','upf','mme','mocngw','smsc','cbc','ausf'] as const;
|
||||||
|
type AllChartType = typeof ALL_NE_TYPES[number];
|
||||||
|
|
||||||
|
// 使用 ref 来使 networkElementTypes 变为响应式,并使用 ALL_NE_TYPES 初始化
|
||||||
|
const networkElementTypes = ref<AllChartType[]>([...ALL_NE_TYPES]);
|
||||||
|
|
||||||
|
// 添加选择的网元类型,也使用 ALL_NE_TYPES 初始化
|
||||||
|
const selectedNeTypes = ref<AllChartType[]>([...ALL_NE_TYPES]);
|
||||||
|
|
||||||
|
// 监听 selectedNeTypes 的变化
|
||||||
|
watch(selectedNeTypes, (newTypes) => {
|
||||||
|
//console.log('Selected types changed:', newTypes);
|
||||||
|
if (JSON.stringify(newTypes) !== JSON.stringify(networkElementTypes.value)) {
|
||||||
|
networkElementTypes.value = newTypes;
|
||||||
|
|
||||||
|
// 更新 chartOrder
|
||||||
|
chartOrder.value = chartOrder.value.filter(item => newTypes.includes(item.i));
|
||||||
|
|
||||||
|
newTypes.forEach((type) => {
|
||||||
|
if (!chartOrder.value.some(item => item.i === type)) {
|
||||||
|
chartOrder.value.push({
|
||||||
|
x: (chartOrder.value.length % 2) * 6,
|
||||||
|
y: Math.floor(chartOrder.value.length / 2) * 4,
|
||||||
|
w: 6,
|
||||||
|
h: 4,
|
||||||
|
i: type,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 确保 chartStates 包含新的网元类型
|
||||||
|
if (!chartStates[type]) {
|
||||||
|
chartStates[type] = createChartState();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//console.log('Updated chartOrder:', chartOrder.value);
|
||||||
|
|
||||||
|
// 保存选中的网元类型到本地存储
|
||||||
|
localStorage.setItem('selectedNeTypes', JSON.stringify(newTypes));
|
||||||
|
|
||||||
|
// 重新初始化图表
|
||||||
|
nextTick(() => {
|
||||||
|
initCharts();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, { deep: true });
|
||||||
|
|
||||||
|
// 初始化所有图表的函数
|
||||||
|
const initCharts = async () => {
|
||||||
|
//console.log('Initializing charts for:', networkElementTypes.value);
|
||||||
|
|
||||||
|
// 清除不再需要的图表
|
||||||
|
Object.keys(chartStates).forEach((key) => {
|
||||||
|
if (!networkElementTypes.value.includes(key as AllChartType)) {
|
||||||
|
const state = chartStates[key as AllChartType];
|
||||||
|
if (state.chart.value) {
|
||||||
|
state.chart.value.dispose();
|
||||||
|
}
|
||||||
|
if (state.observer.value) {
|
||||||
|
state.observer.value.disconnect();
|
||||||
|
}
|
||||||
|
delete chartStates[key as AllChartType];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化或更新需要的图表
|
||||||
|
for (const type of networkElementTypes.value) {
|
||||||
|
//console.log('Initializing chart for:', type);
|
||||||
|
if (!chartStates[type]) {
|
||||||
|
chartStates[type] = createChartState();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await fetchKPITitle(type);
|
||||||
|
await nextTick();
|
||||||
|
initChart(type);
|
||||||
|
await fetchData(type);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error initializing chart for ${type}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log('Finished initializing charts');
|
||||||
|
|
||||||
|
// 保存更新后的布局
|
||||||
|
localStorage.setItem('chartOrder', JSON.stringify(chartOrder.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加类型定义
|
||||||
|
interface LayoutItem {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
w: number;
|
||||||
|
h: number;
|
||||||
|
i: AllChartType; // 将 ChartType 改为 AllChartType
|
||||||
|
}
|
||||||
|
|
||||||
|
type Layout = LayoutItem[];
|
||||||
|
|
||||||
// 定义图表类型
|
|
||||||
type ChartType = 'udm' | 'upf' | 'amf' | 'smf';
|
|
||||||
//构建响应式数组储存图表类型数据
|
//构建响应式数组储存图表类型数据
|
||||||
const chartOrder = ref(['udm', 'upf', 'amf', 'smf']);
|
const chartOrder = ref<Layout>(
|
||||||
|
JSON.parse(localStorage.getItem('chartOrder') || 'null') ||
|
||||||
|
networkElementTypes.value.map((type, index) => ({
|
||||||
|
x: index % 2 * 6, // 每行两个图表,宽度为6
|
||||||
|
y: Math.floor(index / 2) * 4, // 每个图表占据 4 个单位高度
|
||||||
|
w: 6, // 宽度为6单位
|
||||||
|
h: 4, // 高度为4个单位
|
||||||
|
i: type, // 使用网元类型作为唯一标识符
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
// 改变布局触发更新
|
||||||
|
const handleLayoutUpdated = (newLayout: Layout) => {
|
||||||
|
const filteredLayout = newLayout.filter(item => networkElementTypes.value.includes(item.i));
|
||||||
|
if (JSON.stringify(filteredLayout) !== JSON.stringify(chartOrder.value)) {
|
||||||
|
chartOrder.value = filteredLayout;
|
||||||
|
// 保存布局到 localStorage
|
||||||
|
localStorage.setItem('chartOrder', JSON.stringify(chartOrder.value));
|
||||||
|
nextTick(() => {
|
||||||
|
chartOrder.value.forEach((item) => {
|
||||||
|
const state = chartStates[item.i];
|
||||||
|
if (state?.chart.value) {
|
||||||
|
state.chart.value.resize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听 chartOrder 的变化
|
||||||
|
watch(chartOrder, () => {
|
||||||
|
nextTick(() => {
|
||||||
|
Object.values(chartStates).forEach(state => {
|
||||||
|
if (state.chart.value) {
|
||||||
|
state.chart.value.resize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// 定义表格状态类型
|
// 定义表格状态类型
|
||||||
type TableStateType = {
|
type TableStateType = {
|
||||||
@@ -49,10 +180,17 @@ type TableStateType = {
|
|||||||
selectedRowKeys: (string | number)[];
|
selectedRowKeys: (string | number)[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// 创建可复用的状态
|
// 创建可复用的状态
|
||||||
const createChartState = () => ({
|
const createChartState = () => {
|
||||||
chartDom: ref<HTMLElement | null>(null),
|
const chartDom = ref<HTMLElement | null>(null);
|
||||||
chart: ref<echarts.ECharts | null>(null),
|
const chart = ref<echarts.ECharts | null>(null);
|
||||||
|
const observer = ref<ResizeObserver | null>(null);
|
||||||
|
|
||||||
|
return {
|
||||||
|
chartDom,
|
||||||
|
chart,
|
||||||
|
observer,
|
||||||
tableColumns: ref<ColumnsType>([]),
|
tableColumns: ref<ColumnsType>([]),
|
||||||
tableState: reactive<TableStateType>({
|
tableState: reactive<TableStateType>({
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -64,72 +202,67 @@ const createChartState = () => ({
|
|||||||
chartLegendSelected: {} as Record<string, boolean>,
|
chartLegendSelected: {} as Record<string, boolean>,
|
||||||
chartDataXAxisData: [] as string[],
|
chartDataXAxisData: [] as string[],
|
||||||
chartDataYSeriesData: [] as CustomSeriesOption[],
|
chartDataYSeriesData: [] as CustomSeriesOption[],
|
||||||
});
|
};
|
||||||
|
|
||||||
// 为每种图表类型创建状态
|
|
||||||
const chartStates: Record<ChartType, ReturnType<typeof createChartState>> = {
|
|
||||||
udm: createChartState(),
|
|
||||||
upf: createChartState(),
|
|
||||||
amf: createChartState(),
|
|
||||||
smf: createChartState(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 为每种图表类型创建状
|
||||||
|
const chartStates: Record<AllChartType, ReturnType<typeof createChartState>> = Object.fromEntries(
|
||||||
|
networkElementTypes.value.map(type => [type, createChartState()])
|
||||||
|
) as Record<AllChartType, ReturnType<typeof createChartState>>;
|
||||||
|
|
||||||
//日期选择器
|
//日期选择器
|
||||||
interface RangePicker {
|
interface RangePicker extends Record<AllChartType, [string, string]> {
|
||||||
udm: [string, string];
|
|
||||||
upf: [string, string];
|
|
||||||
amf: [string, string];
|
|
||||||
smf: [string, string];
|
|
||||||
placeholder: [string, string];
|
placeholder: [string, string];
|
||||||
ranges: Record<string, [Dayjs, Dayjs]>;
|
ranges: Record<string, [Dayjs, Dayjs]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建日期选择器状态
|
// 创建日期选择器状态
|
||||||
const rangePicker = reactive<RangePicker>({
|
const rangePicker = reactive<RangePicker>({
|
||||||
udm: [
|
...Object.fromEntries(networkElementTypes.value.map(type => [
|
||||||
dayjs('2024-09-20 00:00:00').valueOf().toString(),
|
type,
|
||||||
dayjs('2024-09-20 23:59:59').valueOf().toString(),
|
// [
|
||||||
],
|
// dayjs('2024-09-20 00:00:00').valueOf().toString(),//拟数据的日期设2024.9.20
|
||||||
upf: [
|
// dayjs('2024-09-20 23:59:59').valueOf().toString()
|
||||||
dayjs('2024-09-20 00:00:00').valueOf().toString(),
|
// ]
|
||||||
dayjs('2024-09-20 23:59:59').valueOf().toString(),
|
[
|
||||||
],
|
dayjs().startOf('day').valueOf().toString(), // 当天 0 点 0 分 0 秒
|
||||||
amf: [
|
dayjs().valueOf().toString() // 当前时间
|
||||||
dayjs('2024-09-20 00:00:00').valueOf().toString(),
|
]
|
||||||
dayjs('2024-09-20 23:59:59').valueOf().toString(),
|
])) as Record<AllChartType, [string, string]>,
|
||||||
],
|
placeholder: [t('views.monitor.monitor.startTime'), t('views.monitor.monitor.endTime')] as [string, string],
|
||||||
smf: [
|
|
||||||
dayjs('2024-09-20 00:00:00').valueOf().toString(),
|
|
||||||
dayjs('2024-09-20 23:59:59').valueOf().toString(),
|
|
||||||
],
|
|
||||||
placeholder: [
|
|
||||||
t('views.monitor.monitor.startTime'),
|
|
||||||
t('views.monitor.monitor.endTime'),
|
|
||||||
] as [string, string],
|
|
||||||
ranges: {
|
ranges: {
|
||||||
[t('views.monitor.monitor.yesterday')]: [
|
[t('views.monitor.monitor.yesterday')]: [dayjs().subtract(1, 'day').startOf('day'), dayjs().subtract(1, 'day').endOf('day')],
|
||||||
dayjs().subtract(1, 'day').startOf('day'),
|
|
||||||
dayjs().subtract(1, 'day').endOf('day'),
|
|
||||||
],
|
|
||||||
[t('views.monitor.monitor.today')]: [dayjs().startOf('day'), dayjs()],
|
[t('views.monitor.monitor.today')]: [dayjs().startOf('day'), dayjs()],
|
||||||
[t('views.monitor.monitor.week')]: [
|
[t('views.monitor.monitor.week')]: [dayjs().startOf('week'), dayjs().endOf('week')],
|
||||||
dayjs().startOf('week'),
|
[t('views.monitor.monitor.month')]: [dayjs().startOf('month'), dayjs().endOf('month')],
|
||||||
dayjs().endOf('week'),
|
} as Record<string, [Dayjs, Dayjs]>,
|
||||||
],
|
|
||||||
[t('views.monitor.monitor.month')]: [
|
|
||||||
dayjs().startOf('month'),
|
|
||||||
dayjs().endOf('month'),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 创建可复用的图表初始化函数
|
// 创建可复用的图表初始化函数
|
||||||
const initChart = (type: ChartType) => {
|
const initChart = (type: AllChartType) => {
|
||||||
|
const tryInit = (retries = 3) => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
const state = chartStates[type];
|
const state = chartStates[type];
|
||||||
|
if (!state) {
|
||||||
|
console.warn(`Chart state for ${type} not found`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const container = state.chartDom.value;
|
const container = state.chartDom.value;
|
||||||
if (!container) return;
|
if (!container) {
|
||||||
state.chart.value = markRaw(echarts.init(container, 'light'));
|
if (retries > 0) {
|
||||||
|
console.warn(`Chart container for ${type} not found, retrying... (${retries} attempts left)`);
|
||||||
|
setTimeout(() => tryInit(retries - 1), 100);
|
||||||
|
} else {
|
||||||
|
console.error(`Chart container for ${type} not found after multiple attempts`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.chart.value) {
|
||||||
|
state.chart.value.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
state.chart.value = markRaw(echarts.init(container));
|
||||||
const option: echarts.EChartsOption = {
|
const option: echarts.EChartsOption = {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
@@ -176,29 +309,36 @@ const initChart = (type: ChartType) => {
|
|||||||
series: [],
|
series: [],
|
||||||
};
|
};
|
||||||
state.chart.value.setOption(option);
|
state.chart.value.setOption(option);
|
||||||
});
|
state.chart.value.resize(); // 确保图表正确调整大小
|
||||||
};
|
|
||||||
//结束拖拽事件
|
// 创建 ResizeObserver 实例
|
||||||
const onDragEnd = () => {
|
if (state.observer.value) {
|
||||||
nextTick(() => {
|
state.observer.value.disconnect();
|
||||||
chartOrder.value.forEach(type => {
|
}
|
||||||
const state = chartStates[type as ChartType];
|
state.observer.value = new ResizeObserver(() => {
|
||||||
if (state.chart.value) {
|
if (state.chart.value) {
|
||||||
state.chart.value.dispose(); // 销毁旧的图表实例
|
state.chart.value.resize();
|
||||||
}
|
}
|
||||||
initChart(type as ChartType);
|
|
||||||
fetchData(type as ChartType); // 重新获取数据并渲染图表
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 开始观察图表容器
|
||||||
|
state.observer.value.observe(container);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建可复用的数据获取函数
|
tryInit();
|
||||||
const fetchData = async (type: ChartType) => {
|
};
|
||||||
const state = chartStates[type];
|
|
||||||
|
|
||||||
|
|
||||||
|
// 可复用的数据获函数
|
||||||
|
const fetchData = async (type: AllChartType) => {
|
||||||
|
const state = chartStates[type]; // 直接使用 type
|
||||||
const neId = '001';
|
const neId = '001';
|
||||||
state.tableState.loading = true;
|
state.tableState.loading = true;
|
||||||
try {
|
try {
|
||||||
const [startTime, endTime] = rangePicker[type];
|
const dateRange = rangePicker[type] as [string, string];
|
||||||
|
const [startTime, endTime] = dateRange;
|
||||||
const res = await listKPIData({
|
const res = await listKPIData({
|
||||||
neType: type.toUpperCase(),
|
neType: type.toUpperCase(),
|
||||||
neId,
|
neId,
|
||||||
@@ -210,11 +350,12 @@ const fetchData = async (type: ChartType) => {
|
|||||||
});
|
});
|
||||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||||
state.tableState.data = res.data;
|
state.tableState.data = res.data;
|
||||||
nextTick(() => {
|
await nextTick(() => {
|
||||||
renderChart(type);
|
renderChart(type);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log("123")
|
||||||
console.error(error);
|
console.error(error);
|
||||||
message.error(t('common.getInfoFail'));
|
message.error(t('common.getInfoFail'));
|
||||||
} finally {
|
} finally {
|
||||||
@@ -224,11 +365,11 @@ const fetchData = async (type: ChartType) => {
|
|||||||
|
|
||||||
//建立实时数据连接
|
//建立实时数据连接
|
||||||
function fnRealTimeSwitch(bool: boolean) {
|
function fnRealTimeSwitch(bool: boolean) {
|
||||||
console.log('fnRealTimeSwitch called with:', bool);
|
|
||||||
realTimeEnabled.value = bool;
|
realTimeEnabled.value = bool;
|
||||||
if (bool) {
|
if (bool) {
|
||||||
if(!ws.value){
|
if(!ws.value){
|
||||||
console.log('Creating new WS instance');
|
|
||||||
ws.value = new WS();
|
ws.value = new WS();
|
||||||
}
|
}
|
||||||
Object.values(chartStates).forEach(state => {
|
Object.values(chartStates).forEach(state => {
|
||||||
@@ -238,21 +379,15 @@ function fnRealTimeSwitch(bool: boolean) {
|
|||||||
const options: OptionsType = {
|
const options: OptionsType = {
|
||||||
url: '/ws',
|
url: '/ws',
|
||||||
params: {
|
params: {
|
||||||
subGroupID: Object.keys(chartStates)
|
subGroupID: networkElementTypes.value.map(type => `10_${type.toUpperCase()}_001`).join(','),
|
||||||
.map(type => `10_${type.toUpperCase()}_001`)
|
|
||||||
.join(','),
|
|
||||||
},
|
},
|
||||||
onmessage: wsMessage,
|
onmessage: wsMessage,
|
||||||
onerror: wsError,
|
onerror: wsError,
|
||||||
onopen: () => {
|
|
||||||
console.log('WebSocket connection established');
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
console.log('Attempting to connect with options:', options);
|
|
||||||
ws.value.connect(options);
|
ws.value.connect(options);
|
||||||
console.log('Connection attempt initiated');
|
|
||||||
} else if(ws.value){
|
} else if(ws.value){
|
||||||
console.log('Closing WebSocket connection');
|
|
||||||
Object.values(chartStates).forEach(state => {
|
Object.values(chartStates).forEach(state => {
|
||||||
state.tableState.seached = true;
|
state.tableState.seached = true;
|
||||||
});
|
});
|
||||||
@@ -261,58 +396,61 @@ function fnRealTimeSwitch(bool: boolean) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 接收数据后错误回调
|
// 接收数据后错误回
|
||||||
function wsError(ev: any) {
|
function wsError() {
|
||||||
console.error('WebSocket error:', ev);
|
|
||||||
message.error(t('common.websocketError'));
|
message.error(t('common.websocketError'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 接收数据后回调
|
// 修改 wsMessage 数
|
||||||
function wsMessage(res: Record<string, any>) {
|
function wsMessage(res: Record<string, any>) {
|
||||||
//const res = JSON.parse(event.data);
|
|
||||||
const { code, data } = res;
|
const { code, data } = res;
|
||||||
if (code === RESULT_CODE_ERROR) {
|
if (code === RESULT_CODE_ERROR) {
|
||||||
console.warn(res.msg);
|
console.warn(res.msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 订阅组信息
|
|
||||||
if (!data?.groupId) {
|
if (!data?.groupId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理四个图表的数据
|
networkElementTypes.value.forEach((type) => {
|
||||||
(Object.keys(chartStates) as ChartType[]).forEach(type => {
|
|
||||||
const state = chartStates[type];
|
const state = chartStates[type];
|
||||||
const kpiEvent = data.data[type.toUpperCase()];
|
const kpiEvent:any = data.data[type.toUpperCase()];
|
||||||
|
|
||||||
if (kpiEvent) {
|
if (kpiEvent) {
|
||||||
// 更新 X 轴数据
|
|
||||||
if (kpiEvent.timeGroup) {
|
if (kpiEvent.timeGroup) {
|
||||||
state.chartDataXAxisData.push(parseDateToStr(+kpiEvent.timeGroup));
|
const newTime = parseDateToStr(+kpiEvent.timeGroup);
|
||||||
|
state.chartDataXAxisData.push(newTime);
|
||||||
if (state.chartDataXAxisData.length > 100) {
|
if (state.chartDataXAxisData.length > 100) {
|
||||||
state.chartDataXAxisData.shift();
|
state.chartDataXAxisData.shift();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 更新 Y 轴数据
|
// 使用 appendData 方法追加数据
|
||||||
state.chartDataYSeriesData.forEach(series => {
|
state.chartDataYSeriesData.forEach(series => {
|
||||||
if (kpiEvent[series.customKey as string] !== undefined) {
|
if (kpiEvent[series.customKey as string] !== undefined) {
|
||||||
series.data.push(+kpiEvent[series.customKey as string]);
|
const newValue = +kpiEvent[series.customKey as string];
|
||||||
|
if (state.chart.value) {
|
||||||
|
state.chart.value.appendData({
|
||||||
|
seriesIndex: state.chartDataYSeriesData.indexOf(series),
|
||||||
|
data: [[newTime, newValue]]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 保持数据长度不超过100
|
||||||
if (series.data.length > 100) {
|
if (series.data.length > 100) {
|
||||||
series.data.shift();
|
series.data.shift();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 更新图表
|
// 更新 X 轴
|
||||||
if (state.chart.value) {
|
if (state.chart.value) {
|
||||||
state.chart.value.setOption({
|
state.chart.value.setOption({
|
||||||
xAxis: { data: state.chartDataXAxisData },
|
xAxis: { data: state.chartDataXAxisData }
|
||||||
series: state.chartDataYSeriesData,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,26 +463,23 @@ interface CustomSeriesOption extends Omit<LineSeriesOption, 'data'> {
|
|||||||
data: (number | LineDataItem)[];
|
data: (number | LineDataItem)[];
|
||||||
}
|
}
|
||||||
// 创建可复用的图表渲染函数
|
// 创建可复用的图表渲染函数
|
||||||
const renderChart = (type: ChartType) => {
|
const renderChart = (type: AllChartType) => {
|
||||||
const state = chartStates[type];
|
const state = chartStates[type];
|
||||||
if (state.chart.value == null || state.tableState.data.length <= 0) {
|
if (state.chart.value == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置数据
|
// 重置数据
|
||||||
state.chartLegendSelected = {};
|
state.chartLegendSelected = {};
|
||||||
state.chartDataXAxisData = [];
|
state.chartDataXAxisData = [];
|
||||||
state.chartDataYSeriesData = [];
|
state.chartDataYSeriesData = [];
|
||||||
|
|
||||||
// 处理数据
|
// 处理数据
|
||||||
for (const columns of state.tableColumns.value) {
|
for (const column of state.tableColumns.value) {
|
||||||
if (['neName', 'startIndex', 'timeGroup'].includes(columns.key as string))
|
if (['neName', 'startIndex', 'timeGroup'].includes(column.key as string)) continue;
|
||||||
continue;
|
|
||||||
const color = generateColorRGBA();
|
const color = generateColorRGBA();
|
||||||
state.chartDataYSeriesData.push({
|
state.chartDataYSeriesData.push({
|
||||||
name: columns.title as string,
|
name: column.title as string,
|
||||||
customKey: columns.key as string,
|
customKey: column.key as string,
|
||||||
//key: columns.key as string,
|
|
||||||
type: 'line',
|
type: 'line',
|
||||||
symbol: 'none',
|
symbol: 'none',
|
||||||
sampling: 'lttb',
|
sampling: 'lttb',
|
||||||
@@ -357,17 +492,14 @@ const renderChart = (type: ChartType) => {
|
|||||||
},
|
},
|
||||||
data: [],
|
data: [],
|
||||||
} as CustomSeriesOption);
|
} as CustomSeriesOption);
|
||||||
state.chartLegendSelected[columns.title as string] = true;
|
state.chartLegendSelected[column.title as string] = true;
|
||||||
//});
|
|
||||||
//state.chartLegendSelected[columns.title as string] = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const orgData = [...state.tableState.data].reverse();
|
const orgData = [...state.tableState.data].reverse();
|
||||||
for (const item of orgData) {
|
for (const item of orgData) {
|
||||||
state.chartDataXAxisData.push(parseDateToStr(+item.timeGroup));
|
state.chartDataXAxisData.push(parseDateToStr(+item.timeGroup));
|
||||||
for (const y of state.chartDataYSeriesData) {
|
for (const series of state.chartDataYSeriesData) {
|
||||||
//const key = (y.emphasis as any).customKey;
|
series.data.push(+item[series.customKey as string]);
|
||||||
y.data.push(+item[y.customKey as string]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,7 +507,11 @@ const renderChart = (type: ChartType) => {
|
|||||||
state.chart.value.setOption(
|
state.chart.value.setOption(
|
||||||
{
|
{
|
||||||
legend: { selected: state.chartLegendSelected },
|
legend: { selected: state.chartLegendSelected },
|
||||||
xAxis: { data: state.chartDataXAxisData },
|
xAxis: {
|
||||||
|
data: state.chartDataXAxisData,
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
},
|
||||||
series: state.chartDataYSeriesData,
|
series: state.chartDataYSeriesData,
|
||||||
dataZoom: [
|
dataZoom: [
|
||||||
{
|
{
|
||||||
@@ -393,14 +529,13 @@ const renderChart = (type: ChartType) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取表头数据
|
|
||||||
const fetchKPITitle = async (type: ChartType) => {
|
// 获取头数据
|
||||||
const language =
|
const fetchKPITitle = async (type: AllChartType) => {
|
||||||
currentLocale.value.split('_')[0] === 'zh'
|
const language = currentLocale.value.split('_')[0] === 'zh' ? 'cn' : currentLocale.value.split('_')[0];
|
||||||
? 'cn'
|
|
||||||
: currentLocale.value.split('_')[0];
|
|
||||||
try {
|
try {
|
||||||
const res = await getKPITitle(type.toUpperCase());
|
const res = await getKPITitle(type.toUpperCase());
|
||||||
|
console.log(res);
|
||||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||||
chartStates[type].tableColumns.value = res.data.map(item => ({
|
chartStates[type].tableColumns.value = res.data.map(item => ({
|
||||||
title: item[`${language}Title`],
|
title: item[`${language}Title`],
|
||||||
@@ -414,20 +549,57 @@ const fetchKPITitle = async (type: ChartType) => {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log("321")
|
||||||
console.error(error);
|
console.error(error);
|
||||||
message.warning(t('common.getInfoFail'));
|
message.warning(t('common.getInfoFail'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化所有图表
|
// 定义默认选择的网元类型
|
||||||
|
const DEFAULT_NE_TYPES: AllChartType[] = ['udm', 'amf', 'upf', 'ims'];
|
||||||
|
|
||||||
|
// 在 onMounted 钩子中
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
ws.value = new WS();
|
ws.value = new WS();
|
||||||
await neInfoStore.fnNelist();
|
await neInfoStore.fnNelist();
|
||||||
for (const type of Object.keys(chartStates) as ChartType[]) {
|
|
||||||
await fetchKPITitle(type);
|
// 从本地存储中读取选中的网元类型
|
||||||
initChart(type);
|
const savedSelectedNeTypes = localStorage.getItem('selectedNeTypes');
|
||||||
fetchData(type);
|
if (savedSelectedNeTypes) {
|
||||||
|
const parsedSelectedNeTypes = JSON.parse(savedSelectedNeTypes) as AllChartType[];
|
||||||
|
selectedNeTypes.value = parsedSelectedNeTypes;
|
||||||
|
networkElementTypes.value = parsedSelectedNeTypes;
|
||||||
|
} else {
|
||||||
|
// 如果没有保存的选中网元类型,则使用默认选择
|
||||||
|
selectedNeTypes.value = [...DEFAULT_NE_TYPES];
|
||||||
|
networkElementTypes.value = [...DEFAULT_NE_TYPES];
|
||||||
|
// 保存这个默认选择到本地存储
|
||||||
|
localStorage.setItem('selectedNeTypes', JSON.stringify(DEFAULT_NE_TYPES));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化或更新 chartOrder
|
||||||
|
const savedLayout = localStorage.getItem('chartOrder');
|
||||||
|
if (savedLayout) {
|
||||||
|
const parsedLayout = JSON.parse(savedLayout);
|
||||||
|
// 只保留当前选中的网元类型的布局
|
||||||
|
chartOrder.value = parsedLayout.filter((item: LayoutItem) => networkElementTypes.value.includes(item.i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 chartOrder 为空或者不包含所有选中的网元,重新创建布局
|
||||||
|
if (chartOrder.value.length === 0 || chartOrder.value.length !== networkElementTypes.value.length) {
|
||||||
|
chartOrder.value = networkElementTypes.value.map((type, index) => ({
|
||||||
|
x: index % 2 * 6,
|
||||||
|
y: Math.floor(index / 2) * 4,
|
||||||
|
w: 6,
|
||||||
|
h: 4,
|
||||||
|
i: type,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
//console.log('Initialized networkElementTypes:', networkElementTypes.value);
|
||||||
|
//console.log('Initialized chartOrder:', chartOrder.value);
|
||||||
|
|
||||||
|
await initCharts();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 在组件卸载时销毁图表实例
|
// 在组件卸载时销毁图表实例
|
||||||
@@ -435,45 +607,86 @@ onUnmounted(() => {
|
|||||||
if(ws.value &&ws.value.state()===WebSocket.OPEN) {
|
if(ws.value &&ws.value.state()===WebSocket.OPEN) {
|
||||||
ws.value.close();
|
ws.value.close();
|
||||||
}
|
}
|
||||||
Object.values(chartStates).forEach(state => {
|
Object.values(chartStates).forEach((state) => {
|
||||||
if (state.chart.value) {
|
if (state.chart.value) {
|
||||||
state.chart.value.dispose();
|
state.chart.value.dispose();
|
||||||
}
|
}
|
||||||
|
if (state.observer.value) {
|
||||||
|
state.observer.value.disconnect();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<div class="control-row">
|
<a-card :bordered="false" class="control-card">
|
||||||
<div class="real-time-switch">
|
<a-form layout="inline">
|
||||||
<Switch
|
<a-form-item>
|
||||||
|
<a-switch
|
||||||
v-model:checked="realTimeEnabled"
|
v-model:checked="realTimeEnabled"
|
||||||
@change="handleRealTimeSwitch as any"
|
@change="handleRealTimeSwitch"
|
||||||
/>
|
/>
|
||||||
<span class="switch-label">{{
|
</a-form-item>
|
||||||
realTimeEnabled ? '实时数据已开启' : '实时数据已关闭'
|
<a-form-item>
|
||||||
}}</span>
|
<span class="switch-label">{{ realTimeEnabled ? t('views.dashboard.cdr.realTimeDataStart') : t('views.dashboard.cdr.realTimeDataStop') }}</span>
|
||||||
</div>
|
</a-form-item>
|
||||||
</div>
|
<a-form-item :label="t('views.ne.common.neType')" class="ne-type-select">
|
||||||
<draggable
|
<a-select
|
||||||
v-model="chartOrder"
|
v-model:value="selectedNeTypes"
|
||||||
:animation="200"
|
mode="multiple"
|
||||||
item-key="type"
|
style="min-width: 200px; width: 100%"
|
||||||
@end="onDragEnd"
|
:placeholder="t('common.selectPlease')"
|
||||||
class="row"
|
|
||||||
onscroll="false"
|
|
||||||
>
|
>
|
||||||
<template #item="{ element: type }">
|
<a-select-option v-for="type in ALL_NE_TYPES" :key="type" :value="type">
|
||||||
<div class="col-lg-6 col-md=-6 col-xs-12">
|
{{ type.toUpperCase() }}
|
||||||
<a-card
|
</a-select-option>
|
||||||
:bordered="false"
|
</a-select>
|
||||||
:body-style="{ marginBottom: '24px', padding: '24px' }"
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<GridLayout
|
||||||
|
v-model:layout="chartOrder"
|
||||||
|
:col-num="12"
|
||||||
|
:row-height="100"
|
||||||
|
:margin="[10, 10]"
|
||||||
|
:is-draggable="true"
|
||||||
|
:is-resizable="true"
|
||||||
|
:vertical-compact="true"
|
||||||
|
:use-css-transforms="true"
|
||||||
|
:responsive="true"
|
||||||
|
:prevent-collision="false"
|
||||||
|
@layout-updated="handleLayoutUpdated"
|
||||||
|
class="charts-container"
|
||||||
>
|
>
|
||||||
<template #title>{{ type.toUpperCase() }}</template>
|
<GridItem
|
||||||
|
v-for="item in chartOrder.filter(i => networkElementTypes.includes(i.i))"
|
||||||
|
:key="item.i"
|
||||||
|
:x="item.x"
|
||||||
|
:y="item.y"
|
||||||
|
:w="item.w"
|
||||||
|
:h="item.h"
|
||||||
|
:i="item.i"
|
||||||
|
:min-w="4"
|
||||||
|
:min-h="3"
|
||||||
|
:is-draggable="true"
|
||||||
|
:is-resizable="true"
|
||||||
|
:resizable-handles="['ne']"
|
||||||
|
drag-allow-from=".drag-handle"
|
||||||
|
drag-ignore-from=".no-drag"
|
||||||
|
class="grid-item"
|
||||||
|
>
|
||||||
|
<div class="drag-handle">
|
||||||
|
<DragOutlined />
|
||||||
|
</div>
|
||||||
|
<a-card :bordered="false" class="card-container">
|
||||||
|
<template #title>
|
||||||
|
<span class="no-drag">{{ item.i.toUpperCase() }}</span>
|
||||||
|
</template>
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<a-range-picker
|
<a-range-picker
|
||||||
v-model:value="(rangePicker[type as keyof RangePicker]as[string,string])"
|
v-model:value="rangePicker[item.i]"
|
||||||
:allow-clear="false"
|
:allow-clear="false"
|
||||||
bordered
|
bordered
|
||||||
:show-time="{ format: 'HH:mm:ss' }"
|
:show-time="{ format: 'HH:mm:ss' }"
|
||||||
@@ -482,79 +695,106 @@ onUnmounted(() => {
|
|||||||
:placeholder="rangePicker.placeholder"
|
:placeholder="rangePicker.placeholder"
|
||||||
:ranges="rangePicker.ranges"
|
:ranges="rangePicker.ranges"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
@change="() => fetchData(type)"
|
@change="() => fetchData(item.i)"
|
||||||
></a-range-picker>
|
></a-range-picker>
|
||||||
</template>
|
</template>
|
||||||
<div class="chart" style="padding: 12px">
|
<div class='chart'>
|
||||||
<div
|
<div :ref="el => { if (el && chartStates[item.i]) chartStates[item.i].chartDom.value = el as HTMLElement }"></div>
|
||||||
:ref="el => { if (el) chartStates[type as ChartType].chartDom.value = el as HTMLElement }"
|
|
||||||
style="height: 400px; width: 100%"
|
|
||||||
></div>
|
|
||||||
</div>
|
</div>
|
||||||
</a-card>
|
</a-card>
|
||||||
</div>
|
</GridItem>
|
||||||
</template>
|
</GridLayout>
|
||||||
</draggable>
|
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.chart {
|
.control-card {
|
||||||
width: 100%;
|
|
||||||
height: 450px;
|
|
||||||
}
|
|
||||||
.sortable-ghost {
|
|
||||||
opacity: 0.5;
|
|
||||||
background: #c8ebfb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sortable-drag {
|
|
||||||
opacity: 0.8;
|
|
||||||
background: #f4f4f4;
|
|
||||||
}
|
|
||||||
.row {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-right: -12px;
|
|
||||||
margin-left: -12px;
|
|
||||||
}
|
|
||||||
.col-lg-6 {
|
|
||||||
flex: 0 0 50%;
|
|
||||||
max-width: 50%;
|
|
||||||
padding-right: 12px;
|
|
||||||
padding-left: 12px;
|
|
||||||
}
|
|
||||||
@media (max-width: 991px) {
|
|
||||||
.col-md-6 {
|
|
||||||
flex: 0 0 50%;
|
|
||||||
max-width: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (max-width: 575px) {
|
|
||||||
.col-xs-12 {
|
|
||||||
flex: 0 0 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.control-row {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
padding: 16px;
|
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
background-color: #f0f2f5;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.real-time-switch {
|
.charts-container {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-item {
|
||||||
|
overflow: visible;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-container {
|
||||||
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.switch-label {
|
.switch-label {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #333;
|
color: rgba(0, 0, 0, 0.65);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.drag-handle {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: rgba(24, 144, 255, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: move;
|
||||||
|
z-index: 100;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(24, 144, 255, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ne-type-select {
|
||||||
|
flex-grow: 1;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep {
|
||||||
|
.ant-card-body {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 16px !important;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-form-item {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-form-item-label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: rgba(0, 0, 0, 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select {
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -193,7 +193,6 @@ function fnTableSelectedRows(
|
|||||||
_: (string | number)[],
|
_: (string | number)[],
|
||||||
rows: Record<string, string>[]
|
rows: Record<string, string>[]
|
||||||
) {
|
) {
|
||||||
//debugger
|
|
||||||
tableState.selectedRowKeys = rows.map(item => item.loginId);
|
tableState.selectedRowKeys = rows.map(item => item.loginId);
|
||||||
// 针对单个登录账号解锁
|
// 针对单个登录账号解锁
|
||||||
if (rows.length === 1) {
|
if (rows.length === 1) {
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ let tableColumns: any = [
|
|||||||
* 测试主机连接
|
* 测试主机连接
|
||||||
*/
|
*/
|
||||||
function fnHostTest(row: Record<string, any>) {
|
function fnHostTest(row: Record<string, any>) {
|
||||||
if (tabState.confirmLoading || !row.addr) return;
|
if (tabState.confirmLoading || !row.addr || !row.port) return;
|
||||||
tabState.confirmLoading = true;
|
tabState.confirmLoading = true;
|
||||||
const hide = message.loading(t('common.loading'), 0);
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
testNeHost(row)
|
testNeHost(row)
|
||||||
@@ -187,19 +187,19 @@ function fnHostAuthorized(row: Record<string, any>) {
|
|||||||
* 表单修改网元类型
|
* 表单修改网元类型
|
||||||
*/
|
*/
|
||||||
function fnNeTypeChange(v: any, data: any) {
|
function fnNeTypeChange(v: any, data: any) {
|
||||||
const hostsLen = data.hosts.length;
|
|
||||||
// 网元默认只含22和4100
|
// 网元默认只含22和4100
|
||||||
if (hostsLen === 3 && v !== 'UPF') {
|
if (modalState.from.hosts.length === 3) {
|
||||||
data.hosts.pop();
|
modalState.from.hosts.pop();
|
||||||
}
|
}
|
||||||
|
const hostsLen = modalState.from.hosts.length;
|
||||||
// UPF标准版本可支持5002
|
// UPF标准版本可支持5002
|
||||||
if (hostsLen === 2 && v === 'UPF') {
|
if (hostsLen === 2 && v === 'UPF') {
|
||||||
data.hosts.push({
|
modalState.from.hosts.push({
|
||||||
hostId: undefined,
|
hostId: undefined,
|
||||||
hostType: 'telnet',
|
hostType: 'telnet',
|
||||||
groupId: '1',
|
groupId: '1',
|
||||||
title: 'Telnet_NE_5002',
|
title: 'Telnet_NE_5002',
|
||||||
addr: '',
|
addr: modalState.from.ip,
|
||||||
port: 5002,
|
port: 5002,
|
||||||
user: 'admin',
|
user: 'admin',
|
||||||
authMode: '0',
|
authMode: '0',
|
||||||
@@ -207,6 +207,22 @@ function fnNeTypeChange(v: any, data: any) {
|
|||||||
remark: '',
|
remark: '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// UDM可支持6379
|
||||||
|
if (hostsLen === 2 && v === 'UDM') {
|
||||||
|
modalState.from.hosts.push({
|
||||||
|
hostId: undefined,
|
||||||
|
hostType: 'redis',
|
||||||
|
groupId: '1',
|
||||||
|
title: 'REDIS_NE_6379',
|
||||||
|
addr: modalState.from.ip,
|
||||||
|
port: 6379,
|
||||||
|
user: 'udmdb',
|
||||||
|
authMode: '0',
|
||||||
|
password: 'helloearth',
|
||||||
|
dbName: '0',
|
||||||
|
remark: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//打开新增或修改界面
|
//打开新增或修改界面
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Modal, message } from 'ant-design-vue/lib';
|
import { Modal, message } from 'ant-design-vue/lib';
|
||||||
import { onMounted, reactive, toRaw } from 'vue';
|
import { onMounted, reactive, toRaw } from 'vue';
|
||||||
import useAppStore from '@/store/modules/app';
|
|
||||||
import useI18n from '@/hooks/useI18n';
|
import useI18n from '@/hooks/useI18n';
|
||||||
import { listMenu } from '@/api/system/menu';
|
import { listMenu } from '@/api/system/menu';
|
||||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
import { getConfig, getConfigKey, changeValue } from '@/api/system/config';
|
import { getConfigKey, changeValue } from '@/api/system/config';
|
||||||
|
const { t } = useI18n();
|
||||||
const appStore = useAppStore();
|
|
||||||
const { t, optionsLocale } = useI18n();
|
|
||||||
|
|
||||||
type StateType = {
|
type StateType = {
|
||||||
edite: boolean;
|
edite: boolean;
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, onMounted, onBeforeUnmount, ref } from 'vue';
|
import { reactive, onMounted, onBeforeUnmount, ref } from 'vue';
|
||||||
import { PageContainer } from 'antdv-pro-layout';
|
import { PageContainer } from 'antdv-pro-layout';
|
||||||
import { message } from 'ant-design-vue/lib';
|
import { message, Modal } from 'ant-design-vue/lib';
|
||||||
import useI18n from '@/hooks/useI18n';
|
import useI18n from '@/hooks/useI18n';
|
||||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
|
||||||
import {
|
import {
|
||||||
RESULT_CODE_ERROR,
|
RESULT_CODE_ERROR,
|
||||||
RESULT_CODE_SUCCESS,
|
RESULT_CODE_SUCCESS,
|
||||||
} from '@/constants/result-constants';
|
} from '@/constants/result-constants';
|
||||||
|
import TerminalSSHView from '@/components/TerminalSSHView/index.vue';
|
||||||
import useNeInfoStore from '@/store/modules/neinfo';
|
import useNeInfoStore from '@/store/modules/neinfo';
|
||||||
|
import { iperfI, iperfV } from '@/api/tool/iperf';
|
||||||
const neInfoStore = useNeInfoStore();
|
const neInfoStore = useNeInfoStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const ws = new WS();
|
|
||||||
|
|
||||||
/**网元参数 */
|
/**网元参数 */
|
||||||
let neCascaderOptions = ref<Record<string, any>[]>([]);
|
let neCascaderOptions = ref<Record<string, any>[]>([]);
|
||||||
@@ -20,68 +20,155 @@ let neCascaderOptions = ref<Record<string, any>[]>([]);
|
|||||||
let state = reactive({
|
let state = reactive({
|
||||||
/**初始化 */
|
/**初始化 */
|
||||||
initialized: false,
|
initialized: false,
|
||||||
|
/**运行中 */
|
||||||
|
running: false,
|
||||||
|
/**版本信息 */
|
||||||
|
versionInfo: [],
|
||||||
/**网元类型 */
|
/**网元类型 */
|
||||||
neType: [],
|
neType: [],
|
||||||
/**数据类型 */
|
/**数据类型 */
|
||||||
dataType: 'options' as 'options' | 'command',
|
dataType: 'options' as 'options' | 'command',
|
||||||
/**ws参数 */
|
/**ws参数 */
|
||||||
params: {
|
params: {
|
||||||
neType: 'AMF',
|
neType: '',
|
||||||
neId: '001',
|
neId: '',
|
||||||
cols: 120,
|
cols: 120,
|
||||||
rows: 40,
|
rows: 40,
|
||||||
},
|
},
|
||||||
/**ws数据 */
|
/**ws数据 */
|
||||||
data: {
|
data: {
|
||||||
command: '', // 命令字符串
|
command: '', // 命令字符串
|
||||||
desAddr: 'www.baidu.com', // dns name or ip address
|
client: true, // 服务端或客户端,默认服务端
|
||||||
// Options
|
// Server or Client
|
||||||
interval: 1, // seconds between sending each packet
|
port: 5201, // 服务端口
|
||||||
ttl: 255, // define time to live
|
interval: 1, // 每次报告之间的时间间隔,单位为秒
|
||||||
count: 4, // <count> 次回复后停止
|
// Server
|
||||||
size: 56, // 使用 <size> 作为要发送的数据字节数
|
oneOff: false, // 只进行一次连接
|
||||||
timeout: 2, // seconds time to wait for response
|
// Client
|
||||||
|
host: '', // 客户端连接到的服务端IP地址
|
||||||
|
udp: false, // use UDP rather than TCP
|
||||||
|
time: 10, // 以秒为单位的传输时间(默认为 10 秒)
|
||||||
|
reverse: false, // 以反向模式运行(服务器发送,客户端接收)
|
||||||
|
window: '300k', // 设置窗口大小/套接字缓冲区大小
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**接收数据后回调(成功) */
|
/**连接发送 */
|
||||||
function wsMessage(res: Record<string, any>) {
|
async function fnIPerf3() {
|
||||||
|
const [neType, neId] = state.neType;
|
||||||
|
if (!neType || !neId) {
|
||||||
|
message.warning({
|
||||||
|
content: 'No Found NE Type',
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state.dataType === 'options' && state.data.host === '') {
|
||||||
|
message.warning({
|
||||||
|
content: 'Please fill in the Host',
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state.dataType === 'command' && state.data.command === '') {
|
||||||
|
message.warning({
|
||||||
|
content: 'Please fill in the Command',
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state.initialized) {
|
||||||
|
fnResend();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.params.neType = neType;
|
||||||
|
state.params.neId = neId;
|
||||||
|
const resVersion = await iperfV({ neType, neId });
|
||||||
|
if (resVersion.code !== RESULT_CODE_SUCCESS) {
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: 'Not found if iperf is installed',
|
||||||
|
onOk: () => fnInstall(),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
state.versionInfo = resVersion.data;
|
||||||
|
}
|
||||||
|
state.initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**触发安装iperf3 */
|
||||||
|
function fnInstall() {
|
||||||
|
const key = 'iperfI';
|
||||||
|
message.loading({ content: t('common.loading'), key });
|
||||||
|
const { neType, neId } = state.params;
|
||||||
|
iperfI({ neType, neId }).then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: 'install success',
|
||||||
|
key,
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: 'install fail',
|
||||||
|
key,
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**终端实例 */
|
||||||
|
const toolTerminal = ref();
|
||||||
|
|
||||||
|
/**重置并停止 */
|
||||||
|
function fnReset() {
|
||||||
|
if (!toolTerminal.value) return;
|
||||||
|
toolTerminal.value.ctrlC();
|
||||||
|
// toolTerminal.value.clear();
|
||||||
|
state.running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**重载发送 */
|
||||||
|
function fnResend() {
|
||||||
|
if (!toolTerminal.value) return;
|
||||||
|
state.running = true;
|
||||||
|
toolTerminal.value.ctrlC();
|
||||||
|
toolTerminal.value.clear();
|
||||||
|
setTimeout(() => {
|
||||||
|
const data = JSON.parse(JSON.stringify(state.data));
|
||||||
|
if (state.dataType === 'options') data.command = '';
|
||||||
|
toolTerminal.value.send('iperf3', data);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**终端初始连接*/
|
||||||
|
function fnConnect() {
|
||||||
|
fnResend();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**终端消息监听*/
|
||||||
|
function fnMessage(res: Record<string, any>) {
|
||||||
const { code, requestId, data } = res;
|
const { code, requestId, data } = res;
|
||||||
if (code === RESULT_CODE_ERROR) {
|
if (code === RESULT_CODE_ERROR) {
|
||||||
console.warn(res.msg);
|
console.warn(res.msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!requestId) return;
|
||||||
// 建联时发送请求
|
let lestIndex = data.lastIndexOf('unable to');
|
||||||
if (!requestId && data.clientId) {
|
if (lestIndex !== -1) {
|
||||||
state.initialized = true;
|
state.running = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
lestIndex = data.lastIndexOf('iperf Done.');
|
||||||
// 收到消息数据
|
if (lestIndex !== -1) {
|
||||||
if (requestId.startsWith('ping_')) {
|
state.running = false;
|
||||||
console.log(data);
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**连接到ws*/
|
|
||||||
function fnConnect(reLink: boolean) {
|
|
||||||
if (reLink) {
|
|
||||||
ws.close();
|
|
||||||
}
|
|
||||||
const options: OptionsType = {
|
|
||||||
url: '/tool/ping/run',
|
|
||||||
params: state.params,
|
|
||||||
onmessage: wsMessage,
|
|
||||||
onerror: (ev: any) => {
|
|
||||||
// 接收数据后回调
|
|
||||||
console.error(ev);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
//建立连接
|
|
||||||
ws.connect(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**钩子函数,界面打开初始化*/
|
/**钩子函数,界面打开初始化*/
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 获取网元网元列表
|
// 获取网元网元列表
|
||||||
@@ -111,9 +198,7 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**钩子函数,界面关闭*/
|
/**钩子函数,界面关闭*/
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {});
|
||||||
if (ws.state() === WebSocket.OPEN) ws.close();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -122,29 +207,224 @@ onBeforeUnmount(() => {
|
|||||||
<!-- 插槽-卡片左侧侧 -->
|
<!-- 插槽-卡片左侧侧 -->
|
||||||
<template #title>
|
<template #title>
|
||||||
<a-space :size="8">
|
<a-space :size="8">
|
||||||
|
<span>
|
||||||
|
{{ t('views.ne.common.neType') }}:
|
||||||
<a-cascader
|
<a-cascader
|
||||||
v-model:value="state.neType"
|
v-model:value="state.neType"
|
||||||
:options="neCascaderOptions"
|
:options="neCascaderOptions"
|
||||||
:placeholder="t('common.selectPlease')"
|
:placeholder="t('common.selectPlease')"
|
||||||
|
:disabled="state.running"
|
||||||
/>
|
/>
|
||||||
<a-radio-group v-model:value="state.dataType" button-style="solid">
|
</span>
|
||||||
|
<a-radio-group
|
||||||
|
v-model:value="state.dataType"
|
||||||
|
button-style="solid"
|
||||||
|
:disabled="state.running"
|
||||||
|
>
|
||||||
<a-radio-button value="options">Options</a-radio-button>
|
<a-radio-button value="options">Options</a-radio-button>
|
||||||
<a-radio-button value="commandb">Command</a-radio-button>
|
<a-radio-button value="command">Command</a-radio-button>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
<a-button @click.prevent="fnConnect(false)">
|
|
||||||
<template #icon><PlayCircleOutlined /></template>
|
|
||||||
Connect {{ state.initialized }}
|
|
||||||
</a-button>
|
|
||||||
</a-space>
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 表格列表 -->
|
<!-- 插槽-卡片右侧 -->
|
||||||
<a-table
|
<template #extra>
|
||||||
:columns="[]"
|
<a-space :size="8">
|
||||||
:data-source="[]"
|
<a-button
|
||||||
:pagination="false"
|
@click.prevent="fnIPerf3()"
|
||||||
:loading="false"
|
type="primary"
|
||||||
|
:loading="state.running"
|
||||||
|
>
|
||||||
|
<template #icon><PlayCircleOutlined /></template>
|
||||||
|
{{ state.running ? 'Running' : 'Launch' }}
|
||||||
|
</a-button>
|
||||||
|
<a-button v-if="state.running" @click.prevent="fnReset()" danger>
|
||||||
|
<template #icon><CloseCircleOutlined /></template>
|
||||||
|
Stop
|
||||||
|
</a-button>
|
||||||
|
<!-- 版本信息 -->
|
||||||
|
<a-popover
|
||||||
|
trigger="click"
|
||||||
|
placement="bottomRight"
|
||||||
|
v-if="state.versionInfo.length > 0"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div v-for="v in state.versionInfo">{{ v }}</div>
|
||||||
|
</template>
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
</a-popover>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- options -->
|
||||||
|
<a-form
|
||||||
|
v-if="state.dataType === 'options'"
|
||||||
|
:model="state.data"
|
||||||
|
name="queryParams"
|
||||||
|
layout="horizontal"
|
||||||
|
:label-col="{ span: 6 }"
|
||||||
|
:label-wrap="true"
|
||||||
|
style="padding: 12px"
|
||||||
|
>
|
||||||
|
<a-divider orientation="left">Server or Client</a-divider>
|
||||||
|
<a-row>
|
||||||
|
<a-col :lg="6" :md="6" :xs="12">
|
||||||
|
<a-form-item label="Port" name="port">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="state.data.port"
|
||||||
|
:disabled="state.running"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
style="width: 100%"
|
||||||
|
:min="1024"
|
||||||
|
:max="65535"
|
||||||
|
></a-input-number> </a-form-item
|
||||||
|
></a-col>
|
||||||
|
<a-col :lg="6" :md="6" :xs="12">
|
||||||
|
<a-form-item label="Interval" name="interval">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="state.data.interval"
|
||||||
|
:disabled="state.running"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
style="width: 100%"
|
||||||
|
:min="1"
|
||||||
|
:max="30"
|
||||||
|
></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-divider orientation="left">
|
||||||
|
<a-radio-group
|
||||||
|
v-model:value="state.data.client"
|
||||||
|
:disabled="state.running"
|
||||||
|
>
|
||||||
|
<a-radio :value="true">Client</a-radio>
|
||||||
|
<a-radio :value="false">Server</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-divider>
|
||||||
|
<template v-if="state.data.client">
|
||||||
|
<a-form-item
|
||||||
|
label="Host"
|
||||||
|
name="host"
|
||||||
|
help="run in client mode, connecting to <host>"
|
||||||
|
:label-col="{ span: 3 }"
|
||||||
|
:label-wrap="true"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="state.data.host"
|
||||||
|
:disabled="state.running"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :lg="12" :md="12" :xs="12">
|
||||||
|
<a-form-item
|
||||||
|
label="UDP"
|
||||||
|
name="udp"
|
||||||
|
help="use UDP rather than TCP"
|
||||||
|
>
|
||||||
|
<a-switch
|
||||||
|
v-model:checked="state.data.udp"
|
||||||
|
:disabled="state.running"
|
||||||
/>
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="12" :xs="12">
|
||||||
|
<a-form-item
|
||||||
|
label="Reverse"
|
||||||
|
name="reverse"
|
||||||
|
help="run in reverse mode (server sends, client receives)"
|
||||||
|
>
|
||||||
|
<a-switch
|
||||||
|
v-model:checked="state.data.reverse"
|
||||||
|
:disabled="state.running"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="12" :xs="12">
|
||||||
|
<a-form-item
|
||||||
|
label="Time"
|
||||||
|
name="time"
|
||||||
|
help="time in seconds to transmit for (default 10 secs)"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="state.data.time"
|
||||||
|
:disabled="state.running"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
style="width: 100%"
|
||||||
|
:min="1"
|
||||||
|
:max="60"
|
||||||
|
></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="12" :xs="12">
|
||||||
|
<a-form-item
|
||||||
|
label="Window"
|
||||||
|
name="window"
|
||||||
|
help="set window size / socket buffer size"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="state.data.window"
|
||||||
|
:disabled="state.running"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
style="width: 100%"
|
||||||
|
></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<a-row :gutter="16" v-else>
|
||||||
|
<a-col :lg="12" :md="12" :xs="12">
|
||||||
|
<a-form-item
|
||||||
|
label="OneOff"
|
||||||
|
name="oneOff"
|
||||||
|
help=" handle one client connection then exit"
|
||||||
|
>
|
||||||
|
<a-switch
|
||||||
|
v-model:checked="state.data.oneOff"
|
||||||
|
:disabled="state.running"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
|
||||||
|
<!-- command -->
|
||||||
|
<div v-else style="padding: 12px">
|
||||||
|
<a-auto-complete
|
||||||
|
v-model:value="state.data.command"
|
||||||
|
:disabled="state.running"
|
||||||
|
:dropdown-match-select-width="500"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
addon-before="iperf3"
|
||||||
|
placeholder="Client: -c 172.5.16.100 -p 5201 or Server: -s -p 5201"
|
||||||
|
/>
|
||||||
|
</a-auto-complete>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 运行过程 -->
|
||||||
|
<TerminalSSHView
|
||||||
|
v-if="state.initialized"
|
||||||
|
ref="toolTerminal"
|
||||||
|
:id="`V${Date.now()}`"
|
||||||
|
prefix="iperf3"
|
||||||
|
url="/tool/iperf/run"
|
||||||
|
:ne-type="state.params.neType"
|
||||||
|
:ne-id="state.params.neId"
|
||||||
|
:rows="state.params.rows"
|
||||||
|
:cols="state.params.cols"
|
||||||
|
style="height: 400px"
|
||||||
|
@connect="fnConnect"
|
||||||
|
@message="fnMessage"
|
||||||
|
></TerminalSSHView>
|
||||||
</a-card>
|
</a-card>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, onMounted, onBeforeUnmount, ref } from 'vue';
|
import { reactive, onMounted, onBeforeUnmount, ref, toRaw } from 'vue';
|
||||||
import { PageContainer } from 'antdv-pro-layout';
|
import { PageContainer } from 'antdv-pro-layout';
|
||||||
import { message } from 'ant-design-vue/lib';
|
import { message } from 'ant-design-vue/lib';
|
||||||
import useI18n from '@/hooks/useI18n';
|
import useI18n from '@/hooks/useI18n';
|
||||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
|
||||||
import {
|
import {
|
||||||
RESULT_CODE_ERROR,
|
RESULT_CODE_ERROR,
|
||||||
RESULT_CODE_SUCCESS,
|
RESULT_CODE_SUCCESS,
|
||||||
} from '@/constants/result-constants';
|
} from '@/constants/result-constants';
|
||||||
|
import TerminalSSHView from '@/components/TerminalSSHView/index.vue';
|
||||||
import useNeInfoStore from '@/store/modules/neinfo';
|
import useNeInfoStore from '@/store/modules/neinfo';
|
||||||
|
import { pingV } from '@/api/tool/ping';
|
||||||
const neInfoStore = useNeInfoStore();
|
const neInfoStore = useNeInfoStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const ws = new WS();
|
|
||||||
|
|
||||||
/**网元参数 */
|
/**网元参数 */
|
||||||
let neCascaderOptions = ref<Record<string, any>[]>([]);
|
let neCascaderOptions = ref<Record<string, any>[]>([]);
|
||||||
@@ -20,68 +20,127 @@ let neCascaderOptions = ref<Record<string, any>[]>([]);
|
|||||||
let state = reactive({
|
let state = reactive({
|
||||||
/**初始化 */
|
/**初始化 */
|
||||||
initialized: false,
|
initialized: false,
|
||||||
|
/**运行中 */
|
||||||
|
running: false,
|
||||||
|
/**版本信息 */
|
||||||
|
versionInfo: '',
|
||||||
/**网元类型 */
|
/**网元类型 */
|
||||||
neType: [],
|
neType: [],
|
||||||
/**数据类型 */
|
/**数据类型 */
|
||||||
dataType: 'options' as 'options' | 'command',
|
dataType: 'options' as 'options' | 'command',
|
||||||
/**ws参数 */
|
/**ws参数 */
|
||||||
params: {
|
params: {
|
||||||
neType: 'AMF',
|
neType: '',
|
||||||
neId: '001',
|
neId: '',
|
||||||
cols: 120,
|
cols: 120,
|
||||||
rows: 40,
|
rows: 40,
|
||||||
},
|
},
|
||||||
/**ws数据 */
|
/**ws数据 */
|
||||||
data: {
|
data: {
|
||||||
command: '', // 命令字符串
|
command: '', // 命令字符串
|
||||||
desAddr: 'www.baidu.com', // dns name or ip address
|
desAddr: '8.8.8.8', // dns name or ip address
|
||||||
// Options
|
// Options
|
||||||
interval: 1, // seconds between sending each packet
|
interval: 1, // seconds between sending each packet
|
||||||
ttl: 255, // define time to live
|
ttl: 255, // define time to live
|
||||||
count: 4, // <count> 次回复后停止
|
count: 4, // <count> 次回复后停止
|
||||||
size: 56, // 使用 <size> 作为要发送的数据字节数
|
size: 56, // 使用 <size> 作为要发送的数据字节数
|
||||||
timeout: 2, // seconds time to wait for response
|
timeout: 10, // seconds time to wait for response
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**接收数据后回调(成功) */
|
/**连接发送 */
|
||||||
function wsMessage(res: Record<string, any>) {
|
async function fnPing() {
|
||||||
|
const [neType, neId] = state.neType;
|
||||||
|
if (!neType || !neId) {
|
||||||
|
message.warning({
|
||||||
|
content: 'No Found NE Type',
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state.dataType === 'options' && state.data.desAddr === '') {
|
||||||
|
message.warning({
|
||||||
|
content: 'Please fill in the Destination',
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state.dataType === 'command' && state.data.command === '') {
|
||||||
|
message.warning({
|
||||||
|
content: 'Please fill in the Command',
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state.initialized) {
|
||||||
|
fnResend();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.params.neType = neType;
|
||||||
|
state.params.neId = neId;
|
||||||
|
const resVersion = await pingV({ neType, neId });
|
||||||
|
if (resVersion.code !== RESULT_CODE_SUCCESS) {
|
||||||
|
message.warning({
|
||||||
|
content: 'No Found ping iputils',
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
state.versionInfo = resVersion.data;
|
||||||
|
}
|
||||||
|
state.initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**终端实例 */
|
||||||
|
const toolTerminal = ref();
|
||||||
|
|
||||||
|
/**重置并停止 */
|
||||||
|
function fnReset() {
|
||||||
|
if (!toolTerminal.value) return;
|
||||||
|
toolTerminal.value.ctrlC();
|
||||||
|
// toolTerminal.value.clear();
|
||||||
|
state.running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**重载发送 */
|
||||||
|
function fnResend() {
|
||||||
|
if (!toolTerminal.value) return;
|
||||||
|
state.running = true;
|
||||||
|
toolTerminal.value.ctrlC();
|
||||||
|
toolTerminal.value.clear();
|
||||||
|
setTimeout(() => {
|
||||||
|
const data = JSON.parse(JSON.stringify(state.data));
|
||||||
|
if (state.dataType === 'options') data.command = '';
|
||||||
|
toolTerminal.value.send('ping', data);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**终端初始连接*/
|
||||||
|
function fnConnect() {
|
||||||
|
fnResend();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**终端消息监听*/
|
||||||
|
function fnMessage(res: Record<string, any>) {
|
||||||
const { code, requestId, data } = res;
|
const { code, requestId, data } = res;
|
||||||
if (code === RESULT_CODE_ERROR) {
|
if (code === RESULT_CODE_ERROR) {
|
||||||
console.warn(res.msg);
|
console.warn(res.msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!requestId) return;
|
||||||
// 建联时发送请求
|
let lestIndex = data.lastIndexOf('ping statistics ---');
|
||||||
if (!requestId && data.clientId) {
|
if (lestIndex !== -1) {
|
||||||
state.initialized = true;
|
state.running = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
lestIndex = data.lastIndexOf('failure');
|
||||||
// 收到消息数据
|
if (lestIndex !== -1) {
|
||||||
if (requestId.startsWith('ping_')) {
|
state.running = false;
|
||||||
console.log(data);
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**连接到ws*/
|
|
||||||
function fnConnect(reLink: boolean) {
|
|
||||||
if (reLink) {
|
|
||||||
ws.close();
|
|
||||||
}
|
|
||||||
const options: OptionsType = {
|
|
||||||
url: '/tool/ping/run',
|
|
||||||
params: state.params,
|
|
||||||
onmessage: wsMessage,
|
|
||||||
onerror: (ev: any) => {
|
|
||||||
// 接收数据后回调
|
|
||||||
console.error(ev);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
//建立连接
|
|
||||||
ws.connect(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**钩子函数,界面打开初始化*/
|
/**钩子函数,界面打开初始化*/
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 获取网元网元列表
|
// 获取网元网元列表
|
||||||
@@ -111,9 +170,7 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**钩子函数,界面关闭*/
|
/**钩子函数,界面关闭*/
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {});
|
||||||
if (ws.state() === WebSocket.OPEN) ws.close();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -122,29 +179,194 @@ onBeforeUnmount(() => {
|
|||||||
<!-- 插槽-卡片左侧侧 -->
|
<!-- 插槽-卡片左侧侧 -->
|
||||||
<template #title>
|
<template #title>
|
||||||
<a-space :size="8">
|
<a-space :size="8">
|
||||||
|
<span>
|
||||||
|
{{ t('views.ne.common.neType') }}:
|
||||||
<a-cascader
|
<a-cascader
|
||||||
v-model:value="state.neType"
|
v-model:value="state.neType"
|
||||||
:options="neCascaderOptions"
|
:options="neCascaderOptions"
|
||||||
:placeholder="t('common.selectPlease')"
|
:placeholder="t('common.selectPlease')"
|
||||||
|
:disabled="state.running"
|
||||||
/>
|
/>
|
||||||
<a-radio-group v-model:value="state.dataType" button-style="solid">
|
</span>
|
||||||
|
<a-radio-group
|
||||||
|
v-model:value="state.dataType"
|
||||||
|
button-style="solid"
|
||||||
|
:disabled="state.running"
|
||||||
|
>
|
||||||
<a-radio-button value="options">Options</a-radio-button>
|
<a-radio-button value="options">Options</a-radio-button>
|
||||||
<a-radio-button value="commandb">Command</a-radio-button>
|
<a-radio-button value="command">Command</a-radio-button>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
<a-button @click.prevent="fnConnect(false)">
|
|
||||||
<template #icon><PlayCircleOutlined /></template>
|
|
||||||
Connect {{ state.initialized }}
|
|
||||||
</a-button>
|
|
||||||
</a-space>
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 表格列表 -->
|
<!-- 插槽-卡片右侧 -->
|
||||||
<a-table
|
<template #extra>
|
||||||
:columns="[]"
|
<a-space :size="8">
|
||||||
:data-source="[]"
|
<a-button
|
||||||
:pagination="false"
|
@click.prevent="fnPing()"
|
||||||
:loading="false"
|
type="primary"
|
||||||
/>
|
:loading="state.running"
|
||||||
|
>
|
||||||
|
<template #icon><PlayCircleOutlined /></template>
|
||||||
|
{{ state.running ? 'Running' : 'Launch' }}
|
||||||
|
</a-button>
|
||||||
|
<a-button v-if="state.running" @click.prevent="fnReset()" danger>
|
||||||
|
<template #icon><CloseCircleOutlined /></template>
|
||||||
|
Stop
|
||||||
|
</a-button>
|
||||||
|
<!-- 版本信息 -->
|
||||||
|
<a-popover
|
||||||
|
trigger="click"
|
||||||
|
placement="bottomRight"
|
||||||
|
v-if="state.versionInfo"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
{{ state.versionInfo }}
|
||||||
|
</template>
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
</a-popover>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- options -->
|
||||||
|
<a-form
|
||||||
|
v-if="state.dataType === 'options'"
|
||||||
|
:model="state.data"
|
||||||
|
name="queryParams"
|
||||||
|
layout="horizontal"
|
||||||
|
:label-col="{ span: 6 }"
|
||||||
|
:label-wrap="true"
|
||||||
|
style="padding: 12px"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
label="Destination"
|
||||||
|
name="desAddr"
|
||||||
|
help="dns name or ip address"
|
||||||
|
:label-col="{ span: 3 }"
|
||||||
|
:label-wrap="true"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="state.data.desAddr"
|
||||||
|
:disabled="state.running"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :lg="12" :md="12" :xs="12">
|
||||||
|
<a-form-item
|
||||||
|
label="Interval"
|
||||||
|
name="Interval"
|
||||||
|
help="seconds between sending each packet"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="state.data.interval"
|
||||||
|
:disabled="state.running"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
style="width: 100%"
|
||||||
|
:min="1"
|
||||||
|
:max="120"
|
||||||
|
>
|
||||||
|
</a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="12" :xs="12">
|
||||||
|
<a-form-item label="TTL" name="ttl" help="define time to live">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="state.data.ttl"
|
||||||
|
:disabled="state.running"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
style="width: 100%"
|
||||||
|
:min="1"
|
||||||
|
:max="255"
|
||||||
|
></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="12" :xs="12">
|
||||||
|
<a-form-item
|
||||||
|
label="Count"
|
||||||
|
name="count"
|
||||||
|
help="stop after <count> replies"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="state.data.count"
|
||||||
|
:disabled="state.running"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
style="width: 100%"
|
||||||
|
:min="1"
|
||||||
|
:max="120"
|
||||||
|
></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="12" :xs="12">
|
||||||
|
<a-form-item
|
||||||
|
label="Size"
|
||||||
|
name="size"
|
||||||
|
help="use <size> as number of data bytes to be sent"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="state.data.size"
|
||||||
|
:disabled="state.running"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
style="width: 100%"
|
||||||
|
:min="1"
|
||||||
|
:max="128"
|
||||||
|
></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="12" :xs="12">
|
||||||
|
<a-form-item
|
||||||
|
label="Deadline"
|
||||||
|
name="timeout"
|
||||||
|
help="reply wait <deadline> in seconds"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="state.data.timeout"
|
||||||
|
:disabled="state.running"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
style="width: 100%"
|
||||||
|
:min="1"
|
||||||
|
:max="60"
|
||||||
|
></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
|
||||||
|
<!-- command -->
|
||||||
|
<div v-else style="padding: 12px">
|
||||||
|
<a-auto-complete
|
||||||
|
v-model:value="state.data.command"
|
||||||
|
:disabled="state.running"
|
||||||
|
:dropdown-match-select-width="500"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<a-input addon-before="ping" placeholder="eg: -i 1 -c 4 8.8.8.8" />
|
||||||
|
</a-auto-complete>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 运行过程 -->
|
||||||
|
<TerminalSSHView
|
||||||
|
v-if="state.initialized"
|
||||||
|
ref="toolTerminal"
|
||||||
|
:id="`V${Date.now()}`"
|
||||||
|
prefix="ping"
|
||||||
|
url="/tool/ping/run"
|
||||||
|
:ne-type="state.params.neType"
|
||||||
|
:ne-id="state.params.neId"
|
||||||
|
:rows="state.params.rows"
|
||||||
|
:cols="state.params.cols"
|
||||||
|
style="height: 400px"
|
||||||
|
@connect="fnConnect"
|
||||||
|
@message="fnMessage"
|
||||||
|
></TerminalSSHView>
|
||||||
</a-card>
|
</a-card>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Modal, message } from 'ant-design-vue/lib';
|
|||||||
import { parseDateToStr } from '@/utils/date-utils';
|
import { parseDateToStr } from '@/utils/date-utils';
|
||||||
import { getNeFile, listNeFiles } from '@/api/tool/neFile';
|
import { getNeFile, listNeFiles } from '@/api/tool/neFile';
|
||||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
|
import ViewDrawer from '@/views/logManage/neFile/components/ViewDrawer.vue';
|
||||||
import useNeInfoStore from '@/store/modules/neinfo';
|
import useNeInfoStore from '@/store/modules/neinfo';
|
||||||
import useTabsStore from '@/store/modules/tabs';
|
import useTabsStore from '@/store/modules/tabs';
|
||||||
import useI18n from '@/hooks/useI18n';
|
import useI18n from '@/hooks/useI18n';
|
||||||
@@ -218,16 +219,18 @@ function fnDirCD(dir: string, index?: number) {
|
|||||||
|
|
||||||
/**网元类型选择对应修改 */
|
/**网元类型选择对应修改 */
|
||||||
function fnNeChange(keys: any, _: any) {
|
function fnNeChange(keys: any, _: any) {
|
||||||
// 不是同类型时需要重新加载
|
if (!Array.isArray(keys)) return;
|
||||||
if (Array.isArray(keys) && queryParams.neType !== keys[0]) {
|
|
||||||
const neType = keys[0];
|
const neType = keys[0];
|
||||||
|
const neId = keys[1];
|
||||||
|
// 不是同类型时需要重新加载
|
||||||
|
if (queryParams.neType !== neType || queryParams.neId !== neId) {
|
||||||
queryParams.neType = neType;
|
queryParams.neType = neType;
|
||||||
queryParams.neId = keys[1];
|
queryParams.neId = neId;
|
||||||
if (neType === 'UPF' && tmp.value) {
|
if (neType === 'UPF' && tmp.value) {
|
||||||
nePathArr.value = ['/tmp'];
|
nePathArr.value = ['/tmp'];
|
||||||
queryParams.search = `${neType}_${keys[1]}`;
|
queryParams.search = `${neType}_${neId}`;
|
||||||
} else {
|
} else {
|
||||||
nePathArr.value = [`/tmp/omc/tcpdump/${neType.toLowerCase()}/${keys[1]}`];
|
nePathArr.value = [`/tmp/omc/tcpdump/${neType.toLowerCase()}/${neId}`];
|
||||||
queryParams.search = '';
|
queryParams.search = '';
|
||||||
}
|
}
|
||||||
fnGetList(1);
|
fnGetList(1);
|
||||||
@@ -270,6 +273,25 @@ function fnGetList(pageNum?: number) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**抽屉状态 */
|
||||||
|
const viewDrawerState = reactive({
|
||||||
|
visible: false,
|
||||||
|
/**文件路径 /var/log/amf.log */
|
||||||
|
filePath: '',
|
||||||
|
/**网元类型 */
|
||||||
|
neType: '',
|
||||||
|
/**网元ID */
|
||||||
|
neId: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
/**打开抽屉查看 */
|
||||||
|
function fnDrawerOpen(row: Record<string, any>) {
|
||||||
|
viewDrawerState.filePath = [...nePathArr.value, row.fileName].join('/');
|
||||||
|
viewDrawerState.neType = neTypeSelect.value[0];
|
||||||
|
viewDrawerState.neId = neTypeSelect.value[1];
|
||||||
|
viewDrawerState.visible = !viewDrawerState.visible;
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 获取网元网元列表
|
// 获取网元网元列表
|
||||||
neInfoStore.fnNelist().then(res => {
|
neInfoStore.fnNelist().then(res => {
|
||||||
@@ -375,6 +397,16 @@ onMounted(() => {
|
|||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'fileName'">
|
<template v-if="column.key === 'fileName'">
|
||||||
<a-space :size="8" align="center">
|
<a-space :size="8" align="center">
|
||||||
|
<a-tooltip
|
||||||
|
v-if="
|
||||||
|
record.fileType === 'file' && record.fileName.endsWith('.log')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<template #title>{{ t('common.viewText') }}</template>
|
||||||
|
<a-button type="link" @click.prevent="fnDrawerOpen(record)">
|
||||||
|
<template #icon><ProfileOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
<a-button
|
<a-button
|
||||||
type="link"
|
type="link"
|
||||||
:loading="downLoading"
|
:loading="downLoading"
|
||||||
@@ -398,6 +430,14 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
</a-card>
|
</a-card>
|
||||||
|
|
||||||
|
<!-- 文件内容查看抽屉 -->
|
||||||
|
<ViewDrawer
|
||||||
|
v-model:visible="viewDrawerState.visible"
|
||||||
|
:file-path="viewDrawerState.filePath"
|
||||||
|
:ne-type="viewDrawerState.neType"
|
||||||
|
:ne-id="viewDrawerState.neId"
|
||||||
|
></ViewDrawer>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { useRoute, useRouter } from 'vue-router';
|
|||||||
import { message, Modal } from 'ant-design-vue/lib';
|
import { message, Modal } from 'ant-design-vue/lib';
|
||||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||||
import { PageContainer } from 'antdv-pro-layout';
|
import { PageContainer } from 'antdv-pro-layout';
|
||||||
import { dumpStart, dumpStop, dumpDownload, traceUPF } from '@/api/trace/pcap';
|
import { dumpStart, dumpStop, traceUPF } from '@/api/trace/pcap';
|
||||||
import { listAllNeInfo } from '@/api/ne/neInfo';
|
import { listAllNeInfo } from '@/api/ne/neInfo';
|
||||||
import { getNeFile } from '@/api/tool/neFile';
|
import { getNeDirZip, getNeFile, getNeViewFile } from '@/api/tool/neFile';
|
||||||
import saveAs from 'file-saver';
|
import saveAs from 'file-saver';
|
||||||
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';
|
||||||
@@ -31,7 +31,7 @@ type ModalStateType = {
|
|||||||
/**任务编号 */
|
/**任务编号 */
|
||||||
taskCode: string;
|
taskCode: string;
|
||||||
/**任务日志,upf标准版为空字符串 */
|
/**任务日志,upf标准版为空字符串 */
|
||||||
logMsg: string;
|
taskFiles: string[];
|
||||||
/**提交表单参数 */
|
/**提交表单参数 */
|
||||||
data: {
|
data: {
|
||||||
neType: string;
|
neType: string;
|
||||||
@@ -65,7 +65,14 @@ type ModalStateType = {
|
|||||||
/**详情框是否显示 */
|
/**详情框是否显示 */
|
||||||
visibleByView: boolean;
|
visibleByView: boolean;
|
||||||
/**详情框内容 */
|
/**详情框内容 */
|
||||||
logMsg: string;
|
viewFrom: {
|
||||||
|
neType: string;
|
||||||
|
neId: string;
|
||||||
|
path: string;
|
||||||
|
action: string;
|
||||||
|
files: string[];
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**对话框对象信息状态 */
|
/**对话框对象信息状态 */
|
||||||
@@ -106,7 +113,14 @@ let modalState: ModalStateType = reactive({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
visibleByView: false,
|
visibleByView: false,
|
||||||
logMsg: '',
|
viewFrom: {
|
||||||
|
neType: '',
|
||||||
|
neId: '',
|
||||||
|
path: '',
|
||||||
|
action: '',
|
||||||
|
files: [],
|
||||||
|
content: '',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**表格状态类型 */
|
/**表格状态类型 */
|
||||||
@@ -194,7 +208,7 @@ function fnGetList() {
|
|||||||
cmdStart: start,
|
cmdStart: start,
|
||||||
cmdStop: stop,
|
cmdStop: stop,
|
||||||
taskCode: '',
|
taskCode: '',
|
||||||
logMsg: '',
|
taskFiles: [],
|
||||||
data: {
|
data: {
|
||||||
neType: item.neType,
|
neType: item.neType,
|
||||||
neId: item.neId,
|
neId: item.neId,
|
||||||
@@ -218,7 +232,7 @@ function fnSelectCmd(id: any, option: any) {
|
|||||||
modalState.from[id].cmdStop = option.stop;
|
modalState.from[id].cmdStop = option.stop;
|
||||||
// 重置任务
|
// 重置任务
|
||||||
modalState.from[id].taskCode = '';
|
modalState.from[id].taskCode = '';
|
||||||
modalState.from[id].logMsg = '';
|
modalState.from[id].taskFiles = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -341,7 +355,7 @@ function fnRecordStop(row?: Record<string, any>) {
|
|||||||
if (res.status === 'fulfilled') {
|
if (res.status === 'fulfilled') {
|
||||||
const resV = res.value;
|
const resV = res.value;
|
||||||
fromArr[idx].loading = false;
|
fromArr[idx].loading = false;
|
||||||
fromArr[idx].logMsg = '';
|
fromArr[idx].taskFiles = [];
|
||||||
if (fromArr[idx].cmdStop) {
|
if (fromArr[idx].cmdStop) {
|
||||||
fromArr[idx].taskCode = '';
|
fromArr[idx].taskCode = '';
|
||||||
}
|
}
|
||||||
@@ -350,7 +364,7 @@ function fnRecordStop(row?: Record<string, any>) {
|
|||||||
if (fromArr[idx].cmdStop) {
|
if (fromArr[idx].cmdStop) {
|
||||||
fromArr[idx].taskCode = resV.data;
|
fromArr[idx].taskCode = resV.data;
|
||||||
} else {
|
} else {
|
||||||
fromArr[idx].logMsg = resV.msg;
|
fromArr[idx].taskFiles = resV.data;
|
||||||
}
|
}
|
||||||
message.success({
|
message.success({
|
||||||
content: t('views.traceManage.pcap.stopOk', { title }),
|
content: t('views.traceManage.pcap.stopOk', { title }),
|
||||||
@@ -422,10 +436,15 @@ function fnDownPCAP(row?: Record<string, any>) {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
const { neType, neId } = from.data;
|
||||||
|
const path = `/tmp/omc/tcpdump/${neType.toLowerCase()}/${neId}/${taskCode}`;
|
||||||
reqArr.push(
|
reqArr.push(
|
||||||
dumpDownload(
|
getNeDirZip({
|
||||||
Object.assign({ taskCode: taskCode, delTemp: true }, from.data)
|
neType,
|
||||||
)
|
neId,
|
||||||
|
path,
|
||||||
|
delTemp: true,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -497,8 +516,42 @@ function fnBatchOper(key: string) {
|
|||||||
function fnModalVisibleByVive(id: string | number) {
|
function fnModalVisibleByVive(id: string | number) {
|
||||||
const from = modalState.from[id];
|
const from = modalState.from[id];
|
||||||
if (!from) return;
|
if (!from) return;
|
||||||
|
const { neType, neId } = from.data;
|
||||||
|
const path = `/tmp/omc/tcpdump/${neType.toLowerCase()}/${neId}/${
|
||||||
|
from.taskCode
|
||||||
|
}`;
|
||||||
|
const files = from.taskFiles.filter(f => f.endsWith('log'));
|
||||||
|
modalState.viewFrom.neType = neType;
|
||||||
|
modalState.viewFrom.neId = neId;
|
||||||
|
modalState.viewFrom.path = path;
|
||||||
|
modalState.viewFrom.files = [...files];
|
||||||
|
fnViveTab(files[0]);
|
||||||
modalState.visibleByView = true;
|
modalState.visibleByView = true;
|
||||||
modalState.logMsg = from.logMsg;
|
}
|
||||||
|
|
||||||
|
/**对话框tab查看 */
|
||||||
|
function fnViveTab(action: any) {
|
||||||
|
console.log('fnViveTab', action);
|
||||||
|
if (modalState.viewFrom.action === action) return;
|
||||||
|
modalState.viewFrom.action = action;
|
||||||
|
modalState.viewFrom.content = '';
|
||||||
|
const { neType, neId, path } = modalState.viewFrom;
|
||||||
|
getNeViewFile({
|
||||||
|
neId,
|
||||||
|
neType,
|
||||||
|
path,
|
||||||
|
fileName: action,
|
||||||
|
}).then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
modalState.viewFrom.content = res.data;
|
||||||
|
} else {
|
||||||
|
modalState.viewFrom.content = '';
|
||||||
|
message.warning({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -507,7 +560,8 @@ function fnModalVisibleByVive(id: string | number) {
|
|||||||
*/
|
*/
|
||||||
function fnModalCancel() {
|
function fnModalCancel() {
|
||||||
modalState.visibleByView = false;
|
modalState.visibleByView = false;
|
||||||
modalState.logMsg = '';
|
modalState.viewFrom.action = '';
|
||||||
|
modalState.viewFrom.files = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**跳转文件数据页面 */
|
/**跳转文件数据页面 */
|
||||||
@@ -651,7 +705,7 @@ onMounted(() => {
|
|||||||
placement="topRight"
|
placement="topRight"
|
||||||
v-if="
|
v-if="
|
||||||
!modalState.from[record.id].loading &&
|
!modalState.from[record.id].loading &&
|
||||||
!!modalState.from[record.id].logMsg
|
modalState.from[record.id].taskFiles.length > 0
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
@@ -694,21 +748,39 @@ onMounted(() => {
|
|||||||
<!-- 日志信息框 -->
|
<!-- 日志信息框 -->
|
||||||
<ProModal
|
<ProModal
|
||||||
:drag="true"
|
:drag="true"
|
||||||
|
:fullscreen="true"
|
||||||
|
:borderDraw="true"
|
||||||
:width="800"
|
:width="800"
|
||||||
:visible="modalState.visibleByView"
|
:visible="modalState.visibleByView"
|
||||||
:footer="false"
|
:footer="false"
|
||||||
:maskClosable="false"
|
:maskClosable="false"
|
||||||
:keyboard="false"
|
:keyboard="false"
|
||||||
:body-style="{ padding: '12px' }"
|
:body-style="{ padding: '0 12px 12px' }"
|
||||||
:title="t('views.traceManage.pcap.textLogMsg')"
|
:title="t('views.traceManage.pcap.textLogMsg')"
|
||||||
@cancel="fnModalCancel"
|
@cancel="fnModalCancel"
|
||||||
>
|
>
|
||||||
|
<a-tabs
|
||||||
|
v-model:activeKey="modalState.viewFrom.action"
|
||||||
|
tab-position="top"
|
||||||
|
size="small"
|
||||||
|
@tabClick="fnViveTab"
|
||||||
|
>
|
||||||
|
<a-tab-pane
|
||||||
|
v-for="fileName in modalState.viewFrom.files"
|
||||||
|
:key="fileName"
|
||||||
|
:tab="fileName"
|
||||||
|
:destroyInactiveTabPane="false"
|
||||||
|
>
|
||||||
|
<a-spin :spinning="!modalState.viewFrom.content">
|
||||||
<a-textarea
|
<a-textarea
|
||||||
v-model:value="modalState.logMsg"
|
:value="modalState.viewFrom.content"
|
||||||
:auto-size="{ minRows: 2, maxRows: 18 }"
|
:auto-size="{ minRows: 2 }"
|
||||||
:disabled="true"
|
:disabled="true"
|
||||||
style="color: rgba(0, 0, 0, 0.85)"
|
style="color: rgba(0, 0, 0, 0.85)"
|
||||||
/>
|
/>
|
||||||
|
</a-spin>
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
</ProModal>
|
</ProModal>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
packetStart,
|
packetStart,
|
||||||
packetStop,
|
packetStop,
|
||||||
packetFilter,
|
packetFilter,
|
||||||
|
packetKeep,
|
||||||
} from '@/api/trace/packet';
|
} from '@/api/trace/packet';
|
||||||
const ws = new WS();
|
const ws = new WS();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
@@ -29,6 +30,8 @@ type StateType = {
|
|||||||
devices: { id: string; label: string; children: any[] }[];
|
devices: { id: string; label: string; children: any[] }[];
|
||||||
/**初始化 */
|
/**初始化 */
|
||||||
initialized: boolean;
|
initialized: boolean;
|
||||||
|
/**保活调度器 */
|
||||||
|
keepTimer: any;
|
||||||
/**任务 */
|
/**任务 */
|
||||||
task: {
|
task: {
|
||||||
taskNo: string;
|
taskNo: string;
|
||||||
@@ -64,6 +67,7 @@ type StateType = {
|
|||||||
const state = reactive<StateType>({
|
const state = reactive<StateType>({
|
||||||
devices: [],
|
devices: [],
|
||||||
initialized: false,
|
initialized: false,
|
||||||
|
keepTimer: null,
|
||||||
task: {
|
task: {
|
||||||
taskNo: 'laYlTbq',
|
taskNo: 'laYlTbq',
|
||||||
device: '192.168.5.58',
|
device: '192.168.5.58',
|
||||||
@@ -274,6 +278,9 @@ function wsMessage(res: Record<string, any>) {
|
|||||||
// 建联时发送请求
|
// 建联时发送请求
|
||||||
if (!requestId && data.clientId) {
|
if (!requestId && data.clientId) {
|
||||||
state.initialized = true;
|
state.initialized = true;
|
||||||
|
state.keepTimer = setInterval(() => {
|
||||||
|
packetKeep(state.task.taskNo, 120);
|
||||||
|
}, 90 * 1000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,7 +327,9 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
ws.close();
|
clearInterval(state.keepTimer);
|
||||||
|
state.keepTimer = null;
|
||||||
|
if (ws.state() === WebSocket.OPEN) ws.close();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user