Compare commits
87 Commits
2.2409.4-2
...
2.2410.4-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33a8ce97d3 | ||
|
|
6ee9d464fb | ||
|
|
df5072bae7 | ||
|
|
e12dce1f0f | ||
|
|
d0457fc285 | ||
|
|
63d32f0a39 | ||
|
|
cf5d08aaab | ||
|
|
7ad566d74f | ||
|
|
c312186d91 | ||
|
|
72fd372fe0 | ||
|
|
acdadcbb6f | ||
|
|
7a49de71ea | ||
|
|
56e4419e77 | ||
|
|
3abb4dd4bd | ||
|
|
726a284ab5 | ||
|
|
a45243390b | ||
|
|
1faed9bc3d | ||
|
|
9bd700eeb7 | ||
|
|
5cc3b9c8cf | ||
|
|
208895c7d5 | ||
|
|
46578ce97b | ||
|
|
0ff5bd5e20 | ||
|
|
f08e637e69 | ||
|
|
a600e056b8 | ||
|
|
671c80972e | ||
|
|
d07230b582 | ||
|
|
35c24407ac | ||
|
|
cf33756548 | ||
|
|
1ef98298bc | ||
|
|
b1c2a95ec4 | ||
|
|
147b2fad8d | ||
|
|
b629088406 | ||
|
|
430a067280 | ||
|
|
8a71e8f773 | ||
|
|
ff556ce1ec | ||
|
|
9ed7aed4b4 | ||
|
|
9e14297488 | ||
|
|
8e70706ed5 | ||
|
|
3e0529cf87 | ||
|
|
91af2bed92 | ||
|
|
1ecefb91dc | ||
|
|
72d9895902 | ||
|
|
41fa214137 | ||
|
|
1565f25a03 | ||
|
|
c5c2926d99 | ||
|
|
55456f9220 | ||
|
|
cf1686c348 | ||
|
|
f7833bcd9f | ||
|
|
5a621053a4 | ||
|
|
2a6451ef2a | ||
|
|
f1b440c8dd | ||
|
|
a67e54ca6e | ||
|
|
53d9e63c36 | ||
|
|
b4623d19e5 | ||
|
|
700bff6e38 | ||
|
|
d77c4e43d4 | ||
|
|
6e11d2b16a | ||
|
|
405842bc0b | ||
|
|
bf8d7f2124 | ||
|
|
ba98b37306 | ||
|
|
aa8ed65fd8 | ||
|
|
936a4410b3 | ||
|
|
58ec76f9e5 | ||
|
|
c1a77c8e48 | ||
|
|
477e8e4631 | ||
|
|
4f9d65a3a7 | ||
|
|
b1799d8ccb | ||
|
|
86833e7d6b | ||
|
|
59cf57898b | ||
|
|
fb9382e3a0 | ||
|
|
dae4697cd2 | ||
|
|
1b2e892f74 | ||
|
|
c66c640f75 | ||
|
|
30849416b6 | ||
|
|
5edcee8da5 | ||
|
|
311beed2a7 | ||
|
|
eb5fdfb635 | ||
|
|
78bcde9ef2 | ||
|
|
630e2a16ad | ||
|
|
e1fe031f25 | ||
|
|
855ba7dc9e | ||
|
|
3a72e73d5d | ||
|
|
4cb13a1419 | ||
|
|
8dd84a5255 | ||
|
|
c0e62f48b7 | ||
|
|
b992225e28 | ||
|
|
2f04562a34 |
@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC"
|
||||
VITE_APP_CODE = "OMC"
|
||||
|
||||
# 应用版本
|
||||
VITE_APP_VERSION = "2.240927"
|
||||
VITE_APP_VERSION = "2.241102"
|
||||
|
||||
# 接口基础URL地址-不带/后缀
|
||||
VITE_API_BASE_URL = "/omc-api"
|
||||
|
||||
@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC"
|
||||
VITE_APP_CODE = "OMC"
|
||||
|
||||
# 应用版本
|
||||
VITE_APP_VERSION = "2.240927"
|
||||
VITE_APP_VERSION = "2.241102"
|
||||
|
||||
# 接口基础URL地址-不带/后缀
|
||||
VITE_API_BASE_URL = "/omc-api"
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"dayjs": "^1.11.11",
|
||||
"echarts": "~5.5.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"grid-layout-plus": "^1.0.5",
|
||||
"intl-tel-input": "^23.8.1",
|
||||
"js-base64": "^3.7.7",
|
||||
"js-cookie": "^3.0.5",
|
||||
|
||||
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
178925
public/wiregasm/wiregasm.data
Normal file
178925
public/wiregasm/wiregasm.data
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
BIN
public/wiregasm/wiregasm.wasm
Normal file
BIN
public/wiregasm/wiregasm.wasm
Normal file
Binary file not shown.
Binary file not shown.
@@ -16,12 +16,12 @@ const fetchPackages = async () => {
|
||||
console.log('Fetching packages');
|
||||
let [wasmBuffer, dataBuffer] = await Promise.all([
|
||||
await inflateRemoteBuffer(
|
||||
'/wiregasm/wiregasm.wasm.gz'
|
||||
// 'https://cdn.jsdelivr.net/npm/@goodtools/wiregasm/dist/wiregasm.wasm.gz'
|
||||
'/wiregasm/wiregasm.wasm'
|
||||
// 'https://cdn.jsdelivr.net/npm/@goodtools/wiregasm/dist/wiregasm.wasm'
|
||||
),
|
||||
await inflateRemoteBuffer(
|
||||
'/wiregasm/wiregasm.data.gz'
|
||||
// 'https://cdn.jsdelivr.net/npm/@goodtools/wiregasm/dist/wiregasm.data.gz'
|
||||
'/wiregasm/wiregasm.data'
|
||||
// 'https://cdn.jsdelivr.net/npm/@goodtools/wiregasm/dist/wiregasm.data'
|
||||
),
|
||||
]);
|
||||
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import {
|
||||
RESULT_CODE_ERROR,
|
||||
RESULT_CODE_SUCCESS,
|
||||
RESULT_MSG_ERROR,
|
||||
} from '@/constants/result-constants';
|
||||
import { language, request } from '@/plugins/http-fetch';
|
||||
import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||
|
||||
/**
|
||||
* 查询配置详细
|
||||
* @param tag 配置ID
|
||||
* @returns object
|
||||
*/
|
||||
export async function getConfigInfo(tag: string) {
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/omc_db/config`,
|
||||
method: 'get',
|
||||
params: {
|
||||
SQL: `SELECT * FROM config WHERE config_tag = '${tag}'`,
|
||||
},
|
||||
});
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
|
||||
let data = result.data.data[0];
|
||||
return Object.assign(result, {
|
||||
data: parseObjLineToHump(data['config'][0]),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改配置
|
||||
* @param data 配置对象
|
||||
* @returns object
|
||||
*/
|
||||
export async function updateConfig(tag: string, data: Record<string, any>) {
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/omc_db/config?WHERE=config_tag='${tag}'`,
|
||||
method: 'put',
|
||||
data: { data },
|
||||
});
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS && result.data.data) {
|
||||
let rows = result.data.data.affectedRows;
|
||||
if (rows) {
|
||||
delete result.data;
|
||||
return result;
|
||||
} else {
|
||||
return { code: RESULT_CODE_ERROR, msg: RESULT_MSG_ERROR[language] };
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -2,315 +2,8 @@ import {
|
||||
RESULT_CODE_ERROR,
|
||||
RESULT_CODE_SUCCESS,
|
||||
RESULT_MSG_ERROR,
|
||||
RESULT_MSG_SUCCESS,
|
||||
} from '@/constants/result-constants';
|
||||
import { language, request } from '@/plugins/http-fetch';
|
||||
import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||
|
||||
/**
|
||||
* 查询配置参数标签栏
|
||||
* @param neType 网元类型
|
||||
* @returns object
|
||||
*/
|
||||
export async function getParamConfigTopTab(neType: string) {
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/elementType/omc_db/objectType/param_config`,
|
||||
method: 'get',
|
||||
params: {
|
||||
SQL: `SELECT id,top_display,top_tag,method FROM param_config WHERE ne_type = '${neType}' ORDER BY id ASC`,
|
||||
},
|
||||
});
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
|
||||
let data = result.data.data[0];
|
||||
data = data['param_config'];
|
||||
if (Array.isArray(data)) {
|
||||
return Object.assign(result, {
|
||||
data: parseObjLineToHump(data),
|
||||
});
|
||||
}
|
||||
return Object.assign(result, {
|
||||
data: [],
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询配置参数标签栏对应信息和规则
|
||||
* @param neType 网元类型
|
||||
* @param topTag
|
||||
* @param neId
|
||||
* @returns object { wrRule, dataArr }
|
||||
*/
|
||||
async function getParamConfigInfoAndRule(
|
||||
neType: string,
|
||||
topTag: string,
|
||||
neId: string
|
||||
) {
|
||||
return await Promise.allSettled([
|
||||
// 获取参数规则
|
||||
request({
|
||||
url: `/api/rest/databaseManagement/v1/elementType/omc_db/objectType/param_config`,
|
||||
method: 'get',
|
||||
params: {
|
||||
SQL: `SELECT param_json FROM param_config WHERE ne_type = '${neType}' AND top_tag='${topTag}'`,
|
||||
},
|
||||
}),
|
||||
// 获取对应信息
|
||||
request({
|
||||
url: `/api/rest/systemManagement/v1/elementType/${neType.toLowerCase()}/objectType/config/${topTag}`,
|
||||
method: 'get',
|
||||
params: {
|
||||
ne_id: neId,
|
||||
},
|
||||
}),
|
||||
]).then(resArr => {
|
||||
let wrRule: Record<string, any> = {};
|
||||
// 规则数据
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
const itemV = resArr[0].value;
|
||||
// 解析数据
|
||||
if (
|
||||
itemV.code === RESULT_CODE_SUCCESS &&
|
||||
Array.isArray(itemV.data?.data)
|
||||
) {
|
||||
let itemData = itemV.data.data;
|
||||
const data = itemData[0]['param_config'];
|
||||
if (Array.isArray(data)) {
|
||||
const v = data[0]['param_json'];
|
||||
try {
|
||||
itemData = parseObjLineToHump(JSON.parse(v));
|
||||
wrRule = itemData;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let dataArr: Record<string, any>[] = [];
|
||||
// 对应信息
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
const itemV = resArr[1].value;
|
||||
// 解析数据
|
||||
if (
|
||||
itemV.code === RESULT_CODE_SUCCESS &&
|
||||
Array.isArray(itemV.data?.data)
|
||||
) {
|
||||
let itemData = itemV.data.data;
|
||||
dataArr = parseObjLineToHump(itemData);
|
||||
}
|
||||
}
|
||||
return { wrRule, dataArr };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询配置参数标签栏对应信息-表单结构处理
|
||||
* @param neType 网元类型
|
||||
* @param topTag
|
||||
* @param neId
|
||||
* @returns object
|
||||
*/
|
||||
export async function getParamConfigInfoForm(
|
||||
neType: string,
|
||||
topTag: string,
|
||||
neId: string
|
||||
) {
|
||||
const { wrRule, dataArr } = await getParamConfigInfoAndRule(
|
||||
neType,
|
||||
topTag,
|
||||
neId
|
||||
);
|
||||
|
||||
// 拼装数据
|
||||
const result = {
|
||||
code: RESULT_CODE_SUCCESS,
|
||||
msg: RESULT_MSG_SUCCESS,
|
||||
data: {
|
||||
type: 'list' as 'list' | 'array',
|
||||
data: [] as Record<string, any>[],
|
||||
dataRule: {},
|
||||
},
|
||||
};
|
||||
|
||||
// kv单列表
|
||||
if (Reflect.has(wrRule, 'list')) {
|
||||
result.data.type = 'list';
|
||||
const ruleArr = Object.freeze(wrRule['list']);
|
||||
|
||||
// 列表项数据
|
||||
const dataList = [];
|
||||
for (const item of dataArr) {
|
||||
for (const key in item) {
|
||||
// 规则为准
|
||||
for (const rule of ruleArr) {
|
||||
if (rule['name'] === key) {
|
||||
const ruleItem = Object.assign({ optional: 'true' }, rule, {
|
||||
value: item[key],
|
||||
});
|
||||
dataList.push(ruleItem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result.data.data = dataList;
|
||||
}
|
||||
// 多列表
|
||||
if (Reflect.has(wrRule, 'array')) {
|
||||
result.data.type = 'array';
|
||||
const ruleArr = Object.freeze(wrRule['array']);
|
||||
// 列表项数据
|
||||
const dataArray = [];
|
||||
for (const item of dataArr) {
|
||||
const index = item['index'];
|
||||
let record: Record<string, any>[] = [];
|
||||
for (const key in item) {
|
||||
// 规则为准
|
||||
for (const rule of ruleArr) {
|
||||
if (rule['name'] === key) {
|
||||
const ruleItem = Object.assign({ optional: 'true' }, rule, {
|
||||
value: item[key],
|
||||
});
|
||||
record.push(ruleItem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
dataArray.push({ title: `Index-${index}`, key: index, record });
|
||||
}
|
||||
result.data.data = dataArray;
|
||||
|
||||
// 无数据时,用于新增
|
||||
result.data.dataRule = { title: `Index-0`, key: 0, record: ruleArr };
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询配置参数标签栏对应信息
|
||||
* @param neType 网元类型
|
||||
* @param topTag
|
||||
* @param neId
|
||||
* @returns object
|
||||
*/
|
||||
export async function getParamConfigInfo(
|
||||
neType: string,
|
||||
topTag: string,
|
||||
neId: string
|
||||
) {
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/systemManagement/v1/elementType/${neType.toLowerCase()}/objectType/config/${topTag}`,
|
||||
method: 'get',
|
||||
params: {
|
||||
ne_id: neId,
|
||||
},
|
||||
});
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
|
||||
return Object.assign(result, {
|
||||
data: parseObjLineToHump(result.data.data),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询配置参数标签栏对应信息子节点
|
||||
* @param neType 网元类型
|
||||
* @param topTag
|
||||
* @param neId
|
||||
* @param loc 子节点(index/字段) 1/dnnList
|
||||
* @returns
|
||||
*/
|
||||
export async function getParamConfigInfoChild(
|
||||
neType: string,
|
||||
topTag: string,
|
||||
neId: string,
|
||||
loc: string
|
||||
) {
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/systemManagement/v1/elementType/${neType.toLowerCase()}/objectType/config/${topTag}`,
|
||||
method: 'get',
|
||||
params: {
|
||||
ne_id: neId,
|
||||
loc: loc,
|
||||
},
|
||||
});
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
|
||||
return Object.assign(result, {
|
||||
data: parseObjLineToHump(result.data.data),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改配置参数标签栏对应信息
|
||||
* @param args 对象 {neType,neId,topTag,loc}
|
||||
* @param data 对象 {修改的数据kv}
|
||||
* @returns object
|
||||
*/
|
||||
export function updateParamConfigInfo(
|
||||
type: 'list' | 'array',
|
||||
args: Record<string, any>,
|
||||
data: Record<string, any>
|
||||
) {
|
||||
let url = `/api/rest/systemManagement/v1/elementType/${args.neType.toLowerCase()}/objectType/config/${
|
||||
args.topTag
|
||||
}?ne_id=${args.neId}`;
|
||||
|
||||
// 多列表需要loc
|
||||
if (type === 'array') {
|
||||
url += `&loc=${args.loc}`;
|
||||
}
|
||||
|
||||
return request({
|
||||
url,
|
||||
method: 'put',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增配置参数标签栏对应信息
|
||||
* @param args 对象 {neType,neId,topTag,loc}
|
||||
* @param data 行记录对象
|
||||
* @returns object
|
||||
*/
|
||||
export function addParamConfigInfo(
|
||||
args: Record<string, any>,
|
||||
data: Record<string, any>
|
||||
) {
|
||||
return request({
|
||||
url: `/api/rest/systemManagement/v1/elementType/${args.neType.toLowerCase()}/objectType/config/${
|
||||
args.topTag
|
||||
}?ne_id=${args.neId}&loc=${args.loc}`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除配置参数标签栏对应信息
|
||||
* @param args 对象 {neType,neId,topTag,loc}
|
||||
* loc 多层表的定位信息{index0}/{paraName1}/{index1}
|
||||
* @param data 行记录对象
|
||||
* @returns object
|
||||
*/
|
||||
export function delParamConfigInfo(args: Record<string, any>) {
|
||||
return request({
|
||||
url: `/api/rest/systemManagement/v1/elementType/${args.neType.toLowerCase()}/objectType/config/${
|
||||
args.topTag
|
||||
}?ne_id=${args.neId}&loc=${args.loc}`,
|
||||
method: 'delete',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新网元配置重新载入
|
||||
@@ -343,141 +36,50 @@ export async function updateNeConfigReload(neType: string, neId: string) {
|
||||
|
||||
/**
|
||||
* 从参数配置PCF中获取对应信息提供给PCC用户策略输入框
|
||||
* @param neType 网元类型
|
||||
* @param topTag
|
||||
* @param neId
|
||||
* @returns object { wrRule, dataArr }
|
||||
* @returns object {pccRules,sessionRules,qosTemplate,headerEnrichTemplate,serviceAreaRestriction}
|
||||
*/
|
||||
export async function getPCCRule(neId: any) {
|
||||
return await Promise.allSettled([
|
||||
// 获取参数规则
|
||||
request({
|
||||
url: `/api/rest/systemManagement/v1/elementType/pcf/objectType/config/pccRules`,
|
||||
method: 'get',
|
||||
params: {
|
||||
ne_id: neId,
|
||||
},
|
||||
timeout: 1_000,
|
||||
}),
|
||||
// 获取对应信息
|
||||
request({
|
||||
url: `/api/rest/systemManagement/v1/elementType/pcf/objectType/config/sessionRules`,
|
||||
method: 'get',
|
||||
params: {
|
||||
ne_id: neId,
|
||||
},
|
||||
timeout: 1_000,
|
||||
}),
|
||||
request({
|
||||
url: `/api/rest/systemManagement/v1/elementType/pcf/objectType/config/qosTemplate`,
|
||||
method: 'get',
|
||||
params: {
|
||||
ne_id: neId,
|
||||
},
|
||||
timeout: 1_000,
|
||||
}),
|
||||
request({
|
||||
url: `/api/rest/systemManagement/v1/elementType/pcf/objectType/config/headerEnrichTemplate`,
|
||||
method: 'get',
|
||||
params: {
|
||||
ne_id: neId,
|
||||
},
|
||||
timeout: 1_000,
|
||||
}),
|
||||
request({
|
||||
url: `/api/rest/systemManagement/v1/elementType/pcf/objectType/config/serviceAreaRestriction`,
|
||||
method: 'get',
|
||||
params: {
|
||||
ne_id: neId,
|
||||
},
|
||||
timeout: 1_000,
|
||||
}),
|
||||
]).then(resArr => {
|
||||
let pccJson: any = new Map();
|
||||
let sessJson: any = new Map();
|
||||
let qosJson: any = new Map();
|
||||
let headerJson: any = new Map();
|
||||
let sarJson: any = new Map();
|
||||
const paramNameArr = [
|
||||
'pccRules',
|
||||
'sessionRules',
|
||||
'qosTemplate',
|
||||
'headerEnrichTemplate',
|
||||
'serviceAreaRestriction',
|
||||
];
|
||||
const reqArr = [];
|
||||
for (const paramName of paramNameArr) {
|
||||
reqArr.push(
|
||||
request({
|
||||
url: `/ne/config/data`,
|
||||
params: { neType: 'PCF', neId, paramName },
|
||||
method: 'get',
|
||||
})
|
||||
);
|
||||
}
|
||||
return await Promise.allSettled(reqArr).then(resArr => {
|
||||
// 规则数据
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
const itemV = resArr[0].value;
|
||||
// 解析数据
|
||||
if (
|
||||
itemV.code === RESULT_CODE_SUCCESS &&
|
||||
Array.isArray(itemV.data?.data)
|
||||
) {
|
||||
let itemData = itemV.data.data;
|
||||
itemData.forEach((item: any) => {
|
||||
pccJson.set(item.ruleId, { value: item.ruleId, label: item.ruleId });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
const itemV = resArr[1].value;
|
||||
// 解析数据
|
||||
if (
|
||||
itemV.code === RESULT_CODE_SUCCESS &&
|
||||
Array.isArray(itemV.data?.data)
|
||||
) {
|
||||
let itemData = itemV.data.data;
|
||||
itemData.forEach((item: any) => {
|
||||
sessJson.set(item.ruleId, { value: item.ruleId, label: item.ruleId });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (resArr[2].status === 'fulfilled') {
|
||||
const itemV = resArr[2].value;
|
||||
// 解析数据
|
||||
if (
|
||||
itemV.code === RESULT_CODE_SUCCESS &&
|
||||
Array.isArray(itemV.data?.data)
|
||||
) {
|
||||
let itemData = itemV.data.data;
|
||||
itemData.forEach((item: any) => {
|
||||
qosJson.set(item.qosId, { value: item.qosId, label: item.qosId });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (resArr[3].status === 'fulfilled') {
|
||||
const itemV = resArr[3].value;
|
||||
// 解析数据
|
||||
if (
|
||||
itemV.code === RESULT_CODE_SUCCESS &&
|
||||
Array.isArray(itemV.data?.data)
|
||||
) {
|
||||
let itemData = itemV.data.data;
|
||||
itemData.forEach((item: any) => {
|
||||
headerJson.set(item.templateName, {
|
||||
value: item.templateName,
|
||||
label: item.templateName,
|
||||
const obj: any = {};
|
||||
resArr.forEach((item, i: number) => {
|
||||
if (item.status === 'fulfilled') {
|
||||
const res = item.value;
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
const key = paramNameArr[i];
|
||||
obj[key] = res.data.map((item: any) => {
|
||||
if ('qosTemplate' === key) {
|
||||
return { value: item.qosId, label: item.qosId };
|
||||
}
|
||||
if ('headerEnrichTemplate' === key) {
|
||||
return { value: item.templateName, label: item.templateName };
|
||||
}
|
||||
if ('serviceAreaRestriction' === key) {
|
||||
return { value: item.name, label: item.name };
|
||||
}
|
||||
return { value: item.ruleId, label: item.ruleId };
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resArr[4].status === 'fulfilled') {
|
||||
const itemV = resArr[4].value;
|
||||
// 解析数据
|
||||
if (
|
||||
itemV.code === RESULT_CODE_SUCCESS &&
|
||||
Array.isArray(itemV.data?.data)
|
||||
) {
|
||||
let itemData = itemV.data.data;
|
||||
itemData.forEach((item: any) => {
|
||||
sarJson.set(item.name, { value: item.name, label: item.name });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pccJson = Array.from(pccJson.values());
|
||||
sessJson = Array.from(sessJson.values());
|
||||
qosJson = Array.from(qosJson.values());
|
||||
headerJson = Array.from(headerJson.values());
|
||||
sarJson = Array.from(sarJson.values());
|
||||
|
||||
return { pccJson, sessJson, qosJson, headerJson, sarJson };
|
||||
});
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||
|
||||
/**
|
||||
* 查询软件列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export async function listLicense(query: Record<string, any>) {
|
||||
let totalSQL = 'select count(id) as total from ne_license ';
|
||||
let rowsSQL = ' select * from ne_license ';
|
||||
|
||||
// 查询
|
||||
let querySQL = 'where 1=1';
|
||||
if (query.neType) {
|
||||
querySQL += ` and ne_type like '%${query.neType}%' `;
|
||||
}
|
||||
|
||||
// 分页
|
||||
const pageNum = (query.pageNum - 1) * query.pageSize;
|
||||
const limtSql = ` order by create_time desc limit ${pageNum},${query.pageSize} `;
|
||||
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/select/omc_db/ne_license`,
|
||||
method: 'get',
|
||||
params: {
|
||||
totalSQL: totalSQL + querySQL,
|
||||
rowsSQL: rowsSQL + querySQL + limtSql,
|
||||
},
|
||||
});
|
||||
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS) {
|
||||
const data: DataList = {
|
||||
total: 0,
|
||||
rows: [],
|
||||
code: result.code,
|
||||
msg: result.msg,
|
||||
};
|
||||
result.data.data.forEach((item: any) => {
|
||||
const itemData = item['ne_license'];
|
||||
if (Array.isArray(itemData)) {
|
||||
if (itemData.length === 1 && itemData[0]['total'] >= 0) {
|
||||
data.total = itemData[0]['total'];
|
||||
} else {
|
||||
data.rows = itemData.map(v => parseObjLineToHump(v));
|
||||
}
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
* @param data 表单数据对象
|
||||
* @returns object
|
||||
*/
|
||||
export function uploadLicense(data: FormData) {
|
||||
return request({
|
||||
url: `/api/rest/systemManagement/v1/elementType/${data.get(
|
||||
'nfType'
|
||||
)}/objectType/license?neId=${data.get('nfId')}`,
|
||||
method: 'post',
|
||||
data,
|
||||
dataType: 'form-data',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
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';
|
||||
|
||||
/**
|
||||
* 查询文件列表列表
|
||||
* 查询网元端文件列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
@@ -14,7 +14,7 @@ export function listNeFiles(query: Record<string, any>) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 从网元端获取文件
|
||||
* 从网元到本地获取文件
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
@@ -27,3 +27,24 @@ export function getNeFile(query: Record<string, any>) {
|
||||
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,
|
||||
});
|
||||
}
|
||||
64
src/api/trace/packet.ts
Normal file
64
src/api/trace/packet.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 信令跟踪网卡设备列表
|
||||
* @returns
|
||||
*/
|
||||
export function packetDevices() {
|
||||
return request({
|
||||
url: '/trace/packet/devices',
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 信令跟踪开始
|
||||
* @param data 对象
|
||||
* @returns
|
||||
*/
|
||||
export function packetStart(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/trace/packet/start',
|
||||
method: 'post',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 信令跟踪结束
|
||||
* @param data 对象
|
||||
* @returns
|
||||
*/
|
||||
export function packetStop(taskNo: string) {
|
||||
return request({
|
||||
url: '/trace/packet/stop',
|
||||
method: 'post',
|
||||
data: { taskNo },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 信令跟踪过滤
|
||||
* @param data 对象
|
||||
* @returns
|
||||
*/
|
||||
export function packetFilter(taskNo: string, expr: string) {
|
||||
return request({
|
||||
url: '/trace/packet/filter',
|
||||
method: 'put',
|
||||
data: { taskNo, expr },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 信令跟踪续期保活
|
||||
* @param data 对象
|
||||
* @returns
|
||||
*/
|
||||
export function packetKeep(taskNo: string, duration: number = 120) {
|
||||
return request({
|
||||
url: '/trace/packet/keep-alive',
|
||||
method: 'put',
|
||||
data: { taskNo, duration },
|
||||
});
|
||||
}
|
||||
@@ -6,6 +6,7 @@ export function dumpStart(data: Record<string, string>) {
|
||||
url: '/trace/tcpdump/start',
|
||||
method: 'post',
|
||||
data: data,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,16 +16,6 @@ export function dumpStop(data: Record<string, string>) {
|
||||
url: '/trace/tcpdump/stop',
|
||||
method: 'post',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
// 网元抓包PACP 下载
|
||||
export function dumpDownload(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/trace/tcpdump/download',
|
||||
method: 'get',
|
||||
params: data,
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
@@ -35,5 +26,6 @@ export function traceUPF(data: Record<string, string>) {
|
||||
url: '/trace/tcpdump/upf',
|
||||
method: 'post',
|
||||
data: data,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,6 +13,11 @@ const props = defineProps({
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
/**ws连接地址,必传 如/ws/view */
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
/**网元类型,必传 */
|
||||
neType: {
|
||||
type: String,
|
||||
@@ -33,6 +38,11 @@ const props = defineProps({
|
||||
type: Number,
|
||||
default: 40,
|
||||
},
|
||||
/**ws发送requestId前缀 如ssh_id */
|
||||
prefix: {
|
||||
type: String,
|
||||
default: 'ssh',
|
||||
},
|
||||
});
|
||||
|
||||
/**终端输入DOM节点实例对象 */
|
||||
@@ -148,13 +158,18 @@ function wsMessage(res: Record<string, any>) {
|
||||
if (parts.length > 0) {
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
// 是否还有最后输出标记
|
||||
lestIndex = text.lastIndexOf('\u001b[?2004h');
|
||||
if (lestIndex !== -1) {
|
||||
text = text.substring(0, lestIndex);
|
||||
}
|
||||
// console.log({ parts, text });
|
||||
terminal.value.write(text);
|
||||
return;
|
||||
@@ -168,7 +183,7 @@ onMounted(() => {
|
||||
if (props.neType && props.neId) {
|
||||
// 建立链接
|
||||
const options: OptionsType = {
|
||||
url: '/ws/view',
|
||||
url: props.url,
|
||||
params: {
|
||||
neType: props.neType,
|
||||
neId: props.neId,
|
||||
@@ -185,7 +200,7 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
ws.close();
|
||||
if (ws.state() === WebSocket.OPEN) ws.close();
|
||||
});
|
||||
|
||||
// 给组件设置属性 ref="xxxTerminal"
|
||||
@@ -200,7 +215,7 @@ defineExpose({
|
||||
/**发送命令 */
|
||||
send: (type: string, data: Record<string, any>) => {
|
||||
ws.send({
|
||||
requestId: `ssh_${props.id}`,
|
||||
requestId: `${props.prefix}_${props.id}`,
|
||||
type,
|
||||
data,
|
||||
});
|
||||
@@ -208,7 +223,7 @@ defineExpose({
|
||||
/**模拟按下 Ctrl+C */
|
||||
ctrlC: () => {
|
||||
ws.send({
|
||||
requestId: `ssh_${props.id}`,
|
||||
requestId: `${props.prefix}_${props.id}`,
|
||||
type: 'ctrl-c',
|
||||
});
|
||||
},
|
||||
|
||||
@@ -567,6 +567,8 @@ export default {
|
||||
rowInfo: "Info",
|
||||
type: "Type",
|
||||
duration: "Duration",
|
||||
seizureTime: "Call Start Time",
|
||||
releaseTime: "Hangup Time",
|
||||
caller: "Caller",
|
||||
called: "Called",
|
||||
result: "Result",
|
||||
@@ -685,11 +687,12 @@ export default {
|
||||
addrPlease: "Please fill in the host IP address correctly",
|
||||
port: "Port",
|
||||
portPlease: "Please fill in the host port number correctly",
|
||||
user: "Login User",
|
||||
userPlease: "Please fill in the host login user correctly",
|
||||
user: "User",
|
||||
userPlease: "Please fill in the host user correctly",
|
||||
database: "DataBase",
|
||||
authMode: "Auth Mode",
|
||||
password: "Password",
|
||||
passwordPlease: "Please fill in the host login password correctly",
|
||||
passwordPlease: "Please fill in the host password correctly",
|
||||
privateKey: "Private Key",
|
||||
privateKeyPlease: "Please fill in the private key characters correctly ~/.ssh/id_rsa",
|
||||
passPhrase: "Private Key Cipher",
|
||||
@@ -1071,7 +1074,26 @@ export default {
|
||||
element:'Element',
|
||||
granularity:'Granularity',
|
||||
unit:'Unit',
|
||||
}
|
||||
},
|
||||
kpiKeyTarget:{
|
||||
"fullWidthLayout":"Full Width",
|
||||
"twoColumnLayout":"Two Column",
|
||||
"saveLayout": "Save Layout",
|
||||
"restoreSaved": "Restore Layout",
|
||||
"saveSuccess": " '{name}' saved successfully",
|
||||
"restoreSavedSuccess": " '{name}' restored successfully",
|
||||
"noSavedLayout": "No saved layout found for '{name}'",
|
||||
"layout1": "Layout 1",
|
||||
"layout2": "Layout 2",
|
||||
"layout3": "Layout 3"
|
||||
},
|
||||
kpiOverView:{
|
||||
"kpiChartTitle":"Overview of NE metrics",
|
||||
"changeLine":"Change to Line Charts",
|
||||
"changeBar":"Change to Bar Charts",
|
||||
"chooseShowMetrics":"Select the metric you want to display",
|
||||
"chooseMetrics":"Select an indicator",
|
||||
},
|
||||
},
|
||||
traceManage: {
|
||||
analysis: {
|
||||
@@ -1110,8 +1132,8 @@ export default {
|
||||
fileUPFTip: 'UPF internal packet capture and analysis packet',
|
||||
textStart: "Start",
|
||||
textStop: "Stop",
|
||||
textLog: "Log",
|
||||
textLogMsg: "Log Info",
|
||||
textLog: "LogFile",
|
||||
textLogMsg: "LogFile Info",
|
||||
textDown: "Download",
|
||||
downTip: "Are you sure you want to download the {title} capture data file?",
|
||||
downOk: "{title} file download complete",
|
||||
@@ -1287,7 +1309,7 @@ export default {
|
||||
},
|
||||
exportFile:{
|
||||
fileName:'File Source',
|
||||
downTip: "Confirm the download file name is [{fileName}] File?",
|
||||
downTip: "Confirm the download file name is [{fileName}] File?",
|
||||
downTipErr: "Failed to get file",
|
||||
deleteTip: "Confirm the delete file name is [{fileName}] File?",
|
||||
deleteTipErr: "Failed to delete file",
|
||||
@@ -1782,6 +1804,10 @@ export default {
|
||||
reset: "System Reset",
|
||||
resetInstruction: "A system reset will erase all data in the current system, please proceed with caution!!!!",
|
||||
resetTipContent: 'Are you sure you want to clear all data from the current system and insist on continuing?',
|
||||
homeInstruction:'Set the home page',
|
||||
home: 'Home Page',
|
||||
homeTip:'Do you want to submit the current interface as the system interface?',
|
||||
homeSet:'Home Page Settings',
|
||||
},
|
||||
role:{
|
||||
allScopeOptions:'All data permissions',
|
||||
@@ -2112,7 +2138,7 @@ export default {
|
||||
realTimeStop:"Stop",
|
||||
realTime:"Real Time Speed",
|
||||
pid:"PID",
|
||||
name:"APP Name",
|
||||
name:"Program name",
|
||||
username:"User Name",
|
||||
runTime:"Run Time",
|
||||
numThreads:"Thread",
|
||||
@@ -2121,13 +2147,11 @@ export default {
|
||||
diskWrite:"Disk Write",
|
||||
},
|
||||
net:{
|
||||
PID:"PID",
|
||||
name:"name",
|
||||
localAddr:"localAddr",
|
||||
remoteAddr:"remoteAddr",
|
||||
status:"status",
|
||||
type:"type",
|
||||
port:"port",
|
||||
localAddr:"Local Address",
|
||||
remoteAddr:"Foreign Address",
|
||||
status:"State",
|
||||
proto:"Proto",
|
||||
port:"Port",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -567,6 +567,8 @@ export default {
|
||||
rowInfo: "记录信息",
|
||||
type: "记录类型",
|
||||
duration: "通话时长",
|
||||
seizureTime: "呼叫开始时间",
|
||||
releaseTime: "挂断结束时间",
|
||||
caller: "主叫",
|
||||
called: "被叫",
|
||||
result: "结果",
|
||||
@@ -686,10 +688,11 @@ export default {
|
||||
port: "端口",
|
||||
portPlease: "请正确填写主机端口号",
|
||||
user: "用户名",
|
||||
userPlease: "请正确填写主机登录用户",
|
||||
userPlease: "请正确填写主机用户",
|
||||
database: "数据库",
|
||||
authMode: "认证模式",
|
||||
password: "密码",
|
||||
passwordPlease: "请正确填写主机登录密码",
|
||||
passwordPlease: "请正确填写主机密码",
|
||||
privateKey: "私钥",
|
||||
privateKeyPlease: "请正确填写私钥字符内容 ~/.ssh/id_rsa",
|
||||
passPhrase: "私钥密码",
|
||||
@@ -1071,7 +1074,27 @@ export default {
|
||||
element:'元素',
|
||||
granularity:'颗粒度',
|
||||
unit:'单位',
|
||||
}
|
||||
},
|
||||
kpiKeyTarget:{
|
||||
"fullWidthLayout":"全宽布局",
|
||||
"twoColumnLayout":"两列布局",
|
||||
"saveLayout": "保存布局",
|
||||
"restoreSaved": "恢复布局",
|
||||
"saveSuccess": " {name} 保存成功",
|
||||
"restoreSavedSuccess": " {name} 恢复成功",
|
||||
"noSavedLayout": "没有找到保存的布局 {name}",
|
||||
"layout1": "布局1",
|
||||
"layout2": "布局2",
|
||||
"layout3": "布局3"
|
||||
},
|
||||
kpiOverView:{
|
||||
"kpiChartTitle":"网元指标概览",
|
||||
"changeLine":"切换为折线图",
|
||||
"changeBar":"切换为柱状图",
|
||||
"chooseShowMetrics":"选择需要显示的指标",
|
||||
"chooseMetrics":"选择指标",
|
||||
|
||||
},
|
||||
},
|
||||
traceManage: {
|
||||
analysis: {
|
||||
@@ -1110,8 +1133,8 @@ export default {
|
||||
fileUPFTip: 'UPF内部抓包分析包',
|
||||
textStart: "开始",
|
||||
textStop: "停止",
|
||||
textLog: "日志",
|
||||
textLogMsg: "日志信息",
|
||||
textLog: "日志文件",
|
||||
textLogMsg: "日志文件信息",
|
||||
textDown: "下载",
|
||||
downTip: "确认要下载 {title} 抓包数据文件吗?",
|
||||
downOk: "{title} 文件下载完成",
|
||||
@@ -1126,7 +1149,7 @@ export default {
|
||||
stopNotRun: "{title} 任务未运行",
|
||||
},
|
||||
task: {
|
||||
traceId: '跟踪编号',
|
||||
traceId: '跟踪编号',
|
||||
trackType: '跟踪类型',
|
||||
trackTypePlease: '请选择跟踪类型',
|
||||
creater: '创建人',
|
||||
@@ -1287,7 +1310,7 @@ export default {
|
||||
},
|
||||
exportFile:{
|
||||
fileName:'文件来源',
|
||||
downTip: "确认下载文件名为 【{fileName}】 文件?",
|
||||
downTip: "确认下载文件名为 【{fileName}】 文件?",
|
||||
downTipErr: "文件获取失败",
|
||||
deleteTip: "确认删除文件名为 【{fileName}】 文件?",
|
||||
deleteTipErr: "文件删除失败",
|
||||
@@ -1782,6 +1805,10 @@ export default {
|
||||
reset: "系统重置",
|
||||
resetInstruction: "系统重置将会清除当前系统内所有数据,请谨慎操作!!!",
|
||||
resetTipContent: '确认要清除当前系统内所有数据并坚持继续吗?',
|
||||
homeInstruction:'设置系统首页界面',
|
||||
home: '系统首页',
|
||||
homeTip:'确认要提交当前界面为系统界面吗?',
|
||||
homeSet:'系统首页设置',
|
||||
},
|
||||
role:{
|
||||
allScopeOptions:'全部数据权限',
|
||||
@@ -2105,7 +2132,7 @@ export default {
|
||||
hostSelectMore: "加载更多 {num}",
|
||||
hostSelectHeader: "主机列表",
|
||||
},
|
||||
ps:{
|
||||
ps:{
|
||||
realTimeHigh:"高",
|
||||
realTimeLow:"低",
|
||||
realTimeRegular:"常规",
|
||||
@@ -2121,13 +2148,11 @@ export default {
|
||||
diskWrite:"磁盘写入",
|
||||
},
|
||||
net:{
|
||||
PID:"PID",
|
||||
name:"名称",
|
||||
localAddr:"localAddr",
|
||||
remoteAddr:"remoteAddr",
|
||||
localAddr:"本地地址",
|
||||
remoteAddr:"远程地址",
|
||||
status:"状态",
|
||||
type:"类型",
|
||||
port:"接口",
|
||||
proto:"协议",
|
||||
port:"端口",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -187,7 +187,6 @@ function beforeRequest(options: OptionsType): OptionsType | Promise<any> {
|
||||
const separator = options.url.includes('?') ? '&' : '?';
|
||||
// 请求加密
|
||||
if (options.crypto) {
|
||||
debugger;
|
||||
const data = encryptAES(JSON.stringify(paramStr), APP_DATA_API_KEY);
|
||||
options.url += `${separator}data=${encodeURIComponent(data)}`;
|
||||
} else {
|
||||
|
||||
@@ -172,15 +172,16 @@ export function parseSizeFromKbs(sizeByte: number, timeInterval: number): any {
|
||||
|
||||
/**
|
||||
* 字节数转换单位
|
||||
* @param bits 字节Bit大小
|
||||
* @returns MB
|
||||
* @param bits 字节Bit大小 64009540 = 512.08 MB
|
||||
* @returns xx B / KB / MB / GB / TB / PB / EB / ZB / YB
|
||||
*/
|
||||
export function parseSizeFromBits(bits: number | string): string {
|
||||
bits = Number(bits) || 0;
|
||||
if (bits <= 0) return '0 B';
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
bits = bits * 8;
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
const unitIndex = Math.floor(Math.log2(bits) / 10);
|
||||
const value = (bits / Math.pow(1024, unitIndex)).toFixed(2);
|
||||
const value = (bits / Math.pow(1000, unitIndex)).toFixed(2);
|
||||
const unti = units[unitIndex];
|
||||
return `${value} ${unti}`;
|
||||
}
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { regExpIPv4, regExpIPv6, validURL } from '@/utils/regular-utils';
|
||||
|
||||
export default function useOptions() {
|
||||
const { t } = useI18n();
|
||||
|
||||
/**规则校验 */
|
||||
function ruleVerification(row: Record<string, any>): (string | boolean)[] {
|
||||
let result = [true, ''];
|
||||
const type = row.type;
|
||||
const value = row.value;
|
||||
const filter = row.filter;
|
||||
const display = row.display;
|
||||
|
||||
// 子嵌套的不检查
|
||||
if (row.array) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// 可选的同时没有值不检查
|
||||
if (row.optional === 'true' && !value) {
|
||||
return result;
|
||||
}
|
||||
switch (type) {
|
||||
case 'int':
|
||||
// filter: "0~128"
|
||||
|
||||
if (filter && filter.indexOf('~') !== -1) {
|
||||
const filterArr = filter.split('~');
|
||||
const minInt = parseInt(filterArr[0]);
|
||||
const maxInt = parseInt(filterArr[1]);
|
||||
const valueInt = parseInt(value);
|
||||
if (valueInt < minInt || valueInt > maxInt) {
|
||||
return [
|
||||
false,
|
||||
t('views.configManage.configParamForm.requireInt', {
|
||||
display,
|
||||
filter,
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'ipv4':
|
||||
if (!regExpIPv4.test(value)) {
|
||||
return [
|
||||
false,
|
||||
t('views.configManage.configParamForm.requireIpv4', { display }),
|
||||
];
|
||||
}
|
||||
break;
|
||||
case 'ipv6':
|
||||
if (!regExpIPv6.test(value)) {
|
||||
return [
|
||||
false,
|
||||
t('views.configManage.configParamForm.requireIpv6', { display }),
|
||||
];
|
||||
}
|
||||
break;
|
||||
case 'enum':
|
||||
if (filter && filter.indexOf('{') === 1) {
|
||||
let filterJson: Record<string, any> = {};
|
||||
try {
|
||||
filterJson = JSON.parse(filter); //string---json
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
if (!Object.keys(filterJson).includes(`${value}`)) {
|
||||
return [
|
||||
false,
|
||||
t('views.configManage.configParamForm.requireEnum', { display }),
|
||||
];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'bool':
|
||||
// filter: '{"0":"false", "1":"true"}'
|
||||
|
||||
if (filter && filter.indexOf('{') === 1) {
|
||||
let filterJson: Record<string, any> = {};
|
||||
try {
|
||||
filterJson = JSON.parse(filter); //string---json
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
if (!Object.values(filterJson).includes(`${value}`)) {
|
||||
return [
|
||||
false,
|
||||
t('views.configManage.configParamForm.requireBool', { display }),
|
||||
];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'string':
|
||||
// filter: "0~128"
|
||||
|
||||
// 字符串长度判断
|
||||
if (filter && filter.indexOf('~') !== -1) {
|
||||
try {
|
||||
const filterArr = filter.split('~');
|
||||
let rule = new RegExp(
|
||||
'^\\S{' + filterArr[0] + ',' + filterArr[1] + '}$'
|
||||
);
|
||||
if (!rule.test(value)) {
|
||||
return [
|
||||
false,
|
||||
t('views.configManage.configParamForm.requireString', {
|
||||
display,
|
||||
}),
|
||||
];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
// 字符串http判断
|
||||
if (value.startsWith('http')) {
|
||||
try {
|
||||
if (!validURL(value)) {
|
||||
return [
|
||||
false,
|
||||
t('views.configManage.configParamForm.requireString', {
|
||||
display,
|
||||
}),
|
||||
];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case 'regex':
|
||||
// filter: "^[0-9]{3}$"
|
||||
|
||||
if (filter) {
|
||||
try {
|
||||
let regex = new RegExp(filter);
|
||||
if (!regex.test(value)) {
|
||||
return [
|
||||
false,
|
||||
t('views.configManage.configParamForm.requireString', {
|
||||
display,
|
||||
}),
|
||||
];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return [
|
||||
false,
|
||||
t('views.configManage.configParamForm.requireUn', { display }),
|
||||
];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return { ruleVerification };
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { getParamConfigInfo } from '@/api/configManage/configParam';
|
||||
import { ref } from 'vue';
|
||||
|
||||
export default function useSMFOptions() {
|
||||
/**upfId可选择 */
|
||||
const optionsUPFIds = ref<{ value: string; label: string }[]>([]);
|
||||
|
||||
/**初始加载upfId */
|
||||
function initUPFIds() {
|
||||
getParamConfigInfo('smf', 'upfConfig', '001').then(res => {
|
||||
optionsUPFIds.value = [];
|
||||
for (const s of res.data) {
|
||||
optionsUPFIds.value.push({
|
||||
value: s.id,
|
||||
label: s.id,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return { initUPFIds, optionsUPFIds };
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,500 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { Form, message } from 'ant-design-vue/lib';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { uploadLicense, listLicense } from '@/api/configManage/license';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import { FileType } from 'ant-design-vue/lib/upload/interface';
|
||||
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
const neInfoStore = useNeInfoStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**网元类型 */
|
||||
neType: '',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
neType: '',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
seached: true,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('views.configManage.license.neType'),
|
||||
dataIndex: 'neType',
|
||||
align: 'center',
|
||||
width: 2,
|
||||
},
|
||||
{
|
||||
title: t('views.configManage.license.neId'),
|
||||
dataIndex: 'neId',
|
||||
align: 'center',
|
||||
width: 2,
|
||||
},
|
||||
{
|
||||
title: t('views.configManage.license.serialNum'),
|
||||
dataIndex: 'serialNum',
|
||||
align: 'center',
|
||||
width: 3,
|
||||
},
|
||||
{
|
||||
title: t('views.configManage.license.comment'),
|
||||
dataIndex: 'remark',
|
||||
align: 'center',
|
||||
width: 5,
|
||||
},
|
||||
{
|
||||
title: t('views.configManage.license.createTime'),
|
||||
dataIndex: 'createTime',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value);
|
||||
},
|
||||
width: 2,
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**查询信息列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
listLicense(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
tablePagination.total = res.total;
|
||||
tableState.data = res.rows;
|
||||
if (
|
||||
tablePagination.total <=
|
||||
(queryParams.pageNum - 1) * tablePagination.pageSize &&
|
||||
queryParams.pageNum !== 1
|
||||
) {
|
||||
tableState.loading = false;
|
||||
fnGetList(queryParams.pageNum - 1);
|
||||
}
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**新增框或修改框是否显示 */
|
||||
visibleByEdit: boolean;
|
||||
/**网元版本历史框是否显示 */
|
||||
visibleByHistory: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
visibleByEdit: false,
|
||||
visibleByHistory: false,
|
||||
title: '任务设置',
|
||||
from: {
|
||||
neType: undefined,
|
||||
comment: '',
|
||||
file: undefined,
|
||||
fileList: [],
|
||||
},
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param noticeId 网元id, 不传为新增
|
||||
*/
|
||||
function fnModalVisibleByEdit() {
|
||||
modalState.title = t('common.uploadText');
|
||||
modalState.visibleByEdit = true;
|
||||
}
|
||||
|
||||
/**对话框内表单属性和校验规则 */
|
||||
const modalStateFrom = Form.useForm(
|
||||
modalState.from,
|
||||
reactive({
|
||||
neType: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.configManage.license.neTypePlease'),
|
||||
},
|
||||
],
|
||||
comment: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.configManage.license.updateCommentPlease'),
|
||||
},
|
||||
],
|
||||
file: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.configManage.license.updateFilePlease'),
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalOk() {
|
||||
modalStateFrom
|
||||
.validate()
|
||||
.then(e => {
|
||||
modalState.confirmLoading = true;
|
||||
const from = toRaw(modalState.from);
|
||||
let formData = new FormData();
|
||||
formData.append('nfType', from.neType[0]);
|
||||
formData.append('nfId', from.neType[1]);
|
||||
formData.append('comment', from.comment);
|
||||
formData.append('file', from.file);
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
uploadLicense(formData)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: modalState.title }),
|
||||
duration: 3,
|
||||
});
|
||||
modalState.visibleByEdit = false;
|
||||
modalStateFrom.resetFields();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
modalState.confirmLoading = false;
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.visibleByEdit = false;
|
||||
modalState.visibleByHistory = false;
|
||||
modalStateFrom.resetFields();
|
||||
}
|
||||
|
||||
/**上传前检查或转换压缩 */
|
||||
function fnBeforeUploadFile(file: FileType) {
|
||||
if (modalState.confirmLoading) return false;
|
||||
const fileName = file.name;
|
||||
const suff = fileName.substring(fileName.lastIndexOf('.'));
|
||||
if (!['.ini'].includes(suff)) {
|
||||
message.error(
|
||||
t('views.configManage.softwareManage.onlyAble', { fileText: '(.ini)' }),
|
||||
3
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**上传文件 */
|
||||
function fnUploadFile(up: UploadRequestOption) {
|
||||
// 改为完成状态
|
||||
const file = modalState.from.fileList[0];
|
||||
file.percent = 100;
|
||||
file.status = 'done';
|
||||
// 预置到表单
|
||||
modalState.from.file = up.file;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取网元网元列表
|
||||
neInfoStore.fnNelist().then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
v-show="tableState.seached"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.license.neType')"
|
||||
name="neType "
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="queryParams.neType"
|
||||
:options="neInfoStore.getNeSelectOtions"
|
||||
allow-clear
|
||||
:placeholder="t('views.configManage.license.neTypePlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
{{ t('common.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button type="primary" @click.prevent="fnModalVisibleByEdit()">
|
||||
<template #icon><UploadOutlined /></template>
|
||||
{{ t('common.uploadText') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.searchBarText') }}</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
:checked-children="t('common.switch.show')"
|
||||
:un-checked-children="t('common.switch.hide')"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.sizeText') }}</template>
|
||||
<a-dropdown trigger="click" placement="bottomRight">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">
|
||||
{{ t('common.size.default') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="middle">
|
||||
{{ t('common.size.middle') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="small">
|
||||
{{ t('common.size.small') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: 1000 }"
|
||||
>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 上传框 -->
|
||||
<ProModal
|
||||
:drag="true"
|
||||
:width="800"
|
||||
:destroyOnClose="true"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:visible="modalState.visibleByEdit"
|
||||
:title="modalState.title"
|
||||
:confirm-loading="modalState.confirmLoading"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form name="modalStateFrom" layout="horizontal">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.license.neType')"
|
||||
name="neType"
|
||||
v-bind="modalStateFrom.validateInfos.neType"
|
||||
>
|
||||
<a-cascader
|
||||
v-model:value="modalState.from.neType"
|
||||
:options="useNeInfoStore().getNeCascaderOptions"
|
||||
:allow-clear="false"
|
||||
:placeholder="t('views.configManage.license.neTypePlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.configManage.license.updateComment')"
|
||||
name="comment"
|
||||
v-bind="modalStateFrom.validateInfos.comment"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.comment"
|
||||
:maxlength="200"
|
||||
:show-count="true"
|
||||
:placeholder="t('views.configManage.license.updateCommentPlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('views.configManage.license.updateFile')"
|
||||
name="file"
|
||||
v-bind="modalStateFrom.validateInfos.file"
|
||||
>
|
||||
<a-upload
|
||||
name="file"
|
||||
v-model:file-list="modalState.from.fileList"
|
||||
accept=".ini"
|
||||
list-type="text"
|
||||
:max-count="1"
|
||||
:show-upload-list="true"
|
||||
:before-upload="fnBeforeUploadFile"
|
||||
:custom-request="fnUploadFile"
|
||||
>
|
||||
<a-button type="default" :loading="modalState.confirmLoading">
|
||||
{{ t('views.configManage.license.selectFile') }}
|
||||
</a-button>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ProModal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
438
src/views/configManage/neOverview/index.vue
Normal file
438
src/views/configManage/neOverview/index.vue
Normal file
@@ -0,0 +1,438 @@
|
||||
<script setup lang="ts">
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import { message } from 'ant-design-vue/lib';
|
||||
import { reactive, ref, onMounted, onBeforeUnmount, markRaw } from 'vue';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { TooltipComponent } from 'echarts/components';
|
||||
import { GaugeChart } from 'echarts/charts';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import * as echarts from 'echarts/core';
|
||||
import { TitleComponent, LegendComponent } from 'echarts/components';
|
||||
import { PieChart } from 'echarts/charts';
|
||||
import { LabelLayout } from 'echarts/features';
|
||||
import { useRoute } from 'vue-router';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import { listAllNeInfo } from '@/api/ne/neInfo';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
const { getDict } = useDictStore();
|
||||
const appStore = useAppStore();
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
echarts.use([
|
||||
TooltipComponent,
|
||||
GaugeChart,
|
||||
TitleComponent,
|
||||
LegendComponent,
|
||||
PieChart,
|
||||
CanvasRenderer,
|
||||
LabelLayout,
|
||||
]);
|
||||
|
||||
/**图DOM节点实例对象 */
|
||||
const statusBar = ref<HTMLElement | undefined>(undefined);
|
||||
|
||||
/**图实例对象 */
|
||||
const statusBarChart = ref<any>(null);
|
||||
|
||||
/**网元状态字典数据 */
|
||||
let indexColor = ref<DictType[]>([
|
||||
{ label: 'Normal', value: 'normal', tagType: '', tagClass: '#91cc75' },
|
||||
{
|
||||
label: 'Abnormal',
|
||||
value: 'abnormal',
|
||||
tagType: '',
|
||||
tagClass: '#ee6666',
|
||||
},
|
||||
]);
|
||||
|
||||
/**表格字段列 */
|
||||
//customRender(){} ----单元格处理
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('views.index.object'),
|
||||
dataIndex: 'neName',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: t('views.index.realNeStatus'),
|
||||
dataIndex: 'serverState',
|
||||
align: 'left',
|
||||
key: 'status',
|
||||
},
|
||||
{
|
||||
title: t('views.index.reloadTime'),
|
||||
dataIndex: 'serverState',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
if (opt.value?.refreshTime) return parseDateToStr(opt.value?.refreshTime);
|
||||
return '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.index.version'),
|
||||
dataIndex: 'serverState',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
return opt.value?.version || '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.index.serialNum'),
|
||||
dataIndex: 'serverState',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
return opt.value?.sn || '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.index.expiryDate'),
|
||||
dataIndex: 'serverState',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
return opt.value?.expire || '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.index.ipAddress'),
|
||||
dataIndex: 'serverState',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
return opt.value?.neIP || '-';
|
||||
},
|
||||
},
|
||||
];
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: string;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
seached: false,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格状态 */
|
||||
let nfInfo: any = reactive({
|
||||
obj: 'OMC',
|
||||
version: appStore.version,
|
||||
status: t('views.index.normal'),
|
||||
outTimeDate: '',
|
||||
serialNum: appStore.serialNum,
|
||||
});
|
||||
|
||||
/**表格状态类型 */
|
||||
type nfStateType = {
|
||||
/**主机名 */
|
||||
hostName: string;
|
||||
/**操作系统信息 */
|
||||
osInfo: string;
|
||||
/**IP地址 */
|
||||
ipAddress: string;
|
||||
/**版本 */
|
||||
version: string;
|
||||
/**CPU利用率 */
|
||||
cpuUse: string;
|
||||
/**内存使用 */
|
||||
memoryUse: string;
|
||||
/**用户容量 */
|
||||
capability: number;
|
||||
/**序列号 */
|
||||
serialNum: string;
|
||||
/**许可证到期日期 */
|
||||
/* selectedRowKeys: (string | number)[];*/
|
||||
expiryDate: string;
|
||||
};
|
||||
/**网元详细信息 */
|
||||
let pronInfo: nfStateType = reactive({
|
||||
hostName: '5gc',
|
||||
osInfo: 'Linux 5gc 4.15.0-112-generic 2020 x86_64 GNU/Linux',
|
||||
ipAddress: '-',
|
||||
version: '-',
|
||||
cpuUse: '-',
|
||||
memoryUse: '-',
|
||||
capability: 0,
|
||||
serialNum: '-',
|
||||
expiryDate: '-',
|
||||
});
|
||||
|
||||
/**查询网元状态列表 */
|
||||
function fnGetList(one: boolean) {
|
||||
if (tableState.loading) return;
|
||||
one && (tableState.loading = true);
|
||||
listAllNeInfo({ bandStatus: true }).then(res => {
|
||||
tableState.data = res.data;
|
||||
tableState.loading = false;
|
||||
var rightNum = 0;
|
||||
var errorNum = 0;
|
||||
// if (res.length) nfInfo.serialNum = res[0].serialNum;
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
if (res[i].status == '正常' || res[i].status == 'Normal') {
|
||||
rightNum++;
|
||||
} else {
|
||||
errorNum++;
|
||||
}
|
||||
}
|
||||
|
||||
const optionData: any = {
|
||||
title: {
|
||||
text: '',
|
||||
subtext: '',
|
||||
left: 'center',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
},
|
||||
color: indexColor.value.map(item => item.tagClass),
|
||||
series: [
|
||||
{
|
||||
name: t('views.index.realNeStatus'),
|
||||
type: 'pie',
|
||||
radius: '70%',
|
||||
center: ['50%', '50%'],
|
||||
data: [
|
||||
{ value: rightNum, name: t('views.index.normal') },
|
||||
{ value: errorNum, name: t('views.index.abnormal') },
|
||||
],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
},
|
||||
|
||||
label: {},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
fnDesign(statusBar.value, optionData);
|
||||
});
|
||||
}
|
||||
|
||||
function fnDesign(container: HTMLElement | undefined, option: any) {
|
||||
if (!container) return;
|
||||
|
||||
if (!statusBarChart.value) {
|
||||
statusBarChart.value = markRaw(echarts.init(container, 'light'));
|
||||
}
|
||||
option && statusBarChart.value.setOption(option);
|
||||
|
||||
// 创建 ResizeObserver 实例
|
||||
var observer = new ResizeObserver(entries => {
|
||||
if (statusBarChart.value) {
|
||||
statusBarChart.value.resize();
|
||||
}
|
||||
});
|
||||
// 监听元素大小变化
|
||||
observer.observe(container);
|
||||
}
|
||||
|
||||
/**抽屉 网元详细信息 */
|
||||
const visible = ref(false);
|
||||
const closeDrawer = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
/**抽屉 网元详细信息 */
|
||||
|
||||
/**监听表格行事件*/
|
||||
function rowClick(record: any, index: any) {
|
||||
return {
|
||||
onClick: (event: any) => {
|
||||
let pronData = JSON.parse(JSON.stringify(record.serverState));
|
||||
if (!pronData.online) {
|
||||
message.error(t('views.index.neStatus'), 2);
|
||||
return false;
|
||||
} else {
|
||||
const totalMemInKB = pronData.mem?.totalMem;
|
||||
const nfUsedMemInKB = pronData.mem?.nfUsedMem;
|
||||
const sysMemUsageInKB = pronData.mem?.sysMemUsage;
|
||||
|
||||
// 将KB转换为MB
|
||||
const totalMemInMB = Math.round((totalMemInKB / 1024) * 100) / 100;
|
||||
const nfUsedMemInMB = Math.round((nfUsedMemInKB / 1024) * 100) / 100;
|
||||
const sysMemUsageInMB =
|
||||
Math.round((sysMemUsageInKB / 1024) * 100) / 100;
|
||||
|
||||
//渲染详细信息
|
||||
pronInfo = {
|
||||
hostName: pronData.hostname,
|
||||
osInfo: pronData.os,
|
||||
ipAddress: pronData.neIP,
|
||||
version: pronData.version,
|
||||
cpuUse:
|
||||
pronData.neName +
|
||||
':' +
|
||||
pronData.cpu?.nfCpuUsage / 100 +
|
||||
'%; ' +
|
||||
'SYS:' +
|
||||
pronData.cpu?.sysCpuUsage / 100 +
|
||||
'%',
|
||||
memoryUse:
|
||||
'Total:' +
|
||||
totalMemInMB +
|
||||
'MB; ' +
|
||||
pronData.name +
|
||||
':' +
|
||||
nfUsedMemInMB +
|
||||
'MB; SYS:' +
|
||||
sysMemUsageInMB +
|
||||
'MB',
|
||||
capability: pronData.capability,
|
||||
serialNum: pronData.sn,
|
||||
expiryDate: pronData.expire,
|
||||
};
|
||||
}
|
||||
visible.value = true;
|
||||
},
|
||||
};
|
||||
}
|
||||
let timer: any;
|
||||
|
||||
/**
|
||||
* 国际化翻译转换
|
||||
*/
|
||||
function fnLocale() {
|
||||
let title = route.meta.title as string;
|
||||
if (title.indexOf('router.') !== -1) {
|
||||
title = t(title);
|
||||
}
|
||||
appStore.setTitle(title);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDict('index_status')
|
||||
.then(res => {
|
||||
if (res.length > 0) {
|
||||
indexColor.value = res;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
fnLocale();
|
||||
fnGetList(true);
|
||||
timer = setInterval(() => fnGetList(false), 10000); // 每隔10秒执行一次
|
||||
});
|
||||
});
|
||||
|
||||
// 在组件卸载之前清除定时器
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(timer);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer :breadcrumb="{}">
|
||||
<div>
|
||||
<a-drawer :visible="visible" @close="closeDrawer" :width="700">
|
||||
<a-descriptions bordered :column="1" :label-style="{ width: '160px' }">
|
||||
<a-descriptions-item :label="t('views.index.hostName')">{{
|
||||
pronInfo.hostName
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.osInfo')">{{
|
||||
pronInfo.osInfo
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.ipAddress')">{{
|
||||
pronInfo.ipAddress
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.version')">{{
|
||||
pronInfo.version
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.cpuUse')">{{
|
||||
pronInfo.cpuUse
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.memoryUse')">{{
|
||||
pronInfo.memoryUse
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.serialNum')">{{
|
||||
pronInfo.serialNum
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.expiryDate')">{{
|
||||
pronInfo.expiryDate
|
||||
}}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-drawer>
|
||||
</div>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="14" :md="16" :xs="24">
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:pagination="false"
|
||||
:scroll="{ x: true }"
|
||||
:customRow="rowClick"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<div v-if="record.serverState.online">
|
||||
<a-tag color="blue">{{ t('views.index.normal') }}</a-tag>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-tag color="pink">{{ t('views.index.abnormal') }}</a-tag>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-col>
|
||||
<a-col :lg="10" :md="8" :xs="24">
|
||||
<a-card :title="t('views.index.runStatus')" style="margin-bottom: 16px">
|
||||
<div style="width: 100%; min-height: 200px" ref="statusBar"></div>
|
||||
</a-card>
|
||||
<a-card :title="t('views.index.mark')" style="margin-top: 16px">
|
||||
<a-descriptions
|
||||
bordered
|
||||
:column="1"
|
||||
:label-style="{ width: '160px' }"
|
||||
>
|
||||
<a-descriptions-item :label="t('views.index.object')">{{
|
||||
nfInfo.obj
|
||||
}}</a-descriptions-item>
|
||||
<template v-if="nfInfo.obj === 'OMC'">
|
||||
<a-descriptions-item :label="t('views.index.versionNum')">{{
|
||||
nfInfo.version
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.systemStatus')">{{
|
||||
nfInfo.status
|
||||
}}</a-descriptions-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-descriptions-item :label="t('views.index.serialNum')">{{
|
||||
nfInfo.serialNum
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.expiryDate')">{{
|
||||
nfInfo.outTimeDate
|
||||
}}</a-descriptions-item>
|
||||
</template>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -425,8 +425,6 @@ function fnGetList(pageNum?: number) {
|
||||
(queryParams.pageNum - 1) * tablePagination.pageSize &&
|
||||
queryParams.pageNum !== 1
|
||||
) {
|
||||
debugger;
|
||||
|
||||
tableState.loading = false;
|
||||
fnGetList(queryParams.pageNum - 1);
|
||||
}
|
||||
|
||||
@@ -161,6 +161,13 @@ let tableColumns: ColumnsType = [
|
||||
return cdrJSON.calledParty;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.result'),
|
||||
dataIndex: 'cdrJSON',
|
||||
key: 'cause',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.duration'),
|
||||
dataIndex: 'cdrJSON',
|
||||
@@ -175,20 +182,29 @@ let tableColumns: ColumnsType = [
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.result'),
|
||||
title: t('views.dashboard.cdr.seizureTime'),
|
||||
dataIndex: 'cdrJSON',
|
||||
key: 'cause',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.time'),
|
||||
dataIndex: 'cdrJSON',
|
||||
align: 'center',
|
||||
width: 150,
|
||||
width: 200,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
return parseDateToStr(+cdrJSON.releaseTime * 1000);
|
||||
if (typeof cdrJSON.seizureTime === 'number') {
|
||||
return parseDateToStr(+cdrJSON.seizureTime * 1000);
|
||||
}
|
||||
return cdrJSON.seizureTime;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.releaseTime'),
|
||||
dataIndex: 'cdrJSON',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
if (typeof cdrJSON.releaseTime === 'number') {
|
||||
return parseDateToStr(+cdrJSON.releaseTime * 1000);
|
||||
}
|
||||
return cdrJSON.releaseTime;
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -725,61 +741,73 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</template>
|
||||
<template #expandedRowRender="{ record }">
|
||||
<div style="width: 46%; padding-left: 32px; padding-bottom: 16px">
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.dashboard.cdr.cdrInfo') }}
|
||||
</a-divider>
|
||||
<div>
|
||||
<span>{{ t('views.ne.common.neName') }}: </span>
|
||||
<span>{{ record.neName }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.common.rmUid') }}: </span>
|
||||
<span>{{ record.rmUID }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.time') }}: </span>
|
||||
<span>{{ parseDateToStr(+record.timestamp * 1000) }}</span>
|
||||
</div>
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.dashboard.cdr.rowInfo') }}
|
||||
</a-divider>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.type') }}: </span>
|
||||
<DictTag
|
||||
:options="dict.cdrCallType"
|
||||
:value="record.cdrJSON.callType"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.duration') }}: </span>
|
||||
<span v-if="record.cdrJSON.callType !== 'sms'">
|
||||
{{ parseDuration(record.cdrJSON.callDuration) }}
|
||||
</span>
|
||||
<span v-else> - </span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.caller') }}: </span>
|
||||
<span>{{ record.cdrJSON.callerParty }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.called') }}: </span>
|
||||
<span>{{ record.cdrJSON.calledParty }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.result') }}: </span>
|
||||
<span v-if="record.cdrJSON.callType !== 'sms'">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="5" :md="12" :xs="24">
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.dashboard.cdr.cdrInfo') }}
|
||||
</a-divider>
|
||||
<div>
|
||||
<span>{{ t('views.ne.common.neName') }}: </span>
|
||||
<span>{{ record.neName }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.common.rmUid') }}: </span>
|
||||
<span>{{ record.rmUID }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.time') }}: </span>
|
||||
<span>{{ parseDateToStr(+record.timestamp * 1000) }}</span>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.dashboard.cdr.rowInfo') }}
|
||||
</a-divider>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.type') }}: </span>
|
||||
<DictTag
|
||||
:options="dict.cdrSipCode"
|
||||
:value="record.cdrJSON.cause"
|
||||
value-default="0"
|
||||
:options="dict.cdrCallType"
|
||||
:value="record.cdrJSON.callType"
|
||||
/>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ t('views.dashboard.cdr.resultOk') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.duration') }}: </span>
|
||||
<span v-if="record.cdrJSON.callType !== 'sms'">
|
||||
{{ parseDuration(record.cdrJSON.callDuration) }}
|
||||
</span>
|
||||
<span v-else> - </span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.caller') }}: </span>
|
||||
<span>{{ record.cdrJSON.callerParty }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.called') }}: </span>
|
||||
<span>{{ record.cdrJSON.calledParty }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.result') }}: </span>
|
||||
<span v-if="record.cdrJSON.callType !== 'sms'">
|
||||
<DictTag
|
||||
:options="dict.cdrSipCode"
|
||||
:value="record.cdrJSON.cause"
|
||||
value-default="0"
|
||||
/>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ t('views.dashboard.cdr.resultOk') }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.seizureTime') }}: </span>
|
||||
<span>{{ record.cdrJSON.seizureTime }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.releaseTime') }}: </span>
|
||||
<span>{{ record.cdrJSON.releaseTime }}</span>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
@@ -25,6 +25,9 @@ const queue = new PQueue({ concurrency: 1, autoStart: true });
|
||||
/**网元可选 */
|
||||
let neOtions = ref<Record<string, any>[]>([]);
|
||||
|
||||
/**event Type */
|
||||
let mmeEventType = ref<DictType[]>([]);
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**UE 事件认证代码类型 */
|
||||
@@ -415,11 +418,13 @@ onMounted(() => {
|
||||
dict.ueAauthCode = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.ueEventType = resArr[1].value.map(item => {
|
||||
if (item.value === 'cm-state') {
|
||||
item.label = item.label.replace('CM', 'ECM');
|
||||
resArr[1].value.map(item => {
|
||||
const realJson = JSON.parse(JSON.stringify(item));
|
||||
|
||||
if (realJson.value === 'cm-state') {
|
||||
realJson.label = realJson.label.replace('CM', 'ECM');
|
||||
}
|
||||
return item;
|
||||
mmeEventType.value.push(realJson);
|
||||
});
|
||||
}
|
||||
if (resArr[2].status === 'fulfilled') {
|
||||
@@ -491,7 +496,7 @@ onBeforeUnmount(() => {
|
||||
<a-select
|
||||
v-model:value="eventTypes"
|
||||
mode="multiple"
|
||||
:options="dict.ueEventType"
|
||||
:options="mmeEventType"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
@change="fnQueryEventTypeChange"
|
||||
></a-select>
|
||||
@@ -648,7 +653,7 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'eventType'">
|
||||
<DictTag :options="dict.ueEventType" :value="record.eventType" />
|
||||
<DictTag :options="mmeEventType" :value="record.eventType" />
|
||||
</template>
|
||||
<template v-if="column.key === 'result'">
|
||||
<span v-if="record.eventType === 'auth-result'">
|
||||
@@ -705,7 +710,7 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.ue.eventType') }}: </span>
|
||||
<DictTag :options="dict.ueEventType" :value="record.eventType" />
|
||||
<DictTag :options="mmeEventType" :value="record.eventType" />
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.ue.result') }}: </span>
|
||||
|
||||
@@ -157,22 +157,26 @@ async function fnGetSkim() {
|
||||
/**初始数据函数 */
|
||||
function loadData() {
|
||||
fnGetNeState(); // 获取网元状态
|
||||
userActivitySend();
|
||||
upfTFSend(0);
|
||||
upfTFSend(7);
|
||||
upfTFSend(30);
|
||||
userActivitySend();
|
||||
upfTFSend('0');
|
||||
upfTFSend('7');
|
||||
upfTFSend('30');
|
||||
|
||||
clearInterval(interval10s.value);
|
||||
interval10s.value = setInterval(() => {
|
||||
upfTFActive.value = upfTFActive.value >= 2 ? 0 : upfTFActive.value + 1;
|
||||
if (upfTFActive.value === 0) {
|
||||
upfTFSend(7);
|
||||
} else if (upfTFActive.value === 1) {
|
||||
upfTFSend(30);
|
||||
} else if (upfTFActive.value === 2) {
|
||||
upfTFSend(0);
|
||||
if (upfTFActive.value === '0') {
|
||||
upfTFSend('7');
|
||||
upfTFActive.value = '7';
|
||||
} else if (upfTFActive.value === '7') {
|
||||
upfTFSend('30');
|
||||
upfTFActive.value = '30';
|
||||
} else if (upfTFActive.value === '30') {
|
||||
upfTFSend('0');
|
||||
upfTFActive.value = '0';
|
||||
}
|
||||
}, 10_000);
|
||||
|
||||
clearInterval(interval5s.value);
|
||||
interval5s.value = setInterval(() => {
|
||||
fnGetSkim(); // 获取概览信息
|
||||
fnGetNeState(); // 获取网元状态
|
||||
@@ -425,12 +429,12 @@ function fnRightClick() {
|
||||
<div class="filter">
|
||||
<span
|
||||
:data-key="v"
|
||||
:class="{ active: upfTFActive === i }"
|
||||
v-for="(v, i) in ['0', '7', '30']"
|
||||
:class="{ active: upfTFActive === v }"
|
||||
v-for="v in ['0', '7', '30']"
|
||||
:key="v"
|
||||
@click="
|
||||
() => {
|
||||
upfTFActive = i;
|
||||
upfTFActive = v;
|
||||
}
|
||||
"
|
||||
>
|
||||
@@ -450,14 +454,14 @@ function fnRightClick() {
|
||||
<ArrowUpOutlined style="color: #597ef7" />
|
||||
{{ t('views.dashboard.overview.upfFlowTotal.up') }}
|
||||
</span>
|
||||
<h4>{{ upfTotalFlow[upfTFActive].up }}</h4>
|
||||
<h4>{{ upfTotalFlow[upfTFActive].upFrom }}</h4>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span>
|
||||
<ArrowDownOutlined style="color: #52c41a" />
|
||||
{{ t('views.dashboard.overview.upfFlowTotal.down') }}
|
||||
</span>
|
||||
<h4>{{ upfTotalFlow[upfTFActive].down }}</h4>
|
||||
<h4>{{ upfTotalFlow[upfTFActive].downFrom }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -36,47 +36,63 @@ export function upfFlowParse(data: Record<string, string>) {
|
||||
upfFlowData.value.lineYDown.shift();
|
||||
upfFlowData.value.cap -= 1;
|
||||
}
|
||||
// UPF-总流量数0天 当天24小时
|
||||
upfTFParse('0', {
|
||||
up: upfTotalFlow.value['0'].up + +data['UPF.03'],
|
||||
down: upfTotalFlow.value['0'].down + +data['UPF.06'],
|
||||
});
|
||||
}
|
||||
|
||||
type TFType = {
|
||||
/**上行 N3 */
|
||||
up: string;
|
||||
up: number;
|
||||
upFrom: string;
|
||||
/**下行 N6 */
|
||||
down: string;
|
||||
down: number;
|
||||
downFrom: string;
|
||||
/**请求标记 */
|
||||
requestFlag: boolean;
|
||||
};
|
||||
|
||||
/**UPF-总流量数 */
|
||||
export const upfTotalFlow = ref<TFType[]>([
|
||||
// 0天 当天24小时
|
||||
{
|
||||
up: '0 B',
|
||||
down: '0 B',
|
||||
export const upfTotalFlow = ref<Record<string, TFType>>({
|
||||
'0': {
|
||||
up: 0,
|
||||
upFrom: '0 B',
|
||||
down: 0,
|
||||
downFrom: '0 B',
|
||||
requestFlag: false,
|
||||
},
|
||||
{
|
||||
up: '0 B',
|
||||
down: '0 B',
|
||||
'7': {
|
||||
up: 0,
|
||||
upFrom: '0 B',
|
||||
down: 0,
|
||||
downFrom: '0 B',
|
||||
requestFlag: false,
|
||||
},
|
||||
{
|
||||
up: '0 B',
|
||||
down: '0 B',
|
||||
'30': {
|
||||
up: 0,
|
||||
upFrom: '0 B',
|
||||
down: 0,
|
||||
downFrom: '0 B',
|
||||
requestFlag: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
/**UPF-总流量数 数据解析 */
|
||||
export function upfTFParse(data: Record<string, string>) {
|
||||
export function upfTFParse(day: string, data: Record<string, number>) {
|
||||
let { up, down } = data;
|
||||
up = parseSizeFromBits(up);
|
||||
down = parseSizeFromBits(down);
|
||||
return { up, down };
|
||||
upfTotalFlow.value[day] = {
|
||||
up: up,
|
||||
upFrom: parseSizeFromBits(up),
|
||||
down: down,
|
||||
downFrom: parseSizeFromBits(down),
|
||||
requestFlag: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**UPF-总流量数 选中 */
|
||||
export const upfTFActive = ref<number>(0);
|
||||
export const upfTFActive = ref<string>('0');
|
||||
|
||||
/**属性复位 */
|
||||
export function upfTotalFlowReset() {
|
||||
@@ -86,23 +102,14 @@ export function upfTotalFlowReset() {
|
||||
lineYDown: [],
|
||||
cap: 0,
|
||||
};
|
||||
upfTotalFlow.value = [
|
||||
// 0天 当天24小时
|
||||
{
|
||||
up: '0 B',
|
||||
down: '0 B',
|
||||
for (const key of Object.keys(upfTotalFlow.value)) {
|
||||
upfTotalFlow.value[key] = {
|
||||
up: 0,
|
||||
upFrom: '0 B',
|
||||
down: 0,
|
||||
downFrom: '0 B',
|
||||
requestFlag: false,
|
||||
},
|
||||
{
|
||||
up: '0 B',
|
||||
down: '0 B',
|
||||
requestFlag: false,
|
||||
},
|
||||
{
|
||||
up: '0 B',
|
||||
down: '0 B',
|
||||
requestFlag: false,
|
||||
},
|
||||
];
|
||||
upfTFActive.value = 0;
|
||||
};
|
||||
}
|
||||
upfTFActive.value = '0';
|
||||
}
|
||||
|
||||
@@ -25,12 +25,6 @@ export default function useWS() {
|
||||
ws.send(data);
|
||||
}
|
||||
|
||||
/**接收数据后回调 */
|
||||
function wsError(ev: any) {
|
||||
// 接收数据后回调
|
||||
console.error(ev);
|
||||
}
|
||||
|
||||
/**接收数据后回调 */
|
||||
function wsMessage(res: Record<string, any>) {
|
||||
// console.log(res);
|
||||
@@ -50,7 +44,7 @@ export default function useWS() {
|
||||
// 普通信息
|
||||
switch (requestId) {
|
||||
// AMF_UE会话事件
|
||||
case 'amf_1010_001':
|
||||
case 'amf_1010':
|
||||
if (Array.isArray(data.rows)) {
|
||||
eventListParse('amf_ue', data);
|
||||
}
|
||||
@@ -69,22 +63,13 @@ export default function useWS() {
|
||||
break;
|
||||
//UPF-总流量数
|
||||
case 'upf_001_0':
|
||||
const v0 = upfTFParse(data);
|
||||
upfTotalFlow.value[0].up = v0.up;
|
||||
upfTotalFlow.value[0].down = v0.down;
|
||||
upfTotalFlow.value[0].requestFlag = false;
|
||||
upfTFParse('0', data);
|
||||
break;
|
||||
case 'upf_001_7':
|
||||
const v7 = upfTFParse(data);
|
||||
upfTotalFlow.value[1].up = v7.up;
|
||||
upfTotalFlow.value[1].down = v7.down;
|
||||
upfTotalFlow.value[1].requestFlag = false;
|
||||
upfTFParse('7', data);
|
||||
break;
|
||||
case 'upf_001_30':
|
||||
const v30 = upfTFParse(data);
|
||||
upfTotalFlow.value[2].up = v30.up;
|
||||
upfTotalFlow.value[2].down = v30.down;
|
||||
upfTotalFlow.value[2].requestFlag = false;
|
||||
upfTFParse('30', data);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -100,7 +85,7 @@ export default function useWS() {
|
||||
}
|
||||
break;
|
||||
// AMF_UE会话事件
|
||||
case '1010_001':
|
||||
case '1010':
|
||||
if (data.data) {
|
||||
queue.add(() => eventItemParseAndPush('amf_ue', data.data));
|
||||
}
|
||||
@@ -121,20 +106,12 @@ export default function useWS() {
|
||||
}
|
||||
|
||||
/**UPF-总流量数 发消息*/
|
||||
function upfTFSend(day: 0 | 7 | 30) {
|
||||
function upfTFSend(day: '0' | '7' | '30') {
|
||||
// 请求标记检查避免重复发送
|
||||
let index = 0;
|
||||
if (day === 0) {
|
||||
index = 0;
|
||||
} else if (day === 7) {
|
||||
index = 1;
|
||||
} else if (day === 30) {
|
||||
index = 2;
|
||||
}
|
||||
if (upfTotalFlow.value[index].requestFlag) {
|
||||
if (upfTotalFlow.value[day].requestFlag) {
|
||||
return;
|
||||
}
|
||||
upfTotalFlow.value[index].requestFlag = true;
|
||||
upfTotalFlow.value[day].requestFlag = true;
|
||||
|
||||
ws.send({
|
||||
requestId: `upf_001_${day}`,
|
||||
@@ -142,7 +119,7 @@ export default function useWS() {
|
||||
data: {
|
||||
neType: 'UPF',
|
||||
neId: '001',
|
||||
day: day,
|
||||
day: Number(day),
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -151,7 +128,7 @@ export default function useWS() {
|
||||
function userActivitySend() {
|
||||
// AMF_UE会话事件
|
||||
ws.send({
|
||||
requestId: 'amf_1010_001',
|
||||
requestId: 'amf_1010',
|
||||
type: 'amf_ue',
|
||||
data: {
|
||||
neType: 'AMF',
|
||||
@@ -198,14 +175,16 @@ export default function useWS() {
|
||||
/**订阅通道组
|
||||
*
|
||||
* 指标UPF (GroupID:12_neId)
|
||||
* AMF_UE会话事件(GroupID:1010_neId)
|
||||
* AMF_UE会话事件(GroupID:1010)
|
||||
* MME_UE会话事件(GroupID:1011_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,
|
||||
onerror: wsError,
|
||||
onerror: (ev: any) => {
|
||||
console.error(ev);
|
||||
},
|
||||
};
|
||||
ws.connect(options);
|
||||
});
|
||||
|
||||
@@ -156,19 +156,21 @@ async function fnGetSkim() {
|
||||
function loadData() {
|
||||
fnGetNeState(); // 获取网元状态
|
||||
userActivitySend();
|
||||
upfTFSend(0);
|
||||
upfTFSend(7);
|
||||
upfTFSend(30);
|
||||
upfTFSend('0');
|
||||
upfTFSend('7');
|
||||
upfTFSend('30');
|
||||
|
||||
clearInterval(interval10s.value);
|
||||
interval10s.value = setInterval(() => {
|
||||
upfTFActive.value = upfTFActive.value >= 2 ? 0 : upfTFActive.value + 1;
|
||||
if (upfTFActive.value === 0) {
|
||||
upfTFSend(7);
|
||||
} else if (upfTFActive.value === 1) {
|
||||
upfTFSend(30);
|
||||
} else if (upfTFActive.value === 2) {
|
||||
upfTFSend(0);
|
||||
if (upfTFActive.value === '0') {
|
||||
upfTFSend('7');
|
||||
upfTFActive.value = '7';
|
||||
} else if (upfTFActive.value === '7') {
|
||||
upfTFSend('30');
|
||||
upfTFActive.value = '30';
|
||||
} else if (upfTFActive.value === '30') {
|
||||
upfTFSend('0');
|
||||
upfTFActive.value = '0';
|
||||
}
|
||||
}, 10_000);
|
||||
|
||||
@@ -411,12 +413,12 @@ onBeforeUnmount(() => {
|
||||
<div class="filter">
|
||||
<span
|
||||
:data-key="v"
|
||||
:class="{ active: upfTFActive === i }"
|
||||
v-for="(v, i) in ['0', '7', '30']"
|
||||
:class="{ active: upfTFActive === v }"
|
||||
v-for="v in ['0', '7', '30']"
|
||||
:key="v"
|
||||
@click="
|
||||
() => {
|
||||
upfTFActive = i;
|
||||
upfTFActive = v;
|
||||
}
|
||||
"
|
||||
>
|
||||
@@ -436,14 +438,14 @@ onBeforeUnmount(() => {
|
||||
<ArrowUpOutlined style="color: #597ef7" />
|
||||
{{ t('views.dashboard.overview.upfFlowTotal.up') }}
|
||||
</span>
|
||||
<h4>{{ upfTotalFlow[upfTFActive].up }}</h4>
|
||||
<h4>{{ upfTotalFlow[upfTFActive].upFrom }}</h4>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span>
|
||||
<ArrowDownOutlined style="color: #52c41a" />
|
||||
{{ t('views.dashboard.overview.upfFlowTotal.down') }}
|
||||
</span>
|
||||
<h4>{{ upfTotalFlow[upfTFActive].down }}</h4>
|
||||
<h4>{{ upfTotalFlow[upfTFActive].downFrom }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -813,6 +813,13 @@ onBeforeUnmount(() => {
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Network Function IPv4: </span>
|
||||
<span>{{
|
||||
record.cdrJSON.nFunctionConsumerInformation
|
||||
.networkFunctionIPv4Address
|
||||
}}</span>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
@@ -612,13 +612,16 @@ function fnShowSet() {
|
||||
}
|
||||
|
||||
// key替换中文title
|
||||
function mapKeysWithReduce(data: any, titleMapping: any) {
|
||||
return data.map((item:any) => {
|
||||
return Object.keys(item).reduce((newItem:any, key:any) => {
|
||||
function mapKeysWithReduce(data: any[], titleMapping: Record<string, string>) {
|
||||
return data.map((item: any) => {
|
||||
if (typeof item !== 'object' || item === null) {
|
||||
return item; // 如果不是对象,直接返回原值
|
||||
}
|
||||
return Object.keys(item).reduce((newItem: Record<string, any>, key: string) => {
|
||||
const title = titleMapping[key] || key;
|
||||
newItem[title] = item[key];
|
||||
return newItem;
|
||||
});
|
||||
}, {}); // 确保初始值是一个空对象
|
||||
});
|
||||
}
|
||||
|
||||
@@ -673,6 +676,7 @@ function fnExportAll() {
|
||||
|
||||
return filteredObj;
|
||||
});
|
||||
console.log(mappArr);
|
||||
|
||||
res.data = mapKeysWithReduce(res.data, mappArr);
|
||||
writeSheet(res.data, 'alarm', sortData).then(fileBlob => {
|
||||
|
||||
@@ -427,13 +427,16 @@ function fnCancelConfirm() {
|
||||
}
|
||||
|
||||
// key替换中文title
|
||||
function mapKeysWithReduce(data: any, titleMapping: any) {
|
||||
return data.map((item:any) => {
|
||||
return Object.keys(item).reduce((newItem:any, key:any) => {
|
||||
function mapKeysWithReduce(data: any[], titleMapping: Record<string, string>) {
|
||||
return data.map((item: any) => {
|
||||
if (typeof item !== 'object' || item === null) {
|
||||
return item;
|
||||
}
|
||||
return Object.keys(item).reduce((newItem: Record<string, any>, key: string) => {
|
||||
const title = titleMapping[key] || key;
|
||||
newItem[title] = item[key];
|
||||
return newItem;
|
||||
});
|
||||
}, {});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,446 +1,77 @@
|
||||
<script setup lang="ts">
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import { message } from 'ant-design-vue/lib';
|
||||
import { reactive, toRaw, ref, onMounted, onBeforeUnmount, markRaw } from 'vue';
|
||||
import { listMain } from '@/api/index';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { TooltipComponent } from 'echarts/components';
|
||||
import { GaugeChart } from 'echarts/charts';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import * as echarts from 'echarts/core';
|
||||
import { TitleComponent, LegendComponent } from 'echarts/components';
|
||||
import { PieChart } from 'echarts/charts';
|
||||
import { LabelLayout } from 'echarts/features';
|
||||
import { useRoute } from 'vue-router';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
const { getDict } = useDictStore();
|
||||
const appStore = useAppStore();
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
import {
|
||||
defineAsyncComponent,
|
||||
onMounted,
|
||||
ref,
|
||||
shallowRef,
|
||||
type Component,
|
||||
} from 'vue';
|
||||
import { getConfigKey, changeValue } from '@/api/system/config';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
|
||||
echarts.use([
|
||||
TooltipComponent,
|
||||
GaugeChart,
|
||||
TitleComponent,
|
||||
LegendComponent,
|
||||
PieChart,
|
||||
CanvasRenderer,
|
||||
LabelLayout,
|
||||
]);
|
||||
const currentComponent = shallowRef<Component | null>(null);
|
||||
|
||||
/**图DOM节点实例对象 */
|
||||
const statusBar = ref<HTMLElement | undefined>(undefined);
|
||||
const spinning = ref<boolean>(true);
|
||||
|
||||
/**图实例对象 */
|
||||
const statusBarChart = ref<any>(null);
|
||||
|
||||
/**网元状态字典数据 */
|
||||
let indexColor = ref<DictType[]>([
|
||||
{ label: 'Normal', value: 'normal', tagType: '', tagClass: '#91cc75' },
|
||||
{
|
||||
label: 'Abnormal',
|
||||
value: 'abnormal',
|
||||
tagType: '',
|
||||
tagClass: '#ee6666',
|
||||
},
|
||||
]);
|
||||
|
||||
/**表格字段列 */
|
||||
//customRender(){} ----单元格处理
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('views.index.object'),
|
||||
dataIndex: 'name',
|
||||
align: 'center',
|
||||
key: 'status',
|
||||
},
|
||||
{
|
||||
title: t('views.index.realNeStatus'),
|
||||
dataIndex: 'status',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
if (opt.value == 'Normal') return t('views.index.normal');
|
||||
return t('views.index.abnormal');
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.index.reloadTime'),
|
||||
dataIndex: 'refresh',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.index.version'),
|
||||
dataIndex: 'version',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.index.serialNum'),
|
||||
dataIndex: 'serialNum',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.index.expiryDate'),
|
||||
dataIndex: 'expiryDate',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.index.ipAddress'),
|
||||
dataIndex: 'ipAddress',
|
||||
key: 'groupName',
|
||||
align: 'center',
|
||||
},
|
||||
];
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: string;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
seached: false,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格状态 */
|
||||
let nfInfo: any = reactive({
|
||||
obj: 'OMC',
|
||||
version: appStore.version,
|
||||
status: t('views.index.normal'),
|
||||
outTimeDate: '',
|
||||
serialNum: appStore.serialNum,
|
||||
});
|
||||
|
||||
/**表格状态类型 */
|
||||
type nfStateType = {
|
||||
/**主机名 */
|
||||
hostName: string;
|
||||
/**操作系统信息 */
|
||||
osInfo: string;
|
||||
/**数据库信息 */
|
||||
dbInfo: string;
|
||||
/**IP地址 */
|
||||
ipAddress: string;
|
||||
/**端口 */
|
||||
port: number;
|
||||
/**版本 */
|
||||
version: string;
|
||||
/**CPU利用率 */
|
||||
cpuUse: string;
|
||||
/**内存使用 */
|
||||
memoryUse: string;
|
||||
/**用户容量 */
|
||||
capability: number;
|
||||
/**序列号 */
|
||||
serialNum: string;
|
||||
/**许可证到期日期 */
|
||||
/* selectedRowKeys: (string | number)[];*/
|
||||
expiryDate: string;
|
||||
};
|
||||
/**网元详细信息 */
|
||||
let pronInfo: nfStateType = reactive({
|
||||
hostName: '5gc',
|
||||
osInfo: 'Linux 5gc 4.15.0-112-generic 2020 x86_64 GNU/Linux',
|
||||
dbInfo: 'db v9.9.9',
|
||||
ipAddress: '-',
|
||||
port: 33030,
|
||||
version: '-',
|
||||
cpuUse: '-',
|
||||
memoryUse: '-',
|
||||
capability: 0,
|
||||
serialNum: '-',
|
||||
expiryDate: '-',
|
||||
});
|
||||
|
||||
/**查询网元状态列表 */
|
||||
function fnGetList(one: boolean) {
|
||||
if (tableState.loading) return;
|
||||
one && (tableState.loading = true);
|
||||
listMain().then(res => {
|
||||
tableState.data = res;
|
||||
tableState.loading = false;
|
||||
var rightNum = 0;
|
||||
var errorNum = 0;
|
||||
// if (res.length) nfInfo.serialNum = res[0].serialNum;
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
if (res[i].status == '正常' || res[i].status == 'Normal') {
|
||||
rightNum++;
|
||||
} else {
|
||||
errorNum++;
|
||||
}
|
||||
}
|
||||
|
||||
const optionData: any = {
|
||||
title: {
|
||||
text: '',
|
||||
subtext: '',
|
||||
left: 'center',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
},
|
||||
color: indexColor.value.map(item => item.tagClass),
|
||||
series: [
|
||||
{
|
||||
name: t('views.index.realNeStatus'),
|
||||
type: 'pie',
|
||||
radius: '70%',
|
||||
center: ['50%', '50%'],
|
||||
data: [
|
||||
{ value: rightNum, name: t('views.index.normal') },
|
||||
{ value: errorNum, name: t('views.index.abnormal') },
|
||||
],
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
},
|
||||
|
||||
label: {},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
fnDesign(statusBar.value, optionData);
|
||||
});
|
||||
}
|
||||
|
||||
function fnDesign(container: HTMLElement | undefined, option: any) {
|
||||
if (!container) return;
|
||||
|
||||
if (!statusBarChart.value) {
|
||||
statusBarChart.value = markRaw(echarts.init(container, 'light'));
|
||||
}
|
||||
option && statusBarChart.value.setOption(option);
|
||||
|
||||
// 创建 ResizeObserver 实例
|
||||
var observer = new ResizeObserver(entries => {
|
||||
if (statusBarChart.value) {
|
||||
statusBarChart.value.resize();
|
||||
}
|
||||
});
|
||||
// 监听元素大小变化
|
||||
observer.observe(container);
|
||||
}
|
||||
|
||||
/**抽屉 网元详细信息 */
|
||||
const visible = ref(false);
|
||||
const closeDrawer = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
/**抽屉 网元详细信息 */
|
||||
|
||||
/**监听表格行事件*/
|
||||
function rowClick(record: any, index: any) {
|
||||
return {
|
||||
onClick: (event: any) => {
|
||||
if (
|
||||
toRaw(record).status == '异常' ||
|
||||
toRaw(record).status == 'Abnormal'
|
||||
) {
|
||||
message.error(t('views.index.neStatus'), 2);
|
||||
return false;
|
||||
} else {
|
||||
let pronData = toRaw(record);
|
||||
const totalMemInKB = pronData.memUsage?.totalMem;
|
||||
const nfUsedMemInKB = pronData.memUsage?.nfUsedMem;
|
||||
const sysMemUsageInKB = pronData.memUsage?.sysMemUsage;
|
||||
|
||||
// 将KB转换为MB
|
||||
const totalMemInMB = Math.round((totalMemInKB / 1024) * 100) / 100;
|
||||
const nfUsedMemInMB = Math.round((nfUsedMemInKB / 1024) * 100) / 100;
|
||||
const sysMemUsageInMB =
|
||||
Math.round((sysMemUsageInKB / 1024) * 100) / 100;
|
||||
|
||||
//渲染详细信息
|
||||
pronInfo = {
|
||||
hostName: pronData.hostName,
|
||||
osInfo: pronData.osInfo,
|
||||
dbInfo: pronData.dbInfo,
|
||||
ipAddress: pronData.ipAddress,
|
||||
port: pronData.port,
|
||||
version: pronData.version,
|
||||
cpuUse:
|
||||
pronData.name +
|
||||
':' +
|
||||
pronData.cpuUsage?.nfCpuUsage / 100 +
|
||||
'%; ' +
|
||||
'SYS:' +
|
||||
pronData.cpuUsage?.sysCpuUsage / 100 +
|
||||
'%',
|
||||
memoryUse:
|
||||
'Total:' +
|
||||
totalMemInMB +
|
||||
'MB; ' +
|
||||
pronData.name +
|
||||
':' +
|
||||
nfUsedMemInMB +
|
||||
'MB; SYS:' +
|
||||
sysMemUsageInMB +
|
||||
'MB',
|
||||
capability: pronData.capability,
|
||||
serialNum: pronData.serialNum,
|
||||
expiryDate: pronData.expiryDate,
|
||||
};
|
||||
}
|
||||
visible.value = true;
|
||||
},
|
||||
};
|
||||
}
|
||||
let timer: any;
|
||||
/**匹配views里面所有的.vue或.tsx文件 */
|
||||
const views = import.meta.glob('../views/**/*.{vue,tsx}') as Record<
|
||||
string,
|
||||
() => Promise<Component>
|
||||
>;
|
||||
|
||||
/**
|
||||
* 国际化翻译转换
|
||||
* 查找页面模块
|
||||
* @param dirName 组件路径
|
||||
* @returns 路由懒加载函数
|
||||
*/
|
||||
function fnLocale() {
|
||||
let title = route.meta.title as string;
|
||||
if (title.indexOf('router.') !== -1) {
|
||||
title = t(title);
|
||||
function findView(dirName: string): () => Promise<Component> {
|
||||
for (const dir in views) {
|
||||
let viewDirName = '';
|
||||
const component = dir.match(/\/(.+)\.(vue|tsx)/);
|
||||
if (component && component.length === 3) {
|
||||
viewDirName = component[1];
|
||||
}
|
||||
if (viewDirName === dirName) {
|
||||
return views[dir];
|
||||
}
|
||||
}
|
||||
appStore.setTitle(title);
|
||||
return () => import('@/views/configManage/neOverview/index.vue');
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDict('index_status')
|
||||
.then(res => {
|
||||
if (res.length > 0) {
|
||||
indexColor.value = res;
|
||||
//获取当前系统设置的首页路径
|
||||
getConfigKey('sys.homePage').then(res => {
|
||||
spinning.value = false;
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
console.log(spinning);
|
||||
if (res.data) {
|
||||
const asyncComponent = findView(`${res.data}`);
|
||||
currentComponent.value = defineAsyncComponent(asyncComponent);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
fnLocale();
|
||||
fnGetList(true);
|
||||
timer = setInterval(() => fnGetList(false), 10000); // 每隔10秒执行一次
|
||||
});
|
||||
});
|
||||
|
||||
// 在组件卸载之前清除定时器
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(timer);
|
||||
} else {
|
||||
currentComponent.value = defineAsyncComponent(
|
||||
() => import('@/views/configManage/neOverview/index.vue')
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer :breadcrumb="{}">
|
||||
<div>
|
||||
<a-drawer :visible="visible" @close="closeDrawer" :width="700">
|
||||
<a-descriptions bordered :column="1" :label-style="{ width: '160px' }">
|
||||
<a-descriptions-item :label="t('views.index.hostName')">{{
|
||||
pronInfo.hostName
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.osInfo')">{{
|
||||
pronInfo.osInfo
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.dbInfo')">{{
|
||||
pronInfo.dbInfo
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.ipAddress')">{{
|
||||
pronInfo.ipAddress
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.port')">{{
|
||||
pronInfo.port
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.version')">{{
|
||||
pronInfo.version
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.cpuUse')">{{
|
||||
pronInfo.cpuUse
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.memoryUse')">{{
|
||||
pronInfo.memoryUse
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.capability')">{{
|
||||
pronInfo.capability
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.serialNum')">{{
|
||||
pronInfo.serialNum
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.expiryDate')">{{
|
||||
pronInfo.expiryDate
|
||||
}}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-drawer>
|
||||
</div>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="14" :md="16" :xs="24">
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:pagination="false"
|
||||
:scroll="{ x: true }"
|
||||
:customRow="rowClick"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<div v-if="record.status == '正常' || record.status == 'Normal'">
|
||||
<a-tag color="blue">{{ record.name }}</a-tag>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-tag color="pink">{{ record.name }}</a-tag>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-col>
|
||||
<a-col :lg="10" :md="8" :xs="24">
|
||||
<a-card :title="t('views.index.runStatus')" style="margin-bottom: 16px">
|
||||
<div style="width: 100%; min-height: 200px" ref="statusBar"></div>
|
||||
</a-card>
|
||||
<a-card :title="t('views.index.mark')" style="margin-top: 16px">
|
||||
<a-descriptions
|
||||
bordered
|
||||
:column="1"
|
||||
:label-style="{ width: '160px' }"
|
||||
>
|
||||
<a-descriptions-item :label="t('views.index.object')">{{
|
||||
nfInfo.obj
|
||||
}}</a-descriptions-item>
|
||||
<template v-if="nfInfo.obj === 'OMC'">
|
||||
<a-descriptions-item :label="t('views.index.versionNum')">{{
|
||||
nfInfo.version
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.systemStatus')">{{
|
||||
nfInfo.status
|
||||
}}</a-descriptions-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-descriptions-item :label="t('views.index.serialNum')">{{
|
||||
nfInfo.serialNum
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.expiryDate')">{{
|
||||
nfInfo.outTimeDate
|
||||
}}</a-descriptions-item>
|
||||
</template>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</PageContainer>
|
||||
<a-spin
|
||||
size="large"
|
||||
:spinning="spinning"
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
min-height: 362px;
|
||||
"
|
||||
>
|
||||
<component :is="currentComponent" />
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<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 TerminalSSHView from '@/components/TerminalSSHView/index.vue';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
@@ -86,19 +86,20 @@ function fnInit() {
|
||||
function fnReload() {
|
||||
if (!viewTerminal.value) return;
|
||||
viewTerminal.value.ctrlC();
|
||||
|
||||
if (state.form.showType !== 'lines') {
|
||||
state.form.lines = 10;
|
||||
} else {
|
||||
state.form.char = 0;
|
||||
}
|
||||
viewTerminal.value.clear();
|
||||
viewTerminal.value.send('tail', {
|
||||
filePath: props.filePath,
|
||||
lines: state.form.lines,
|
||||
char: state.form.char,
|
||||
follow: state.form.follow,
|
||||
});
|
||||
setTimeout(() => {
|
||||
viewTerminal.value.send('tail', {
|
||||
filePath: props.filePath,
|
||||
lines: state.form.lines,
|
||||
char: state.form.char,
|
||||
follow: state.form.follow,
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -122,9 +123,11 @@ function fnReload() {
|
||||
<TerminalSSHView
|
||||
ref="viewTerminal"
|
||||
:id="`V${Date.now()}`"
|
||||
style="height: calc(100% - 36px)"
|
||||
prefix="tail"
|
||||
url="/ws/view"
|
||||
:ne-type="neType"
|
||||
:ne-id="neId"
|
||||
style="height: calc(100% - 36px)"
|
||||
@connect="fnInit()"
|
||||
></TerminalSSHView>
|
||||
<!-- 命令控制属性 -->
|
||||
|
||||
@@ -204,11 +204,13 @@ function fnDirCD(dir: string, index?: number) {
|
||||
|
||||
/**网元类型选择对应修改 */
|
||||
function fnNeChange(keys: any, _: any) {
|
||||
if (!Array.isArray(keys)) return;
|
||||
const neType = keys[0];
|
||||
const neId = keys[1];
|
||||
// 不是同类型时需要重新加载
|
||||
if (Array.isArray(keys) && queryParams.neType !== keys[0]) {
|
||||
const neType = keys[0];
|
||||
if (queryParams.neType !== neType || queryParams.neId !== neId) {
|
||||
queryParams.neType = neType;
|
||||
queryParams.neId = keys[1];
|
||||
queryParams.neId = neId;
|
||||
if (neType === 'IMS') {
|
||||
nePathArr.value = ['/var/log/ims'];
|
||||
queryParams.search = '';
|
||||
|
||||
@@ -40,7 +40,7 @@ let dict: {
|
||||
* 测试主机连接
|
||||
*/
|
||||
function fnHostTest(row: Record<string, any>) {
|
||||
if (modalState.confirmLoading || !row.addr) return;
|
||||
if (modalState.confirmLoading || !row.addr || !row.port) return;
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
testNeHost(row)
|
||||
@@ -124,8 +124,8 @@ let modalState: ModalStateType = reactive({
|
||||
addr: '',
|
||||
port: 22,
|
||||
user: 'omcuser',
|
||||
authMode: '0',
|
||||
password: 'a9tU53r',
|
||||
authMode: '2',
|
||||
password: '',
|
||||
privateKey: '',
|
||||
passPhrase: '',
|
||||
remark: '',
|
||||
@@ -283,11 +283,11 @@ function fnModalCancel() {
|
||||
|
||||
/**表单修改网元类型 */
|
||||
function fnNeTypeChange(v: any) {
|
||||
const hostsLen = modalState.from.hosts.length;
|
||||
// 网元默认只含22和4100
|
||||
if (hostsLen === 3 && v !== 'UPF') {
|
||||
if (modalState.from.hosts.length === 3) {
|
||||
modalState.from.hosts.pop();
|
||||
}
|
||||
const hostsLen = modalState.from.hosts.length;
|
||||
// UPF标准版本可支持5002
|
||||
if (hostsLen === 2 && v === 'UPF') {
|
||||
modalState.from.hosts.push({
|
||||
@@ -295,11 +295,27 @@ function fnNeTypeChange(v: any) {
|
||||
hostType: 'telnet',
|
||||
groupId: '1',
|
||||
title: 'Telnet_NE_5002',
|
||||
addr: '',
|
||||
addr: modalState.from.ip,
|
||||
port: 5002,
|
||||
user: 'user',
|
||||
user: 'admin',
|
||||
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: '',
|
||||
});
|
||||
}
|
||||
@@ -626,8 +642,13 @@ onMounted(() => {
|
||||
(s:any) => !(s.hostType === 'telnet' && modalState.from.neType === 'OMC')
|
||||
)"
|
||||
: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-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.ne.neHost.addr')">
|
||||
@@ -654,7 +675,22 @@ onMounted(() => {
|
||||
</a-col>
|
||||
</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-form-item :label="t('views.ne.neHost.user')">
|
||||
<a-input
|
||||
@@ -672,7 +708,6 @@ onMounted(() => {
|
||||
v-model:value="host.authMode"
|
||||
default-value="0"
|
||||
:options="dict.neHostAuthMode"
|
||||
:disabled="host.hostType === 'telnet'"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
@@ -692,7 +727,6 @@ onMounted(() => {
|
||||
>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<template v-if="host.authMode === '1'">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.privateKey')"
|
||||
@@ -722,6 +756,21 @@ onMounted(() => {
|
||||
</a-form-item>
|
||||
</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
|
||||
:label="t('common.remark')"
|
||||
:label-col="{ span: 3 }"
|
||||
@@ -736,6 +785,7 @@ onMounted(() => {
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 测试 -->
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.test')"
|
||||
name="test"
|
||||
|
||||
@@ -229,11 +229,11 @@ function fnModalOk() {
|
||||
* 表单修改网元类型
|
||||
*/
|
||||
function fnNeTypeChange(v: any) {
|
||||
const hostsLen = modalState.from.hosts.length;
|
||||
// 网元默认只含22和4100
|
||||
if (hostsLen === 3 && v !== 'UPF') {
|
||||
if (modalState.from.hosts.length === 3) {
|
||||
modalState.from.hosts.pop();
|
||||
}
|
||||
const hostsLen = modalState.from.hosts.length;
|
||||
// UPF标准版本可支持5002
|
||||
if (hostsLen === 2 && v === 'UPF') {
|
||||
modalState.from.hosts.push({
|
||||
@@ -249,6 +249,22 @@ function fnNeTypeChange(v: any) {
|
||||
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: '',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,7 +284,7 @@ function fnStepPrev() {
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.ne.neQuickSetup.stepPrevTip'),
|
||||
onOk() {
|
||||
fnRestStepState();
|
||||
fnRestStepState(t);
|
||||
fnToStepName('Start');
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { Modal, TableColumnsType, message } from 'ant-design-vue/lib';
|
||||
import { defineAsyncComponent, onMounted, reactive, ref, toRaw } from 'vue';
|
||||
import { Modal, message } from 'ant-design-vue/lib';
|
||||
import { defineAsyncComponent, onMounted, reactive, toRaw } from 'vue';
|
||||
import { fnToStepName, stepState } from '../hooks/useStep';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { listNeVersion, operateNeVersion } from '@/api/ne/neVersion';
|
||||
import { operateNeVersion } from '@/api/ne/neVersion';
|
||||
import {
|
||||
RESULT_CODE_ERROR,
|
||||
RESULT_CODE_SUCCESS,
|
||||
} from '@/constants/result-constants';
|
||||
import {
|
||||
addNeSoftware,
|
||||
listNeSoftware,
|
||||
newNeVersion,
|
||||
} from '@/api/ne/neSoftware';
|
||||
import { listNeSoftware, newNeVersion } from '@/api/ne/neSoftware';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -82,7 +82,7 @@ function fnStepEnd() {
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.ne.neQuickSetup.licenseEndTip'),
|
||||
onOk() {
|
||||
fnRestStepState();
|
||||
fnRestStepState(t);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ let fromState = ref({
|
||||
nef_ip: '172.16.5.210',
|
||||
mme_ip: '172.16.5.220',
|
||||
n3iwf_ip: '172.16.5.230',
|
||||
smsc_ip: '172.16.5.240',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -102,6 +102,9 @@ export function usePara5G() {
|
||||
case 'N3IWF':
|
||||
state.from.sbi.n3iwf_ip = item.ip;
|
||||
break;
|
||||
case 'SMSC':
|
||||
state.from.sbi.smsc_ip = item.ip;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ export function fnRestStepState(t?: any) {
|
||||
stepState.current = -1;
|
||||
stepState.neHost = {};
|
||||
stepState.neInfo = {};
|
||||
|
||||
// 多语言翻译
|
||||
if (t) {
|
||||
stepState.steps = [
|
||||
@@ -104,8 +105,5 @@ export function useStep({ t }: any) {
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
fnRestStepState(t);
|
||||
});
|
||||
return { currentComponent };
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ watch(
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
fnRestStepState(t);
|
||||
fnReloadData();
|
||||
});
|
||||
|
||||
|
||||
@@ -316,8 +316,8 @@ function fnModalOk() {
|
||||
.then(e => {
|
||||
modalState.confirmLoading = true;
|
||||
const from = toRaw(modalState.from);
|
||||
from.neId = queryParams.neId || '-';
|
||||
from.algoIndex = `${from.algoIndex}`;
|
||||
from.neId = queryParams.neId || '-';
|
||||
const result = from.id
|
||||
? updateUDMAuth(from)
|
||||
: from.num === 1
|
||||
@@ -484,43 +484,34 @@ function fnRecordDelete(imsi: string) {
|
||||
/**
|
||||
* UDM鉴权用户勾选导出
|
||||
*/
|
||||
function fnRecordExport(type: string = 'txt') {
|
||||
function fnRecordExport(type: string = 'txt') {
|
||||
const selectLen = tableState.selectedRowKeys.length;
|
||||
if (selectLen <= 0) return;
|
||||
const rows: Record<string, any>[] = tableState.data.filter(
|
||||
(row: Record<string, any>) =>
|
||||
tableState.selectedRowKeys.indexOf(row.imsi) >= 0
|
||||
);
|
||||
|
||||
let content = '';
|
||||
if (type == 'txt') {
|
||||
for (const row of rows) {
|
||||
const opc = row.opc === '-' ? '' : `,${row.opc}`;
|
||||
content += `${row.imsi},${row.ki},${row.algoIndex},${row.amf}${opc}\r\n`;
|
||||
}
|
||||
}
|
||||
if (type == 'csv') {
|
||||
content = `IMSI,ki,Algo Index,AMF,OPC\r\n`;
|
||||
for (const row of rows) {
|
||||
const opc = row.opc === '-' ? '' : `,${row.opc}`;
|
||||
content += `${row.imsi},${row.ki},${row.algoIndex},${row.amf}${opc}\r\n`;
|
||||
}
|
||||
}
|
||||
|
||||
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
|
||||
saveAs(blob, `UDMAuth_${Date.now()}.${type}`);
|
||||
const neId = queryParams.neId;
|
||||
if (!neId) return;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
exportUDMAuth({ type: type, neId: neId, imsis: tableState.selectedRowKeys })
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(t('common.msgSuccess', { msg: t('common.export') }), 3);
|
||||
saveAs(res.data, `UDMAuth_select_${Date.now()}.${type}`);
|
||||
} else {
|
||||
message.error(`${res.msg}`, 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**列表导出全部数据 */
|
||||
function fnExportList(type: string) {
|
||||
const neId = queryParams.neId;
|
||||
if (!neId) return;
|
||||
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
exportUDMAuth({
|
||||
neId: neId,
|
||||
type: type,
|
||||
})
|
||||
exportUDMAuth(Object.assign({ type: type }, queryParams))
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(t('common.msgSuccess', { msg: t('common.export') }), 3);
|
||||
@@ -558,6 +549,9 @@ function fnLoadData() {
|
||||
fnQueryReset();
|
||||
}, timerS * 1000);
|
||||
} else {
|
||||
modalState.loadDataLoading = false;
|
||||
tableState.loading = false; // 表格loading
|
||||
fnQueryReset();
|
||||
message.error({
|
||||
content: t('common.getInfoFail'),
|
||||
duration: 3,
|
||||
@@ -737,6 +731,7 @@ onMounted(() => {
|
||||
v-model:value="queryParams.neId"
|
||||
:options="neOtions"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
@change="fnGetList(1)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
@@ -818,7 +813,7 @@ onMounted(() => {
|
||||
{{ t('views.neUser.auth.import') }}
|
||||
</a-button>
|
||||
|
||||
<a-popconfirm
|
||||
<!-- <a-popconfirm
|
||||
:title="t('views.neUser.auth.exportConfirm')"
|
||||
placement="topRight"
|
||||
ok-text="TXT"
|
||||
@@ -827,6 +822,13 @@ onMounted(() => {
|
||||
:show-cancel="false"
|
||||
cancel-text="CSV"
|
||||
@cancel="fnExportList('csv')"
|
||||
> -->
|
||||
<a-popconfirm
|
||||
:title="t('views.neUser.auth.exportConfirm')"
|
||||
placement="topRight"
|
||||
ok-text="TXT"
|
||||
ok-type="default"
|
||||
@confirm="fnExportList('txt')"
|
||||
>
|
||||
<a-button type="dashed">
|
||||
<template #icon><ExportOutlined /></template>
|
||||
|
||||
@@ -29,12 +29,12 @@ const { t } = useI18n();
|
||||
let neOtions = ref<Record<string, any>[]>([]);
|
||||
|
||||
/**表单中多选的OPTION */
|
||||
const pcfRuleOption = reactive({
|
||||
pccOpt: [],
|
||||
sessOpt: [],
|
||||
qosOpt: [],
|
||||
headerOpt: [],
|
||||
sarOpt: [],
|
||||
const pcfRuleOption = ref<Record<string, any[]>>({
|
||||
pccRules: [],
|
||||
sessionRules: [],
|
||||
qosTemplate: [],
|
||||
headerEnrichTemplate: [],
|
||||
serviceAreaRestriction: [],
|
||||
});
|
||||
|
||||
/**查询参数 */
|
||||
@@ -103,7 +103,7 @@ let tableColumns = ref<TableColumnsType>([
|
||||
title: 'SAR',
|
||||
dataIndex: 'sar',
|
||||
align: 'left',
|
||||
width: 50,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'RFSP',
|
||||
@@ -121,34 +121,54 @@ let tableColumns = ref<TableColumnsType>([
|
||||
title: 'QoS Audio',
|
||||
dataIndex: 'qosAudio',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'Online Billing', // 在线计费
|
||||
dataIndex: 'online',
|
||||
align: 'left',
|
||||
width: 120,
|
||||
customRender(opt) {
|
||||
const status = +opt.value;
|
||||
return status ? 'Enable' : 'Disable';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Offline Billing', // 离线计费
|
||||
dataIndex: 'offline',
|
||||
align: 'left',
|
||||
width: 120,
|
||||
customRender(opt) {
|
||||
const status = +opt.value;
|
||||
return status ? 'Enable' : 'Disable';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'PCC Rules',
|
||||
dataIndex: 'pccRules',
|
||||
align: 'left',
|
||||
resizable: true,
|
||||
width: 150,
|
||||
minWidth: 100,
|
||||
maxWidth: 300,
|
||||
},
|
||||
{
|
||||
title: 'PCC Rules',
|
||||
dataIndex: 'pccRules',
|
||||
align: 'left',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: 'SESS Rules',
|
||||
dataIndex: 'sessRules',
|
||||
align: 'left',
|
||||
width: 120,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'HDR Enrich',
|
||||
dataIndex: 'hdrEnrich',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'UE Policy',
|
||||
dataIndex: 'uePolicy',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
@@ -258,12 +278,8 @@ const modalStateFrom = Form.useForm(
|
||||
*/
|
||||
function fnModalVisibleByEdit(row?: Record<string, any>) {
|
||||
getPCCRule(queryParams.neId)
|
||||
.then((res: any) => {
|
||||
pcfRuleOption.pccOpt = res.pccJson;
|
||||
pcfRuleOption.sessOpt = res.sessJson;
|
||||
pcfRuleOption.qosOpt = res.qosJson;
|
||||
pcfRuleOption.headerOpt = res.headerJson;
|
||||
pcfRuleOption.sarOpt = res.sarJson;
|
||||
.then((data: any) => {
|
||||
pcfRuleOption.value = data;
|
||||
})
|
||||
.finally(() => {
|
||||
modalState.isBatch = false;
|
||||
@@ -438,12 +454,9 @@ function fnModalCancel() {
|
||||
*/
|
||||
function fnModalVisibleByBatch(type: 'delete' | 'add' | 'update') {
|
||||
getPCCRule(queryParams.neId)
|
||||
.then((res: any) => {
|
||||
pcfRuleOption.pccOpt = res.pccJson;
|
||||
pcfRuleOption.sessOpt = res.sessJson;
|
||||
pcfRuleOption.qosOpt = res.qosJson;
|
||||
pcfRuleOption.headerOpt = res.headerJson;
|
||||
pcfRuleOption.sarOpt = res.sarJson;
|
||||
.then((data: any) => {
|
||||
pcfRuleOption.value = data;
|
||||
console.log(data);
|
||||
})
|
||||
.finally(() => {
|
||||
modalStateFrom.resetFields(); //重置表单
|
||||
@@ -767,9 +780,6 @@ onMounted(() => {
|
||||
ok-text="TXT"
|
||||
ok-type="default"
|
||||
@confirm="fnExportList('txt')"
|
||||
:show-cancel="false"
|
||||
cancel-text="CSV"
|
||||
@cancel="fnExportList('csv')"
|
||||
>
|
||||
<a-button type="dashed">
|
||||
<template #icon><ExportOutlined /></template>
|
||||
@@ -1021,7 +1031,7 @@ onMounted(() => {
|
||||
v-model:value="modalState.from.pccRules"
|
||||
allow-clear
|
||||
mode="tags"
|
||||
:options="pcfRuleOption.pccOpt"
|
||||
:options="pcfRuleOption.pccRules"
|
||||
:title="t('views.neUser.pcf.pccRuleTip')"
|
||||
/>
|
||||
</a-form-item>
|
||||
@@ -1032,7 +1042,7 @@ onMounted(() => {
|
||||
v-model:value="modalState.from.sessRules"
|
||||
allow-clear
|
||||
mode="tags"
|
||||
:options="pcfRuleOption.sessOpt"
|
||||
:options="pcfRuleOption.sessionRules"
|
||||
:title="t('views.neUser.pcf.sessRuleTip')"
|
||||
/>
|
||||
</a-form-item>
|
||||
@@ -1045,7 +1055,7 @@ onMounted(() => {
|
||||
<a-auto-complete
|
||||
v-model:value="modalState.from.qosAudio"
|
||||
allow-clear
|
||||
:options="pcfRuleOption.qosOpt"
|
||||
:options="pcfRuleOption.qosTemplate"
|
||||
:filter-option="filterOption"
|
||||
/>
|
||||
</a-form-item>
|
||||
@@ -1055,7 +1065,7 @@ onMounted(() => {
|
||||
<a-auto-complete
|
||||
v-model:value="modalState.from.qosVideo"
|
||||
allow-clear
|
||||
:options="pcfRuleOption.qosOpt"
|
||||
:options="pcfRuleOption.qosTemplate"
|
||||
:filter-option="filterOption"
|
||||
/>
|
||||
</a-form-item>
|
||||
@@ -1068,7 +1078,7 @@ onMounted(() => {
|
||||
<a-auto-complete
|
||||
v-model:value="modalState.from.hdrEnrich"
|
||||
allow-clear
|
||||
:options="pcfRuleOption.headerOpt"
|
||||
:options="pcfRuleOption.headerEnrichTemplate"
|
||||
:filter-option="filterOption"
|
||||
/>
|
||||
</a-form-item>
|
||||
@@ -1099,7 +1109,7 @@ onMounted(() => {
|
||||
<a-auto-complete
|
||||
v-model:value="modalState.from.sar"
|
||||
allow-clear
|
||||
:options="pcfRuleOption.sarOpt"
|
||||
:options="pcfRuleOption.serviceAreaRestriction"
|
||||
:filter-option="filterOption"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
@@ -118,12 +118,6 @@ let tableColumns = ref<ColumnsType>([
|
||||
align: 'center',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'RAT',
|
||||
dataIndex: 'rat',
|
||||
align: 'center',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title: 'Forbidden Areas',
|
||||
dataIndex: 'arfb',
|
||||
@@ -252,19 +246,24 @@ let modalState: ModalStateType = reactive({
|
||||
title: 'UDM签约用户',
|
||||
from: {
|
||||
id: undefined,
|
||||
num: 1,
|
||||
msisdn: '',
|
||||
neId: '',
|
||||
imsi: '',
|
||||
msisdn: '',
|
||||
// amDat
|
||||
ambr: 'def_ambr',
|
||||
nssai: 'def_nssai',
|
||||
rat: '0',
|
||||
rat: '0', // 0x00:VIRTUAL 0x01:WLAN 0x02:EUTRA 0x03:NR
|
||||
arfb: 'def_arfb',
|
||||
sar: 'def_sar',
|
||||
cn: '3',
|
||||
smData: '',
|
||||
smfSel: 'def_snssai',
|
||||
epsDat: '',
|
||||
neId: '',
|
||||
cnType: '3', // 0x00:EPC和5GC 0x01:5GC 0x02:EPC 0x03:EPC+5GC
|
||||
rfspIndex: 1,
|
||||
regTimer: 12000,
|
||||
ueUsageType: 1,
|
||||
activeTime: 1000,
|
||||
mico: '0',
|
||||
odbPs: '1',
|
||||
groupId: '-',
|
||||
// epsDat
|
||||
epsFlag: '1',
|
||||
epsOdb: [2],
|
||||
hplmnOdb: [3, 4],
|
||||
@@ -273,9 +272,12 @@ let modalState: ModalStateType = reactive({
|
||||
contextId: '1',
|
||||
apnContext: [1, 2, 0, 0, 0, 0],
|
||||
staticIp: '-',
|
||||
rfsp: 1,
|
||||
ueType: 1,
|
||||
//
|
||||
smData: '',
|
||||
smfSel: 'def_snssai',
|
||||
cag: 'def_cag',
|
||||
// 非字段
|
||||
num: 1,
|
||||
remark: '',
|
||||
},
|
||||
BatchDelForm: {
|
||||
@@ -623,6 +625,10 @@ function fnModalOk() {
|
||||
.map((item: number) => `${item}`.padStart(2, '0'))
|
||||
.join('');
|
||||
|
||||
from.activeTime = `${from.activeTime}`;
|
||||
from.rfspIndex = `${from.rfspIndex}`;
|
||||
from.regTimer = `${from.regTimer}`;
|
||||
from.ueUsageType = `${from.ueUsageType}`;
|
||||
from.neId = queryParams.neId || '-';
|
||||
const result = from.id
|
||||
? updateUDMSub(from)
|
||||
@@ -825,50 +831,30 @@ function fnRecordDelete(imsi: string) {
|
||||
/**
|
||||
* UDM签约用户导出
|
||||
*/
|
||||
function fnRecordExport(type: string = 'txt') {
|
||||
function fnRecordExport(type: string = 'txt') {
|
||||
const selectLen = tableState.selectedRowKeys.length;
|
||||
if (selectLen <= 0) return;
|
||||
const rows: Record<string, any>[] = tableState.data.filter(
|
||||
(row: Record<string, any>) =>
|
||||
tableState.selectedRowKeys.indexOf(row.imsi) >= 0
|
||||
);
|
||||
|
||||
let content = '';
|
||||
if (type == 'txt') {
|
||||
for (const row of rows) {
|
||||
debugger;
|
||||
const epsDat = [
|
||||
row.epsFlag,
|
||||
row.epsOdb,
|
||||
row.hplmnOdb,
|
||||
row.ard,
|
||||
row.epstpl,
|
||||
row.contextId,
|
||||
row.apnContext,
|
||||
row.staticIp,
|
||||
].join(',');
|
||||
content += `${row.imsi},${row.msisdn},${row.ambr},${row.nssai},${row.arfb},${row.sar},${row.rat},${row.cn},${row.smfSel},${row.smData},${epsDat}\r\n`;
|
||||
}
|
||||
}
|
||||
if (type == 'csv') {
|
||||
content = `imsi,msisdn,ambr,nssai,arfb,sar,rat,cn,smf_sel,sm_dat,eps_dat\r\n`;
|
||||
for (const row of rows) {
|
||||
const epsDat = [
|
||||
row.epsFlag,
|
||||
row.epsOdb,
|
||||
row.hplmnOdb,
|
||||
row.ard,
|
||||
row.epstpl,
|
||||
row.contextId,
|
||||
row.apnContext,
|
||||
row.staticIp,
|
||||
].join(',');
|
||||
content += `${row.imsi},${row.msisdn},${row.ambr},${row.nssai},${row.arfb},${row.sar},${row.rat},${row.cn},${row.smfSel},${row.smData},${epsDat}\r\n`;
|
||||
}
|
||||
}
|
||||
|
||||
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
|
||||
saveAs(blob, `UDMSub_${Date.now()}.${type}`);
|
||||
const neId = queryParams.neId;
|
||||
if (!neId) return;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
exportUDMSub({ type: type, neId: neId, imsis: tableState.selectedRowKeys })
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: t('common.export') }),
|
||||
duration: 2,
|
||||
});
|
||||
saveAs(res.data, `UDMSub_select_${Date.now()}.${type}`);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
});
|
||||
}
|
||||
|
||||
/**列表导出 */
|
||||
@@ -877,10 +863,7 @@ function fnExportList(type: string) {
|
||||
if (!neId) return;
|
||||
const key = 'exportSub';
|
||||
message.loading({ content: t('common.loading'), key });
|
||||
exportUDMSub({
|
||||
neId: neId,
|
||||
type: type,
|
||||
}).then(res => {
|
||||
exportUDMSub(Object.assign({ type: type }, queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: t('common.export') }),
|
||||
@@ -922,6 +905,9 @@ function fnLoadData() {
|
||||
fnQueryReset();
|
||||
}, timerS * 1000);
|
||||
} else {
|
||||
modalState.loadDataLoading = false;
|
||||
tableState.loading = false; // 表格loading
|
||||
fnQueryReset();
|
||||
message.error({
|
||||
content: t('common.getInfoFail'),
|
||||
duration: 3,
|
||||
@@ -1116,6 +1102,7 @@ onMounted(() => {
|
||||
v-model:value="queryParams.neId"
|
||||
:options="neOtions"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
@change="fnGetList(1)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
@@ -1211,7 +1198,7 @@ onMounted(() => {
|
||||
{{ t('views.neUser.sub.import') }}
|
||||
</a-button>
|
||||
|
||||
<a-popconfirm
|
||||
<!-- <a-popconfirm
|
||||
:title="t('views.neUser.sub.exportConfirm')"
|
||||
placement="topRight"
|
||||
ok-text="TXT"
|
||||
@@ -1220,6 +1207,13 @@ onMounted(() => {
|
||||
:show-cancel="false"
|
||||
cancel-text="CSV"
|
||||
@cancel="fnExportList('csv')"
|
||||
> -->
|
||||
<a-popconfirm
|
||||
:title="t('views.neUser.sub.exportConfirm')"
|
||||
placement="topRight"
|
||||
ok-text="TXT"
|
||||
ok-type="default"
|
||||
@confirm="fnExportList('txt')"
|
||||
>
|
||||
<a-button type="dashed">
|
||||
<template #icon>
|
||||
@@ -1333,7 +1327,7 @@ onMounted(() => {
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'cnFlag'">
|
||||
{{
|
||||
record.cn === '3'
|
||||
['1', '3'].includes(record.cnType)
|
||||
? t('views.neUser.sub.enable')
|
||||
: t('views.neUser.sub.disable')
|
||||
}}
|
||||
@@ -1599,7 +1593,7 @@ onMounted(() => {
|
||||
name="cnFlag"
|
||||
:help="t('views.neUser.sub.cnFlag')"
|
||||
>
|
||||
<a-select v-model:value="modalState.from.cn">
|
||||
<a-select v-model:value="modalState.from.cnType">
|
||||
<a-select-option value="3">
|
||||
{{ t('views.neUser.sub.enable') }}
|
||||
</a-select-option>
|
||||
@@ -1753,7 +1747,7 @@ onMounted(() => {
|
||||
name="mico"
|
||||
:help="t('views.neUser.sub.micoTip')"
|
||||
>
|
||||
<a-select value="1">
|
||||
<a-select v-model:value="modalState.from.mico">
|
||||
<a-select-option value="1">
|
||||
{{ t('views.neUser.sub.enable') }}
|
||||
</a-select-option>
|
||||
@@ -1764,9 +1758,19 @@ onMounted(() => {
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="24" :md="24" :xs="24">
|
||||
<a-form-item label="5G UE Usage Type" name="ueType">
|
||||
<a-form-item label="5G RAT Mode" name="rat">
|
||||
<a-select v-model:value="modalState.from.rat">
|
||||
<a-select-option value="0">VIRTUAL</a-select-option>
|
||||
<a-select-option value="1">WLAN</a-select-option>
|
||||
<a-select-option value="2">EUTRA</a-select-option>
|
||||
<a-select-option value="3">NR</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="24" :md="24" :xs="24">
|
||||
<a-form-item label="5G UE Usage Type" name="ueUsageType">
|
||||
<a-input-number
|
||||
v-model:value="modalState.from.ueType"
|
||||
v-model:value="modalState.from.ueUsageType"
|
||||
style="width: 100%"
|
||||
:min="0"
|
||||
:max="127"
|
||||
@@ -1786,9 +1790,9 @@ onMounted(() => {
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="24" :md="24" :xs="24">
|
||||
<a-form-item label="5G RFSP Index" name="rfsp">
|
||||
<a-form-item label="5G RFSP Index" name="rfspIndex">
|
||||
<a-input-number
|
||||
v-model:value="modalState.from.rfsp"
|
||||
v-model:value="modalState.from.rfspIndex"
|
||||
style="width: 100%"
|
||||
:min="0"
|
||||
:max="127"
|
||||
|
||||
@@ -647,7 +647,11 @@ onMounted(() => {
|
||||
name="title"
|
||||
v-bind="modalStateFrom.validateInfos.title"
|
||||
>
|
||||
<a-input v-model:value="modalState.from.title" allow-clear>
|
||||
<a-input
|
||||
v-model:value="modalState.from.title"
|
||||
:maxlength="255"
|
||||
allow-clear
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
@@ -657,7 +661,11 @@ onMounted(() => {
|
||||
name="kpiId"
|
||||
v-bind="modalStateFrom.validateInfos.kpiId"
|
||||
>
|
||||
<a-input v-model:value="modalState.from.kpiId" allow-clear>
|
||||
<a-input
|
||||
v-model:value="modalState.from.kpiId"
|
||||
:maxlength="16"
|
||||
allow-clear
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
@@ -669,7 +677,11 @@ onMounted(() => {
|
||||
:label-col="{ span: 3 }"
|
||||
v-bind="modalStateFrom.validateInfos.expression"
|
||||
>
|
||||
<a-input v-model:value="modalState.from.expression" allow-clear>
|
||||
<a-input
|
||||
v-model:value="modalState.from.expression"
|
||||
:maxlength="1024"
|
||||
allow-clear
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
|
||||
@@ -251,14 +251,14 @@ function fnGetListTitle() {
|
||||
|
||||
// 获取表头文字
|
||||
getKPITitle(state.neType[0])
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
tableColumns.value = [];
|
||||
const columns: ColumnsType = [];
|
||||
for (const item of res.data) {
|
||||
const kpiDisplay = item[`${language}Title`];
|
||||
.then(res => {//处理getKPITitle返回的结果
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {//检查值
|
||||
tableColumns.value = [];//设为空数组
|
||||
const columns: ColumnsType = [];//初始化,构建新表头
|
||||
for (const item of res.data) {//遍历res.data
|
||||
const kpiDisplay = item[`${language}Title`];//提取标题kpiDisplay和ID标识kpiValue
|
||||
const kpiValue = item[`kpiId`];
|
||||
columns.push({
|
||||
columns.push({//
|
||||
title: kpiDisplay,
|
||||
dataIndex: kpiValue,
|
||||
align: 'left',
|
||||
@@ -297,7 +297,7 @@ function fnGetListTitle() {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.then(result => {
|
||||
.then(result => {//result是前一个.then返回的值(true or false)
|
||||
result && fnGetList();
|
||||
});
|
||||
}
|
||||
@@ -334,27 +334,27 @@ function fnChangShowType() {
|
||||
|
||||
/**绘制图表 */
|
||||
function fnRanderChart() {
|
||||
const container: HTMLElement | undefined = kpiChartDom.value;
|
||||
if (!container) return;
|
||||
const container: HTMLElement | undefined = kpiChartDom.value;//获取图表容器DOM元素
|
||||
if (!container) return;//若没有,则退出函数
|
||||
kpiChart.value = markRaw(echarts.init(container, 'light'));
|
||||
|
||||
const option: EChartsOption = {
|
||||
//初始化Echarts图表实例,应用light主题,并赋值给kpiChart.value,markRaw是vue函数,用于标记对象为不可响应
|
||||
const option: EChartsOption = {//定义图表的配置对象,tooltip的出发方式为axis
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
position: function (pt: any) {
|
||||
return [pt[0], '10%'];
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
xAxis: {//x类别轴
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: [], // 数据x轴
|
||||
},
|
||||
yAxis: {
|
||||
yAxis: {//y类别轴
|
||||
type: 'value',
|
||||
boundaryGap: [0, '100%'],
|
||||
},
|
||||
legend: {
|
||||
legend: {//图例垂直滚动
|
||||
type: 'scroll',
|
||||
orient: 'vertical',
|
||||
top: 40,
|
||||
@@ -367,13 +367,13 @@ function fnRanderChart() {
|
||||
icon: 'circle',
|
||||
selected: {},
|
||||
},
|
||||
grid: {
|
||||
grid: {//网格区域边距
|
||||
left: '10%',
|
||||
right: '30%',
|
||||
bottom: '20%',
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
{//启用图表的数据缩放,范围90%-100%
|
||||
type: 'inside',
|
||||
start: 90,
|
||||
end: 100,
|
||||
@@ -385,9 +385,9 @@ function fnRanderChart() {
|
||||
],
|
||||
series: [], // 数据y轴
|
||||
};
|
||||
kpiChart.value.setOption(option);
|
||||
kpiChart.value.setOption(option);//设置图表配置项,应用到kpiChart实例上
|
||||
|
||||
// 创建 ResizeObserver 实例
|
||||
// 创建 ResizeObserver 实例 监听图表容器大小变化,并在变化时调整图表大小
|
||||
var observer = new ResizeObserver(entries => {
|
||||
if (kpiChart.value) {
|
||||
kpiChart.value.resize();
|
||||
@@ -452,6 +452,7 @@ function fnRanderChartData() {
|
||||
|
||||
// 用降序就反转
|
||||
let orgData = tableState.data;
|
||||
console.log(orgData);
|
||||
if (queryParams.sortOrder === 'desc') {
|
||||
orgData = orgData.toReversed();
|
||||
}
|
||||
|
||||
1024
src/views/perfManage/kpiKeyTarget/index.vue
Normal file
1024
src/views/perfManage/kpiKeyTarget/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
791
src/views/perfManage/kpiOverView/index.vue
Normal file
791
src/views/perfManage/kpiOverView/index.vue
Normal file
@@ -0,0 +1,791 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted, nextTick, computed } from 'vue';
|
||||
import * as echarts from 'echarts/core';
|
||||
import { LegendComponent } from 'echarts/components';
|
||||
import { LineChart, BarChart } from 'echarts/charts';
|
||||
import { GridComponent, TooltipComponent, TitleComponent } from 'echarts/components';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { getKPITitle, listKPIData } from '@/api/perfManage/goldTarget';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { RESULT_CODE_ERROR, RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import type { Dayjs } from 'dayjs';
|
||||
import dayjs from 'dayjs';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import { generateColorRGBA } from '@/utils/generate-utils';
|
||||
import { BarChartOutlined, LineChartOutlined, UnorderedListOutlined, DownOutlined, MoreOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
// 在这里定义 ChartDataItem 接口
|
||||
interface ChartDataItem {
|
||||
date: string; // 将存储完整的时间字符串,包含时分秒
|
||||
[kpiId: string]: string | number; // 动态指标
|
||||
}
|
||||
|
||||
echarts.use([
|
||||
LineChart,
|
||||
BarChart,
|
||||
GridComponent,
|
||||
TooltipComponent,
|
||||
TitleComponent,
|
||||
CanvasRenderer,
|
||||
LegendComponent
|
||||
]);
|
||||
// WebSocket连接
|
||||
const ws = ref<WS | null>(null);
|
||||
|
||||
//日期范围响应式变量
|
||||
const dateRange = ref<[string, string]>([
|
||||
dayjs().startOf('day').valueOf().toString(),
|
||||
dayjs().valueOf().toString()
|
||||
]);
|
||||
//实时数据状态
|
||||
const isRealtime = ref(false);
|
||||
//图表数据响应式数组
|
||||
const chartData = ref<ChartDataItem[]>([]);
|
||||
|
||||
//储存Echarts的实例的变量
|
||||
let chart: echarts.ECharts | null = null;
|
||||
|
||||
//observer 变量 监听图表容器大小
|
||||
let observer: ResizeObserver | null = null;
|
||||
|
||||
//日期变化时更新图表数据
|
||||
const handleDateChange = (
|
||||
value: [string, string] | [Dayjs, Dayjs],
|
||||
dateStrings: [string, string]
|
||||
) => {
|
||||
if (!dateStrings[0] || !dateStrings[1]) {
|
||||
console.warn('Invalid date strings:', dateStrings);
|
||||
return;
|
||||
}
|
||||
|
||||
dateRange.value = [
|
||||
dayjs(dateStrings[0]).valueOf().toString(),
|
||||
dayjs(dateStrings[1]).valueOf().toString()
|
||||
];
|
||||
fetchChartData();
|
||||
};
|
||||
|
||||
//切换实时数据
|
||||
const toggleRealtime = () => {
|
||||
fnRealTimeSwitch(isRealtime.value);
|
||||
};
|
||||
|
||||
// 定义所有网元类型
|
||||
const ALL_NE_TYPES = ['AMF', 'SMF', 'UPF', 'MME', 'IMS', 'SMSC'] as const;
|
||||
type NeType = typeof ALL_NE_TYPES[number];
|
||||
|
||||
// 定义要筛选的指标 ID,按网元类型组织
|
||||
const TARGET_KPI_IDS: Record<NeType, string[]> = {
|
||||
AMF: ['AMF.02', 'AMF.03', 'AMF.A.07', 'AMF.A.08'],
|
||||
SMF: ['SMF.02', 'SMF.03', 'SMF.04', 'SMF.05'],
|
||||
UPF: ['UPF.03', 'UPF.04', 'UPF.05', 'UPF.06'],
|
||||
MME: ['MME.A.01', 'MME.A.02', 'MME.A.03'],
|
||||
IMS: ['SCSCF.01', 'SCSCF.02', 'SCSCF.05', 'SCSCF.06'],
|
||||
SMSC: ['SMSC.A.01', 'SMSC.A.02', 'SMSC.A.03']
|
||||
};
|
||||
|
||||
// 实时数据开关函数
|
||||
const fnRealTimeSwitch = (bool: boolean) => {
|
||||
if (bool) {
|
||||
if(!ws.value) {
|
||||
ws.value = new WS();
|
||||
}
|
||||
chartData.value = [];
|
||||
|
||||
const options: OptionsType = {
|
||||
url: '/ws',
|
||||
params: {
|
||||
subGroupID: ALL_NE_TYPES.map(type => `10_${type}_001`).join(','),
|
||||
},
|
||||
onmessage: wsMessage,
|
||||
onerror: wsError,
|
||||
};
|
||||
ws.value.connect(options);
|
||||
} else if(ws.value) {
|
||||
ws.value.close();//断开链接
|
||||
ws.value = null;//清空链接
|
||||
}
|
||||
}
|
||||
|
||||
// 接收数据后错误回调
|
||||
const wsError = () => {
|
||||
message.error(t('common.websocketError'));
|
||||
}
|
||||
|
||||
// 接收数据后回调
|
||||
const wsMessage = (res: Record<string, any>) => {
|
||||
const { code, data } = res;
|
||||
if (code === RESULT_CODE_ERROR) {
|
||||
console.warn(res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data?.groupId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const kpiEvent = data.data;
|
||||
if (!kpiEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 构造新的数据点
|
||||
const newData: ChartDataItem = {
|
||||
date: kpiEvent.timeGroup
|
||||
? kpiEvent.timeGroup.toString()
|
||||
: Date.now().toString()
|
||||
};
|
||||
|
||||
// 只添加已选中的指标的数据
|
||||
selectedKPIs.value.forEach(kpiId => {
|
||||
if (kpiEvent[kpiId] !== undefined) {
|
||||
newData[kpiId] = Number(kpiEvent[kpiId]);
|
||||
} else {
|
||||
newData[kpiId] = 0;
|
||||
}
|
||||
});
|
||||
|
||||
// 更新数据
|
||||
updateChartData(newData);
|
||||
};
|
||||
|
||||
// 获取图表数据方法
|
||||
const fetchChartData = async () => {
|
||||
if (kpiColumns.value.length === 0) {
|
||||
console.warn('No KPI columns available');
|
||||
updateChart();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const [startTime, endTime] = dateRange.value;
|
||||
|
||||
if (!startTime || !endTime) {
|
||||
console.warn('Invalid date range:', dateRange.value);
|
||||
return;
|
||||
}
|
||||
|
||||
const allData: any[] = [];
|
||||
|
||||
// 使用 ALL_NE_TYPES 遍历网元类型
|
||||
for (const neType of ALL_NE_TYPES) {
|
||||
const params = {
|
||||
neType,
|
||||
neId: '001',
|
||||
startTime: String(startTime),
|
||||
endTime: String(endTime),
|
||||
sortField: 'timeGroup',
|
||||
sortOrder: 'asc',
|
||||
interval: 5,
|
||||
kpiIds: TARGET_KPI_IDS[neType].join(',')
|
||||
};
|
||||
|
||||
|
||||
|
||||
const res = await listKPIData(params);
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
allData.push(...res.data);
|
||||
}
|
||||
}
|
||||
|
||||
// 按时间分组合数据
|
||||
const groupedData = new Map<string, any>();
|
||||
allData.forEach(item => {
|
||||
const timeKey = item.timeGroup;
|
||||
if (!groupedData.has(timeKey)) {
|
||||
groupedData.set(timeKey, { timeGroup: timeKey });
|
||||
}
|
||||
const existingData = groupedData.get(timeKey);
|
||||
Object.assign(existingData, item);
|
||||
});
|
||||
|
||||
// 直接将处理后的数据赋值给 chartData.value
|
||||
chartData.value = Array.from(groupedData.values())
|
||||
.sort((a, b) => Number(a.timeGroup) - Number(b.timeGroup))
|
||||
.map(item => {
|
||||
const dataItem: ChartDataItem = {
|
||||
date: item.timeGroup.toString(),
|
||||
};
|
||||
kpiColumns.value.forEach(kpi => {
|
||||
dataItem[kpi.kpiId] = Number(item[kpi.kpiId]) || 0;
|
||||
});
|
||||
return dataItem;
|
||||
});
|
||||
|
||||
updateChart();
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch chart data:', error);
|
||||
message.error(t('common.getInfoFail'));
|
||||
}
|
||||
};
|
||||
|
||||
// 添加一个 Map 来存储每个指标的临时固定颜色
|
||||
const kpiColors = new Map<string, string>();
|
||||
|
||||
// 定义图表类型的响应式变量
|
||||
const chartType = ref<'line' | 'bar'>('line');
|
||||
|
||||
// 添加切换图表类型的方法
|
||||
const toggleChartType = () => {
|
||||
chartType.value = chartType.value === 'line' ? 'bar' : 'line';
|
||||
updateChart();
|
||||
};
|
||||
|
||||
// 更新图表
|
||||
const updateChart = () => {
|
||||
if (!chart || !kpiColumns.value.length) return;//首先检查图表实例和指标是否存在
|
||||
//过滤出已选择的指标列
|
||||
const filteredColumns = kpiColumns.value.filter(col => selectedKPIs.value.includes(col.kpiId));
|
||||
const legendData = filteredColumns.map(item => item.title);//创建图例数据数组,包含所有选中的指标的标题
|
||||
//为每个选中的指标创建一个系列配置
|
||||
const series = filteredColumns.map(item => {
|
||||
const color = kpiColors.get(item.kpiId) || generateColorRGBA();
|
||||
if (!kpiColors.has(item.kpiId)) {
|
||||
kpiColors.set(item.kpiId, color);//保持指标颜色的临时一致性
|
||||
}
|
||||
|
||||
return {
|
||||
name: item.title,
|
||||
type: chartType.value, // 使用当前选择的图表类型
|
||||
data: chartData.value.length > 0
|
||||
? chartData.value.map(dataItem => dataItem[item.kpiId] || 0)
|
||||
: [0],
|
||||
smooth: chartType.value === 'line', // 只在折线图时使用平滑
|
||||
symbol: chartType.value === 'line' ? 'circle' : undefined, // 只在折线图时显示标记
|
||||
symbolSize: chartType.value === 'line' ? 6 : undefined,
|
||||
showSymbol: chartType.value === 'line',
|
||||
itemStyle: { color }
|
||||
};
|
||||
});
|
||||
//图表配置对象
|
||||
const option = {
|
||||
title: {
|
||||
text: t('views.perfManage.kpiOverView.kpiChartTitle'),
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
position: function(pt: any) {
|
||||
return [pt[0], '10%'];
|
||||
},
|
||||
},
|
||||
legend: {//图例配置
|
||||
data: legendData,
|
||||
type: 'scroll',
|
||||
orient: 'horizontal',
|
||||
top: 25,
|
||||
textStyle: {
|
||||
fontSize: 12
|
||||
},
|
||||
selected: Object.fromEntries(legendData.map(name => [name, true])),
|
||||
show: true,
|
||||
left: 'center',
|
||||
width: '80%',
|
||||
height: 50,
|
||||
padding: [5, 10],
|
||||
},
|
||||
grid: { //网格配置
|
||||
left: '3%',
|
||||
right: '4%',
|
||||
bottom: '3%',
|
||||
top: 100,
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: { //x轴配置
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: chartData.value.length > 0
|
||||
? chartData.value.map(item => {
|
||||
// 将时间戳转换为包含时分秒的格式
|
||||
return dayjs(Number(item.date)).format('YYYY-MM-DD HH:mm:ss');
|
||||
})
|
||||
: [''],
|
||||
axisLabel: {
|
||||
formatter: (value: string) => {
|
||||
// 自定义 x 轴标签的显示格式
|
||||
return dayjs(value).format('YYYY-MM-DD HH:mm:ss');
|
||||
},
|
||||
rotate: 0,
|
||||
interval: 'auto', // 自动计算显示间隔
|
||||
align: 'right'
|
||||
}
|
||||
},
|
||||
yAxis: { // y轴配置
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter: '{value}'
|
||||
},
|
||||
// 添加自动计算的分割段数
|
||||
splitNumber: 5,
|
||||
// 添加自动计算的最小/最大值围
|
||||
scale: true
|
||||
},
|
||||
series: series //配置数据
|
||||
};
|
||||
|
||||
chart.setOption(option);//使用新的配置更新图表
|
||||
chart.resize();//调整图表大小以适应容器
|
||||
|
||||
// 如果已经有 observer,先断开连接
|
||||
if (observer) {
|
||||
observer.disconnect();
|
||||
}
|
||||
|
||||
// 创建新的 ResizeObserver
|
||||
observer = new ResizeObserver(() => {
|
||||
if (chart) {
|
||||
chart.resize();
|
||||
}
|
||||
});
|
||||
|
||||
// 观察图表容器
|
||||
const container = document.getElementById('chartContainer');
|
||||
if (container) {
|
||||
observer.observe(container);
|
||||
}
|
||||
};
|
||||
|
||||
//钩子函数
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// 获取所有网元的指标
|
||||
await fetchSpecificKPI();
|
||||
await nextTick();
|
||||
|
||||
const container = document.getElementById('chartContainer');
|
||||
if (container && !chart) {
|
||||
chart = echarts.init(container);
|
||||
|
||||
if (kpiColumns.value.length > 0) {
|
||||
|
||||
updateChart();
|
||||
await fetchChartData();
|
||||
} else {
|
||||
console.warn('No KPI columns available after fetching');
|
||||
}
|
||||
} else if (chart) {
|
||||
console.warn('Chart already initialized, skipping initialization');
|
||||
} else {
|
||||
console.error('Chart container not found');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize:', error);
|
||||
message.error(t('common.initFail'));
|
||||
}
|
||||
});
|
||||
|
||||
// 定义指列类型
|
||||
interface KPIColumn {
|
||||
title: string;
|
||||
dataIndex: string;
|
||||
key: string;
|
||||
kpiId: string;
|
||||
neType: string; // 添加网元类型字段
|
||||
}
|
||||
|
||||
// 存储指标列信
|
||||
const kpiColumns = ref<KPIColumn[]>([]);
|
||||
// 添加选中指标的的状态
|
||||
const selectedKPIs = ref<string[]>([]);
|
||||
// 添加对话框可见性状态
|
||||
const isModalVisible = ref(false);
|
||||
// 添加临时存储下拉框选择的数组
|
||||
const tempSelectedKPIs = ref<string[]>([]);
|
||||
|
||||
// 添加一个变量保存打开对话框时的选择状态
|
||||
const originalSelectedKPIs = ref<string[]>([]);
|
||||
|
||||
// 打开对话框的方法
|
||||
const showKPISelector = () => {
|
||||
// 保存当前的选择状态
|
||||
originalSelectedKPIs.value = [...selectedKPIs.value];
|
||||
|
||||
// 初始化临时选择为当前已选择的其他指标
|
||||
const primaryKPIs = Object.values(TARGET_KPI_IDS).flat();
|
||||
tempSelectedKPIs.value = selectedKPIs.value.filter(kpiId =>
|
||||
!primaryKPIs.includes(kpiId)
|
||||
);
|
||||
isModalVisible.value = true;
|
||||
};
|
||||
|
||||
// 保存选中指标到 localStorage 的方法
|
||||
const saveSelectedKPIs = () => {
|
||||
localStorage.setItem('selectedKPIs', JSON.stringify(selectedKPIs.value));
|
||||
};
|
||||
|
||||
// 取消按钮的处理方法
|
||||
const handleModalCancel = () => {
|
||||
// 恢复到打开对话框时的选择状态
|
||||
selectedKPIs.value = [...originalSelectedKPIs.value];
|
||||
// 清空临时选择
|
||||
tempSelectedKPIs.value = [];
|
||||
isModalVisible.value = false;
|
||||
};
|
||||
|
||||
// 确认按钮的处理方法
|
||||
const handleModalOk = () => {
|
||||
// 获取主要指标列表
|
||||
const primaryKPIs = Object.values(TARGET_KPI_IDS).flat();
|
||||
|
||||
// 获取当前在主界面选中的主要指标
|
||||
const selectedPrimaryKPIs = selectedKPIs.value.filter(kpiId =>
|
||||
primaryKPIs.includes(kpiId)
|
||||
);
|
||||
|
||||
// 合并选中的主要指标和临时选中的其他指标
|
||||
selectedKPIs.value = Array.from(new Set([
|
||||
...selectedPrimaryKPIs, // 只包含已选中的主要指标
|
||||
...tempSelectedKPIs.value // 临时选中的其他指标
|
||||
]));
|
||||
|
||||
// 清空临时选择和原始选择
|
||||
tempSelectedKPIs.value = [];
|
||||
originalSelectedKPIs.value = [];
|
||||
|
||||
// 保存选择并更新图表
|
||||
saveSelectedKPIs();
|
||||
updateChart();
|
||||
isModalVisible.value = false;
|
||||
};
|
||||
|
||||
// 获取网元指标
|
||||
const fetchSpecificKPI = async () => {
|
||||
const language = currentLocale.value.split('_')[0] === 'zh' ? 'cn' : currentLocale.value.split('_')[0];
|
||||
|
||||
try {
|
||||
let allKPIs: KPIColumn[] = [];
|
||||
|
||||
// 1. 获取所有网元的全部指标
|
||||
for (const neType of ALL_NE_TYPES) {
|
||||
const res = await getKPITitle(neType.toUpperCase());
|
||||
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
// 转换指标格式
|
||||
const formattedKPIs = res.data.map(item => ({
|
||||
title: item[`${language}Title`],
|
||||
dataIndex: item.kpiId,
|
||||
key: item.kpiId,
|
||||
kpiId: item.kpiId,
|
||||
neType: neType // 添加网元类型信息
|
||||
}));
|
||||
|
||||
// 添加到所有指标数组
|
||||
allKPIs = [...allKPIs, ...formattedKPIs];
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 更新所有指标到 kpiColumns
|
||||
kpiColumns.value = allKPIs;
|
||||
|
||||
// 3. 尝试加载保存的选择
|
||||
const savedKPIs = localStorage.getItem('selectedKPIs');
|
||||
if (savedKPIs) {
|
||||
// 确保保存的选择仍然存在于当前指标中
|
||||
const validSavedKPIs = JSON.parse(savedKPIs).filter(
|
||||
(kpiId: string) => kpiColumns.value.some(col => col.kpiId === kpiId)
|
||||
);
|
||||
if (validSavedKPIs.length > 0) {
|
||||
selectedKPIs.value = validSavedKPIs;
|
||||
} else {
|
||||
// 如果没有有效的保存选择,则默认选择<E98089><E68BA9>要指标
|
||||
selectedKPIs.value = Object.values(TARGET_KPI_IDS).flat();
|
||||
}
|
||||
} else {
|
||||
// 如果没有保存的选择,则默认选择重要指标
|
||||
selectedKPIs.value = Object.values(TARGET_KPI_IDS).flat();
|
||||
}
|
||||
|
||||
if (kpiColumns.value.length === 0) {
|
||||
console.warn('No KPIs found');
|
||||
} else {
|
||||
console.log(`Found ${kpiColumns.value.length} total KPIs`);
|
||||
}
|
||||
|
||||
return kpiColumns.value;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch KPI titles:', error);
|
||||
message.error(t('common.getInfoFail'));
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
// onUnmounted 钩子
|
||||
onUnmounted(() => {
|
||||
if(ws.value && ws.value.state() === WebSocket.OPEN) {
|
||||
ws.value.close();
|
||||
}
|
||||
if (observer) {
|
||||
observer.disconnect();
|
||||
observer = null;
|
||||
}
|
||||
if (chart) {
|
||||
chart.dispose();
|
||||
chart = null;
|
||||
}
|
||||
// 可选:在组件卸载时保存选择
|
||||
saveSelectedKPIs();
|
||||
});
|
||||
|
||||
const { t, currentLocale } = useI18n();
|
||||
|
||||
// 更新图表数据方法
|
||||
const updateChartData = (newData: ChartDataItem) => {
|
||||
chartData.value.push(newData);
|
||||
if (chartData.value.length > 100) {
|
||||
chartData.value.shift();
|
||||
}
|
||||
|
||||
if (chart) {
|
||||
chart.setOption({
|
||||
xAxis: {
|
||||
data: chartData.value.map(item =>
|
||||
dayjs(Number(item.date)).format('YYYY-MM-DD HH:mm:ss')
|
||||
)
|
||||
},
|
||||
series: selectedKPIs.value.map(kpiId => ({
|
||||
type: chartType.value, // 使用当前选择的图表类型
|
||||
data: chartData.value.map(item => item[kpiId] || 0)
|
||||
}))
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// groupedKPIs 计算属性,使用 TARGET_KPI_IDS 来分组过滤
|
||||
const groupedKPIs = computed(() => {
|
||||
const groups: Record<string, KPIColumn[]> = {};
|
||||
|
||||
ALL_NE_TYPES.forEach(neType => {
|
||||
// 使用 TARGET_KPI_IDS 中定义的指标 ID 来过滤
|
||||
const targetIds = TARGET_KPI_IDS[neType];
|
||||
groups[neType] = kpiColumns.value.filter(kpi =>
|
||||
targetIds.includes(kpi.kpiId)
|
||||
);
|
||||
});
|
||||
return groups;
|
||||
});
|
||||
|
||||
// 计算其他指标
|
||||
const secondaryKPIs = computed(() => {
|
||||
const groups: Record<string, KPIColumn[]> = {};
|
||||
|
||||
if (kpiColumns.value.length === 0) {
|
||||
console.warn('No KPI columns available');
|
||||
return groups;
|
||||
}
|
||||
|
||||
ALL_NE_TYPES.forEach(neType => {
|
||||
// 获取当前网元类型的主要指标 ID
|
||||
const primaryIds = TARGET_KPI_IDS[neType];
|
||||
|
||||
// 从所有指标中筛选出当前网元其他指标
|
||||
groups[neType] = kpiColumns.value.filter(kpi => {
|
||||
// 检查是否不在主要指标列表中
|
||||
const isNotPrimary = !primaryIds.includes(kpi.kpiId);
|
||||
|
||||
// 检查是否属于当前网元类型
|
||||
// 使用 getKPITitle API 返回的原始数据中的网元类型信息
|
||||
const isCurrentNeType = kpi.neType === neType;
|
||||
|
||||
return isCurrentNeType && isNotPrimary;
|
||||
});
|
||||
});
|
||||
return groups;
|
||||
});
|
||||
|
||||
// 添加处理其他指标选择变化的方法
|
||||
const handleSecondaryKPIChange = (kpiId: string, checked: boolean) => {
|
||||
if (checked) {
|
||||
// 如果选中,将指标 ID 添加到临时列表
|
||||
if (!tempSelectedKPIs.value.includes(kpiId)) {
|
||||
tempSelectedKPIs.value = [...tempSelectedKPIs.value, kpiId];
|
||||
}
|
||||
} else {
|
||||
// 如果取消选中,从临时列表中移除指标 ID
|
||||
tempSelectedKPIs.value = tempSelectedKPIs.value.filter(id => id !== kpiId);
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="kpi-overview">
|
||||
<div class="controls">
|
||||
<a-range-picker
|
||||
v-model:value="dateRange"
|
||||
:show-time="{ format: 'HH:mm:ss' }"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
:value-format="'x'"
|
||||
:disabled="isRealtime"
|
||||
@change="handleDateChange"
|
||||
/>
|
||||
<a-button @click="showKPISelector">
|
||||
<template #icon>
|
||||
<unordered-list-outlined />
|
||||
</template>
|
||||
{{t('views.perfManage.kpiOverView.chooseMetrics')}}
|
||||
</a-button>
|
||||
<a-button @click="toggleChartType">
|
||||
<template #icon>
|
||||
<bar-chart-outlined v-if="chartType === 'line'" />
|
||||
<line-chart-outlined v-else />
|
||||
</template>
|
||||
{{ chartType === 'line' ? t('views.perfManage.kpiOverView.changeLine') : t('views.perfManage.kpiOverView.changeBar') }}
|
||||
</a-button>
|
||||
<a-form-item :label="isRealtime ? t('views.dashboard.cdr.realTimeDataStart') : t('views.dashboard.cdr.realTimeDataStop')">
|
||||
<a-switch
|
||||
v-model:checked="isRealtime"
|
||||
@change="toggleRealtime"
|
||||
/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div id="chartContainer" class="chart-container"></div>
|
||||
|
||||
<!-- 修改指标选择对话框 -->
|
||||
<a-modal
|
||||
v-model:visible="isModalVisible"
|
||||
title="选择要显示的指标"
|
||||
@ok="handleModalOk"
|
||||
@cancel="handleModalCancel"
|
||||
width="800px"
|
||||
:bodyStyle="{ maxHeight: '600px', overflow: 'auto' }"
|
||||
>
|
||||
<a-checkbox-group v-model:value="selectedKPIs">
|
||||
<div class="kpi-checkbox-list">
|
||||
<a-card
|
||||
v-for="neType in ALL_NE_TYPES"
|
||||
:key="neType"
|
||||
class="ne-type-card"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #title>
|
||||
<span class="card-title">{{ neType.toUpperCase() }}</span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<a-dropdown v-if="secondaryKPIs[neType]?.length" trigger="click">
|
||||
<a-button type="link" size="small">
|
||||
<more-outlined />
|
||||
<down-outlined />
|
||||
<span class="secondary-count">({{ secondaryKPIs[neType].length }})</span>
|
||||
</a-button>
|
||||
<template >
|
||||
<div class="secondary-kpi-menu" @click.stop>
|
||||
<div
|
||||
v-for="kpi in secondaryKPIs[neType]"
|
||||
:key="kpi.kpiId"
|
||||
class="secondary-kpi-item"
|
||||
@click.stop
|
||||
>
|
||||
<a-checkbox
|
||||
:value="kpi.kpiId"
|
||||
:checked="tempSelectedKPIs.includes(kpi.kpiId)"
|
||||
@change="(e) => handleSecondaryKPIChange(kpi.kpiId, e.target.checked)"
|
||||
@click.stop
|
||||
>
|
||||
{{ kpi.title }}
|
||||
</a-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<div class="ne-type-items">
|
||||
<div
|
||||
v-for="kpi in groupedKPIs[neType]"
|
||||
:key="kpi.kpiId"
|
||||
class="kpi-checkbox-item"
|
||||
>
|
||||
<a-checkbox :value="kpi.kpiId">
|
||||
{{ kpi.title }}
|
||||
</a-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</a-checkbox-group>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 基础布局样式 */
|
||||
.kpi-overview {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: calc(100vh - 160px);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 指标选择对话框样式 */
|
||||
.kpi-checkbox-list {
|
||||
padding: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 网元指标列表样式 */
|
||||
.ne-type-items {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.kpi-checkbox-item {
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 其他指标下拉菜单样式 */
|
||||
.secondary-kpi-menu {
|
||||
background: #fff;
|
||||
padding: 4px 0;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 3px 6px -4px rgba(0,0,0,.12), 0 6px 16px 0 rgba(0,0,0,.08);
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.secondary-kpi-item {
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* 组件统一样式 */
|
||||
:deep(.ant-form-item),
|
||||
:deep(.ant-picker),
|
||||
:deep(.ant-btn) {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.ant-checkbox-wrapper) {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 添加次要指标数量的样式 */
|
||||
.secondary-count {
|
||||
margin-left: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,16 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>Perf Report</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -193,7 +193,6 @@ function fnTableSelectedRows(
|
||||
_: (string | number)[],
|
||||
rows: Record<string, string>[]
|
||||
) {
|
||||
//debugger
|
||||
tableState.selectedRowKeys = rows.map(item => item.loginId);
|
||||
// 针对单个登录账号解锁
|
||||
if (rows.length === 1) {
|
||||
|
||||
@@ -139,7 +139,7 @@ let tableColumns: any = [
|
||||
* 测试主机连接
|
||||
*/
|
||||
function fnHostTest(row: Record<string, any>) {
|
||||
if (tabState.confirmLoading || !row.addr) return;
|
||||
if (tabState.confirmLoading || !row.addr || !row.port) return;
|
||||
tabState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
testNeHost(row)
|
||||
@@ -187,19 +187,19 @@ function fnHostAuthorized(row: Record<string, any>) {
|
||||
* 表单修改网元类型
|
||||
*/
|
||||
function fnNeTypeChange(v: any, data: any) {
|
||||
const hostsLen = data.hosts.length;
|
||||
// 网元默认只含22和4100
|
||||
if (hostsLen === 3 && v !== 'UPF') {
|
||||
data.hosts.pop();
|
||||
if (modalState.from.hosts.length === 3) {
|
||||
modalState.from.hosts.pop();
|
||||
}
|
||||
const hostsLen = modalState.from.hosts.length;
|
||||
// UPF标准版本可支持5002
|
||||
if (hostsLen === 2 && v === 'UPF') {
|
||||
data.hosts.push({
|
||||
modalState.from.hosts.push({
|
||||
hostId: undefined,
|
||||
hostType: 'telnet',
|
||||
groupId: '1',
|
||||
title: 'Telnet_NE_5002',
|
||||
addr: '',
|
||||
addr: modalState.from.ip,
|
||||
port: 5002,
|
||||
user: 'admin',
|
||||
authMode: '0',
|
||||
@@ -207,6 +207,22 @@ function fnNeTypeChange(v: any, data: any) {
|
||||
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: '',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//打开新增或修改界面
|
||||
|
||||
@@ -147,6 +147,9 @@ function fnGetList() {
|
||||
case 'N3IWF':
|
||||
state.from.sbi.n3iwf_ip = item.ip;
|
||||
break;
|
||||
case 'SMSC':
|
||||
state.from.sbi.smsc_ip = item.ip;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ onMounted(() => {
|
||||
<a-input
|
||||
v-model:value="state.copyright"
|
||||
allow-clear
|
||||
:maxlength="40"
|
||||
:maxlength="128"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
@@ -101,7 +101,7 @@ onMounted(() => {
|
||||
<a-typography>
|
||||
<a-typography-paragraph>
|
||||
{{ t('views.system.setting.sysCopyrightLimitation') }}
|
||||
<a-typography-text mark>40</a-typography-text>
|
||||
<a-typography-text mark>128</a-typography-text>
|
||||
{{ t('views.system.setting.charMaxLen') }}
|
||||
</a-typography-paragraph>
|
||||
</a-typography>
|
||||
|
||||
129
src/views/system/setting/components/change-home-index.vue
Normal file
129
src/views/system/setting/components/change-home-index.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<script lang="ts" setup>
|
||||
import { Modal, message } from 'ant-design-vue/lib';
|
||||
import { onMounted, reactive, toRaw } from 'vue';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { listMenu } from '@/api/system/menu';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { getConfigKey, changeValue } from '@/api/system/config';
|
||||
const { t } = useI18n();
|
||||
|
||||
type StateType = {
|
||||
edite: boolean;
|
||||
loading: boolean;
|
||||
open: boolean;
|
||||
default: any;
|
||||
options: any;
|
||||
};
|
||||
|
||||
let state: StateType = reactive({
|
||||
edite: false,
|
||||
loading: false,
|
||||
open: false,
|
||||
default: '',
|
||||
options: [],
|
||||
});
|
||||
|
||||
/**提交保存 */
|
||||
function fnSave() {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.system.setting.homeTip'),
|
||||
onOk() {
|
||||
// 发送请求
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
state.loading = true;
|
||||
changeValue({ key: 'sys.homePage', value: state.default })
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(t('views.system.setting.saveSuccess'), 3);
|
||||
fnEdit(false);
|
||||
} else {
|
||||
message.error(res.msg, 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
state.loading = false;
|
||||
hide();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**进入可编辑 */
|
||||
function fnEdit(v: boolean) {
|
||||
state.edite = v;
|
||||
state.open = v;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
listMenu(toRaw({ status: 1 })).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
// 过滤旧前端菜单以及不是菜单类型以及路径为空
|
||||
res.data = res.data
|
||||
.filter(i => i.perms !== 'page' && i.menuType === 'M' && i.component)
|
||||
.map((item: any) => {
|
||||
state.options.push({
|
||||
label: item.menuName,
|
||||
value: item.component,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//获取当前系统设置的首页路径 111为configID
|
||||
getConfigKey('sys.homePage').then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
state.default = res.data;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24" style="margin-bottom: 30px">
|
||||
<template v-if="state.edite">
|
||||
<a-form-item :label="t('views.system.setting.home')">
|
||||
<a-select
|
||||
ref="select"
|
||||
v-model:value="state.default"
|
||||
style="width: 240px"
|
||||
:disabled="false"
|
||||
:options="state.options"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-button type="primary" @click="fnSave">
|
||||
{{ t('views.system.setting.saveSubmit') }}
|
||||
</a-button>
|
||||
<a-button style="margin-left: 10px" @click="fnEdit(false)">
|
||||
{{ t('common.cancel') }}
|
||||
</a-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-form-item :label="t('views.system.setting.home')">
|
||||
<a-select
|
||||
ref="select"
|
||||
v-model:value="state.default"
|
||||
style="width: 240px"
|
||||
:disabled="true"
|
||||
:options="state.options"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-button type="dashed" @click="fnEdit(true)">
|
||||
{{ t('common.editText') }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-typography>
|
||||
<a-typography-paragraph>
|
||||
{{ t('views.system.setting.homeInstruction') }}
|
||||
</a-typography-paragraph>
|
||||
</a-typography>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -8,6 +8,7 @@ import ChangeHelpDoc from './components/change-help-doc.vue';
|
||||
import ChangeOfficialUrl from './components/change-official-url.vue';
|
||||
import ChangeI18n from './components/change-i18n.vue';
|
||||
import SystemReset from './components/system-reset.vue';
|
||||
import ChangeHome from './components/change-home-index.vue';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
@@ -49,6 +50,10 @@ const { t } = useI18n();
|
||||
</a-divider>
|
||||
<ChangeI18n></ChangeI18n>
|
||||
</div>
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.system.setting.homeSet') }}
|
||||
</a-divider>
|
||||
<ChangeHome></ChangeHome>
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.system.setting.reset') }}
|
||||
</a-divider>
|
||||
|
||||
432
src/views/tool/iperf/index.vue
Normal file
432
src/views/tool/iperf/index.vue
Normal file
@@ -0,0 +1,432 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, onBeforeUnmount, ref } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { message, Modal } from 'ant-design-vue/lib';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import {
|
||||
RESULT_CODE_ERROR,
|
||||
RESULT_CODE_SUCCESS,
|
||||
} from '@/constants/result-constants';
|
||||
import TerminalSSHView from '@/components/TerminalSSHView/index.vue';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import { iperfI, iperfV } from '@/api/tool/iperf';
|
||||
const neInfoStore = useNeInfoStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
/**网元参数 */
|
||||
let neCascaderOptions = ref<Record<string, any>[]>([]);
|
||||
|
||||
/**状态对象 */
|
||||
let state = reactive({
|
||||
/**初始化 */
|
||||
initialized: false,
|
||||
/**运行中 */
|
||||
running: false,
|
||||
/**版本信息 */
|
||||
versionInfo: [],
|
||||
/**网元类型 */
|
||||
neType: [],
|
||||
/**数据类型 */
|
||||
dataType: 'options' as 'options' | 'command',
|
||||
/**ws参数 */
|
||||
params: {
|
||||
neType: '',
|
||||
neId: '',
|
||||
cols: 120,
|
||||
rows: 40,
|
||||
},
|
||||
/**ws数据 */
|
||||
data: {
|
||||
command: '', // 命令字符串
|
||||
client: true, // 服务端或客户端,默认服务端
|
||||
// Server or Client
|
||||
port: 5201, // 服务端口
|
||||
interval: 1, // 每次报告之间的时间间隔,单位为秒
|
||||
// Server
|
||||
oneOff: false, // 只进行一次连接
|
||||
// Client
|
||||
host: '', // 客户端连接到的服务端IP地址
|
||||
udp: false, // use UDP rather than TCP
|
||||
time: 10, // 以秒为单位的传输时间(默认为 10 秒)
|
||||
reverse: false, // 以反向模式运行(服务器发送,客户端接收)
|
||||
window: '300k', // 设置窗口大小/套接字缓冲区大小
|
||||
},
|
||||
});
|
||||
|
||||
/**连接发送 */
|
||||
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;
|
||||
if (code === RESULT_CODE_ERROR) {
|
||||
console.warn(res.msg);
|
||||
return;
|
||||
}
|
||||
if (!requestId) return;
|
||||
let lestIndex = data.lastIndexOf('unable to');
|
||||
if (lestIndex !== -1) {
|
||||
state.running = false;
|
||||
return;
|
||||
}
|
||||
lestIndex = data.lastIndexOf('iperf Done.');
|
||||
if (lestIndex !== -1) {
|
||||
state.running = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**钩子函数,界面打开初始化*/
|
||||
onMounted(() => {
|
||||
// 获取网元网元列表
|
||||
neInfoStore.fnNelist().then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
if (res.data.length > 0) {
|
||||
// 过滤不可用的网元
|
||||
for (const item of neInfoStore.getNeCascaderOptions) {
|
||||
neCascaderOptions.value.push(item);
|
||||
}
|
||||
|
||||
if (neCascaderOptions.value.length === 0) {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**钩子函数,界面关闭*/
|
||||
onBeforeUnmount(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8">
|
||||
<span>
|
||||
{{ t('views.ne.common.neType') }}:
|
||||
<a-cascader
|
||||
v-model:value="state.neType"
|
||||
:options="neCascaderOptions"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
:disabled="state.running"
|
||||
/>
|
||||
</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="command">Command</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8">
|
||||
<a-button
|
||||
@click.prevent="fnIPerf3()"
|
||||
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>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -1,272 +1,336 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive ,toRaw, onMounted } from 'vue';
|
||||
import { reactive, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { ColumnsType} from 'ant-design-vue/lib/table';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import {
|
||||
RESULT_CODE_ERROR
|
||||
} from '@/constants/result-constants';
|
||||
import { RESULT_CODE_ERROR } from '@/constants/result-constants';
|
||||
|
||||
const { t } = useI18n();
|
||||
const ws = new WS();
|
||||
|
||||
//查询数据
|
||||
/**表单查询参数 */
|
||||
let queryParams = reactive({
|
||||
pid: undefined,
|
||||
name:"",
|
||||
port:undefined,
|
||||
flag: false,
|
||||
changeTime:5000,
|
||||
name: '',
|
||||
port: undefined,
|
||||
});
|
||||
|
||||
//临时缓存
|
||||
let queryParams2 = reactive({
|
||||
pid: undefined,
|
||||
name:"",
|
||||
port:undefined,
|
||||
flag: false,
|
||||
changeTime:5000,
|
||||
})
|
||||
|
||||
//时间粒度
|
||||
let timeOptions =[
|
||||
{label:t('views.tool.ps.fastSpeed'),value:3000},
|
||||
{label:t('views.tool.ps.normalSpeed'),value:5000},
|
||||
{label:t('views.tool.ps.slowSpeed'),value:10000},
|
||||
];
|
||||
|
||||
/**钩子函数,界面打开初始化*/
|
||||
onMounted(() =>{
|
||||
fnRealTime()//建立连接
|
||||
extracted()//先立刻发送请求获取第一次的数据
|
||||
queryReset2(false)//设置定时器
|
||||
/**状态对象 */
|
||||
let state = reactive({
|
||||
/**调度器 */
|
||||
interval: null as any,
|
||||
/**刷新周期 */
|
||||
intervalTime: 5_000,
|
||||
/**查询参数 */
|
||||
query: {
|
||||
pid: undefined,
|
||||
name: '',
|
||||
port: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
/**查询按钮**/
|
||||
function queryTime(){//将表单中的数据传递给缓存数据(缓存数据自动同步请求信息中)
|
||||
queryParams2.pid=queryParams.pid;
|
||||
queryParams2.port=queryParams.port;
|
||||
queryParams2.name=queryParams.name;
|
||||
//queryParams.flag = true
|
||||
queryParams2.flag=true
|
||||
queryParams.pid=undefined;
|
||||
queryParams.name="";
|
||||
queryParams.port=undefined;
|
||||
}
|
||||
|
||||
/**重置按钮**/
|
||||
function queryReset(){
|
||||
queryParams.flag = false
|
||||
queryParams2.flag = false
|
||||
}
|
||||
|
||||
let s:any = null
|
||||
/**定时器**/
|
||||
function queryReset2(c:boolean){
|
||||
if(c){
|
||||
clearInterval(s)//清理旧定时器
|
||||
ws.close();//断开原来的wb连接
|
||||
fnRealTime();//建立新的实时数据连接
|
||||
/**接收数据后回调(成功) */
|
||||
function wsMessage(res: Record<string, any>) {
|
||||
const { code, requestId, data } = res;
|
||||
if (code === RESULT_CODE_ERROR) {
|
||||
console.warn(res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// 建联时发送请求
|
||||
if (!requestId && data.clientId) {
|
||||
fnGetList();
|
||||
return;
|
||||
}
|
||||
|
||||
// 收到消息数据
|
||||
if (requestId.startsWith('net_')) {
|
||||
// 将数据填入表格
|
||||
if (Array.isArray(data)) {
|
||||
if (tableState.loading) {
|
||||
tableState.loading = false;
|
||||
}
|
||||
tableState.data = data;
|
||||
} else {
|
||||
tableState.data = [];
|
||||
}
|
||||
}
|
||||
s = setInterval(()=>{//设置新的定时器s
|
||||
extracted();
|
||||
},queryParams.changeTime)
|
||||
}
|
||||
|
||||
/**刷新频率改变**/
|
||||
function fnRealTime2() {//时间粒度改变时触发
|
||||
queryReset2(true)//改变定时器
|
||||
}
|
||||
/**
|
||||
* 实时数据
|
||||
*/
|
||||
function fnRealTime() {
|
||||
/**实时数据*/
|
||||
function fnRealTime(reLink: boolean) {
|
||||
if (reLink) {
|
||||
ws.close();
|
||||
}
|
||||
const options: OptionsType = {
|
||||
url: '/ws',
|
||||
onmessage: wsMessage,
|
||||
onerror: wsError,
|
||||
onerror: (ev: any) => {
|
||||
// 接收数据后回调
|
||||
console.error(ev);
|
||||
},
|
||||
};
|
||||
//建立连接
|
||||
ws.connect(options);
|
||||
}
|
||||
|
||||
/**接收数据后回调(失败) */
|
||||
function wsError(ev: any) {
|
||||
// 接收数据后回调
|
||||
console.error(ev);
|
||||
/**调度器周期变更*/
|
||||
function fnIntervalChange(v: any) {
|
||||
clearInterval(state.interval);
|
||||
state.interval = null;
|
||||
const timer = parseInt(v);
|
||||
if (timer > 1_000) {
|
||||
state.intervalTime = v;
|
||||
fnGetList();
|
||||
}
|
||||
}
|
||||
|
||||
/**接收数据后回调(成功) */
|
||||
function wsMessage(res: Record<string, any>) {
|
||||
const { code, requestId, data } = res;//获取数据
|
||||
if (code === RESULT_CODE_ERROR) {
|
||||
console.warn(res.msg);
|
||||
return;
|
||||
}
|
||||
// 处理数据,组成ip : port
|
||||
let processedData: any;
|
||||
/**查询列表 */
|
||||
function fnGetList() {
|
||||
if (tableState.loading || ws.state() === -1) return;
|
||||
tableState.loading = true;
|
||||
const msg = {
|
||||
requestId: `net_${state.interval}`,
|
||||
type: 'net',
|
||||
data: state.query,
|
||||
};
|
||||
// 首发
|
||||
ws.send(msg);
|
||||
// 定时刷新数据
|
||||
state.interval = setInterval(() => {
|
||||
msg.data = JSON.parse(JSON.stringify(state.query));
|
||||
ws.send(msg);
|
||||
}, state.intervalTime);
|
||||
}
|
||||
|
||||
processedData = data.map((item:any) => {
|
||||
const localAddr = `${item.localaddr.ip} : ${item.localaddr.port}`;
|
||||
const remoteAddr = `${item.remoteaddr.ip} : ${item.remoteaddr.port}`;
|
||||
return { ...item, localAddr, remoteAddr };
|
||||
/**查询参数传入 */
|
||||
function fnQuery() {
|
||||
state.query = JSON.parse(JSON.stringify(queryParams));
|
||||
nextTick(() => {
|
||||
ws.send({
|
||||
requestId: `net_${state.interval}`,
|
||||
type: 'net',
|
||||
data: state.query,
|
||||
});
|
||||
|
||||
if (requestId) {
|
||||
tableState.data = processedData; // 将数据填入表格
|
||||
}
|
||||
}
|
||||
|
||||
function extracted() {//将表单各条件值取出,并打包在data中发送请求
|
||||
const {pid,name, port, flag} = toRaw(queryParams2)//从queryParams中取出各属性值
|
||||
let data = {} // { 'PID': PID, 'name': name, 'port': port }
|
||||
if (flag){//若flag为真则把值封装进data
|
||||
data = { 'pid': pid, 'name': name, 'port': port }
|
||||
}
|
||||
//发送请求
|
||||
ws.send({
|
||||
'requestId': 'dxxx',
|
||||
'type': 'net',
|
||||
'data': data,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TableStateType = reactive({
|
||||
loading: false,
|
||||
size: 'large',
|
||||
data: [],
|
||||
});
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
Object.assign(queryParams, {
|
||||
pid: undefined,
|
||||
name: '',
|
||||
port: undefined,
|
||||
});
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
// 重置查询条件
|
||||
Object.assign(state.query, {
|
||||
pid: undefined,
|
||||
name: '',
|
||||
port: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TableStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TableStateType = reactive({
|
||||
loading: false,
|
||||
data: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
const tableColumns: ColumnsType<any> = [
|
||||
{
|
||||
title: t('views.tool.net.PID'),
|
||||
title: t('views.tool.ps.pid'),
|
||||
dataIndex: 'pid',
|
||||
align: 'center',
|
||||
width: 50,
|
||||
sorter:{//PID排序
|
||||
compare:(a:any, b:any)=>a.pid-b.pid,
|
||||
multiple:1,
|
||||
}
|
||||
align: 'right',
|
||||
width: 100,
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => a.pid - b.pid,
|
||||
multiple: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.tool.net.name'),
|
||||
dataIndex: 'name',
|
||||
align: 'center',
|
||||
title: t('views.tool.net.proto'),
|
||||
dataIndex: 'type',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
customRender(opt) {
|
||||
return `${opt.value}`.toUpperCase();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.tool.net.localAddr'),
|
||||
dataIndex: 'localAddr',
|
||||
align: 'center',
|
||||
width: 70,
|
||||
align: 'left',
|
||||
width: 150,
|
||||
customRender(opt) {
|
||||
const { ip, port } = opt.value;
|
||||
return port !== 0 ? `${ip}:${port}` : `${ip}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.tool.net.remoteAddr'),
|
||||
dataIndex:'remoteAddr',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
dataIndex: 'remoteAddr',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
customRender(opt) {
|
||||
const { ip, port } = opt.value;
|
||||
return port !== 0 ? `${ip}:${port}` : `${ip}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.tool.net.status'),
|
||||
dataIndex: 'status',
|
||||
align: 'center',
|
||||
width: 70,
|
||||
align: 'left',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: t('views.tool.net.type'),
|
||||
dataIndex: 'type',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
title: t('views.tool.ps.name'),
|
||||
dataIndex: 'name',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
},
|
||||
});
|
||||
|
||||
/**钩子函数,界面打开初始化*/
|
||||
onMounted(() => {
|
||||
fnRealTime(false);
|
||||
});
|
||||
|
||||
/**钩子函数,界面关闭*/
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(state.interval);
|
||||
state.interval = null;
|
||||
ws.close();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<a-form :model="queryParams" name="formParams" layout="horizontal">
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="3" :md="6" :xs="12">
|
||||
<a-form-item :label="t('views.tool.ps.changeTime')" name="changeTime">
|
||||
<a-select
|
||||
v-model:value="queryParams.changeTime"
|
||||
:options="timeOptions"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
@change='fnRealTime2'
|
||||
/>
|
||||
</a-form-item></a-col>
|
||||
<a-col :lg="3" :md="6" :xs="12">
|
||||
<a-form-item :label="t('views.tool.ps.PID')" name="pid">
|
||||
<a-col :lg="4" :md="6" :xs="12">
|
||||
<a-form-item :label="t('views.tool.ps.pid')" name="pid">
|
||||
<a-input-number
|
||||
v-model:value="queryParams.pid"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
style='width: 100%'
|
||||
style="width: 100%"
|
||||
></a-input-number>
|
||||
</a-form-item></a-col>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.tool.net.name')" name="name">
|
||||
<a-form-item :label="t('views.tool.ps.name')" name="name">
|
||||
<a-input
|
||||
v-model:value="queryParams.name"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</a-form-item></a-col>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.tool.net.port')" name="port">
|
||||
<a-input
|
||||
<a-form-item :label="t('views.tool.net.port')" name="port">
|
||||
<a-input-number
|
||||
v-model:value="queryParams.port"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</a-form-item></a-col>
|
||||
<a-col :lg="3" :md="4" :xs="5">
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click='queryTime()'>
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="queryReset()" style='left: 20px;'>
|
||||
<template #icon><ClearOutlined /></template>
|
||||
{{ t('common.reset') }}
|
||||
</a-button>
|
||||
style="width: 100%"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="4" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnQuery()">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
{{ t('common.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:scroll="{ y: 'calc(100vh - 480px)' }"
|
||||
:pagination='false'
|
||||
></a-table>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-form layout="inline">
|
||||
<a-form-item :label="t('views.tool.ps.realTime')" name="realTime">
|
||||
<a-select
|
||||
v-model:value="state.intervalTime"
|
||||
:options="[
|
||||
{ label: t('views.tool.ps.realTimeHigh'), value: 3_000 },
|
||||
{ label: t('views.tool.ps.realTimeRegular'), value: 5_000 },
|
||||
{ label: t('views.tool.ps.realTimeLow'), value: 10_000 },
|
||||
{ label: t('views.tool.ps.realTimeStop'), value: -1 },
|
||||
]"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
@change="fnIntervalChange"
|
||||
style="width: 100px"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumns"
|
||||
:pagination="tablePagination"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
size="small"
|
||||
:scroll="{ x: tableColumns.length * 120 }"
|
||||
></a-table>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
|
||||
374
src/views/tool/ping/index.vue
Normal file
374
src/views/tool/ping/index.vue
Normal file
@@ -0,0 +1,374 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, onBeforeUnmount, ref, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { message } from 'ant-design-vue/lib';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import {
|
||||
RESULT_CODE_ERROR,
|
||||
RESULT_CODE_SUCCESS,
|
||||
} from '@/constants/result-constants';
|
||||
import TerminalSSHView from '@/components/TerminalSSHView/index.vue';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import { pingV } from '@/api/tool/ping';
|
||||
const neInfoStore = useNeInfoStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
/**网元参数 */
|
||||
let neCascaderOptions = ref<Record<string, any>[]>([]);
|
||||
|
||||
/**状态对象 */
|
||||
let state = reactive({
|
||||
/**初始化 */
|
||||
initialized: false,
|
||||
/**运行中 */
|
||||
running: false,
|
||||
/**版本信息 */
|
||||
versionInfo: '',
|
||||
/**网元类型 */
|
||||
neType: [],
|
||||
/**数据类型 */
|
||||
dataType: 'options' as 'options' | 'command',
|
||||
/**ws参数 */
|
||||
params: {
|
||||
neType: '',
|
||||
neId: '',
|
||||
cols: 120,
|
||||
rows: 40,
|
||||
},
|
||||
/**ws数据 */
|
||||
data: {
|
||||
command: '', // 命令字符串
|
||||
desAddr: '8.8.8.8', // dns name or ip address
|
||||
// Options
|
||||
interval: 1, // seconds between sending each packet
|
||||
ttl: 255, // define time to live
|
||||
count: 4, // <count> 次回复后停止
|
||||
size: 56, // 使用 <size> 作为要发送的数据字节数
|
||||
timeout: 10, // seconds time to wait for response
|
||||
},
|
||||
});
|
||||
|
||||
/**连接发送 */
|
||||
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;
|
||||
if (code === RESULT_CODE_ERROR) {
|
||||
console.warn(res.msg);
|
||||
return;
|
||||
}
|
||||
if (!requestId) return;
|
||||
let lestIndex = data.lastIndexOf('ping statistics ---');
|
||||
if (lestIndex !== -1) {
|
||||
state.running = false;
|
||||
return;
|
||||
}
|
||||
lestIndex = data.lastIndexOf('failure');
|
||||
if (lestIndex !== -1) {
|
||||
state.running = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**钩子函数,界面打开初始化*/
|
||||
onMounted(() => {
|
||||
// 获取网元网元列表
|
||||
neInfoStore.fnNelist().then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
if (res.data.length > 0) {
|
||||
// 过滤不可用的网元
|
||||
for (const item of neInfoStore.getNeCascaderOptions) {
|
||||
neCascaderOptions.value.push(item);
|
||||
}
|
||||
|
||||
if (neCascaderOptions.value.length === 0) {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**钩子函数,界面关闭*/
|
||||
onBeforeUnmount(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8">
|
||||
<span>
|
||||
{{ t('views.ne.common.neType') }}:
|
||||
<a-cascader
|
||||
v-model:value="state.neType"
|
||||
:options="neCascaderOptions"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
:disabled="state.running"
|
||||
/>
|
||||
</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="command">Command</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8">
|
||||
<a-button
|
||||
@click.prevent="fnPing()"
|
||||
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>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -50,6 +50,9 @@ function wsMessage(res: Record<string, any>) {
|
||||
if (requestId.startsWith('ps_')) {
|
||||
// 将数据填入表格
|
||||
if (Array.isArray(data)) {
|
||||
if (tableState.loading) {
|
||||
tableState.loading = false;
|
||||
}
|
||||
tableState.data = data;
|
||||
} else {
|
||||
tableState.data = [];
|
||||
@@ -77,6 +80,7 @@ function fnRealTime(reLink: boolean) {
|
||||
/**调度器周期变更*/
|
||||
function fnIntervalChange(v: any) {
|
||||
clearInterval(state.interval);
|
||||
state.interval = null;
|
||||
const timer = parseInt(v);
|
||||
if (timer > 1_000) {
|
||||
state.intervalTime = v;
|
||||
@@ -97,10 +101,9 @@ function fnGetList() {
|
||||
ws.send(msg);
|
||||
// 定时刷新数据
|
||||
state.interval = setInterval(() => {
|
||||
msg.data = state.query;
|
||||
msg.data = JSON.parse(JSON.stringify(state.query));
|
||||
ws.send(msg);
|
||||
}, state.intervalTime);
|
||||
tableState.loading = false;
|
||||
}
|
||||
|
||||
/**查询参数传入 */
|
||||
@@ -263,6 +266,8 @@ onMounted(() => {
|
||||
|
||||
/**钩子函数,界面关闭*/
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(state.interval);
|
||||
state.interval = null;
|
||||
ws.close();
|
||||
});
|
||||
</script>
|
||||
@@ -320,8 +325,6 @@ onBeforeUnmount(() => {
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
<div>{{ state.query }}</div>
|
||||
<div>{{ queryParams }}</div>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
|
||||
@@ -340,7 +340,7 @@ function fnTabClose(id: string) {
|
||||
|
||||
<template #rightExtra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip placement="topRight">
|
||||
<a-tooltip placement="topRight" v-if="false">
|
||||
<template #title>
|
||||
{{ t('views.tool.terminal.new') }}
|
||||
</template>
|
||||
|
||||
@@ -8,7 +8,7 @@ onMounted(() => {});
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>Perf Set</h1>
|
||||
<h1>Test Page</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
@@ -7,6 +7,7 @@ import { Modal, message } from 'ant-design-vue/lib';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { getNeFile, listNeFiles } from '@/api/tool/neFile';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import ViewDrawer from '@/views/logManage/neFile/components/ViewDrawer.vue';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import useTabsStore from '@/store/modules/tabs';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
@@ -218,16 +219,18 @@ function fnDirCD(dir: string, index?: number) {
|
||||
|
||||
/**网元类型选择对应修改 */
|
||||
function fnNeChange(keys: any, _: any) {
|
||||
if (!Array.isArray(keys)) return;
|
||||
const neType = keys[0];
|
||||
const neId = keys[1];
|
||||
// 不是同类型时需要重新加载
|
||||
if (Array.isArray(keys) && queryParams.neType !== keys[0]) {
|
||||
const neType = keys[0];
|
||||
if (queryParams.neType !== neType || queryParams.neId !== neId) {
|
||||
queryParams.neType = neType;
|
||||
queryParams.neId = keys[1];
|
||||
queryParams.neId = neId;
|
||||
if (neType === 'UPF' && tmp.value) {
|
||||
nePathArr.value = ['/tmp'];
|
||||
queryParams.search = `${neType}_${keys[1]}`;
|
||||
queryParams.search = `${neType}_${neId}`;
|
||||
} else {
|
||||
nePathArr.value = [`/tmp/omc/tcpdump/${neType.toLowerCase()}/${keys[1]}`];
|
||||
nePathArr.value = [`/tmp/omc/tcpdump/${neType.toLowerCase()}/${neId}`];
|
||||
queryParams.search = '';
|
||||
}
|
||||
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(() => {
|
||||
// 获取网元网元列表
|
||||
neInfoStore.fnNelist().then(res => {
|
||||
@@ -375,6 +397,16 @@ onMounted(() => {
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'fileName'">
|
||||
<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
|
||||
type="link"
|
||||
:loading="downLoading"
|
||||
@@ -398,6 +430,14 @@ onMounted(() => {
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 文件内容查看抽屉 -->
|
||||
<ViewDrawer
|
||||
v-model:visible="viewDrawerState.visible"
|
||||
:file-path="viewDrawerState.filePath"
|
||||
:ne-type="viewDrawerState.neType"
|
||||
:ne-id="viewDrawerState.neId"
|
||||
></ViewDrawer>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import { useRoute, useRouter } from 'vue-router';
|
||||
import { message, Modal } from 'ant-design-vue/lib';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
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 { getNeFile } from '@/api/tool/neFile';
|
||||
import { getNeDirZip, getNeFile, getNeViewFile } from '@/api/tool/neFile';
|
||||
import saveAs from 'file-saver';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
@@ -31,7 +31,7 @@ type ModalStateType = {
|
||||
/**任务编号 */
|
||||
taskCode: string;
|
||||
/**任务日志,upf标准版为空字符串 */
|
||||
logMsg: string;
|
||||
taskFiles: string[];
|
||||
/**提交表单参数 */
|
||||
data: {
|
||||
neType: string;
|
||||
@@ -65,7 +65,14 @@ type ModalStateType = {
|
||||
/**详情框是否显示 */
|
||||
visibleByView: boolean;
|
||||
/**详情框内容 */
|
||||
logMsg: string;
|
||||
viewFrom: {
|
||||
neType: string;
|
||||
neId: string;
|
||||
path: string;
|
||||
action: string;
|
||||
files: string[];
|
||||
content: string;
|
||||
};
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
@@ -81,7 +88,7 @@ let modalState: ModalStateType = reactive({
|
||||
{
|
||||
label: t('views.traceManage.pcap.execCmd2'),
|
||||
value: 'any2',
|
||||
start: 'sctp or tcp port 3030 or 8088',
|
||||
start: 'sctp or tcp port 8088 or 33030',
|
||||
stop: '',
|
||||
},
|
||||
{
|
||||
@@ -106,7 +113,14 @@ let modalState: ModalStateType = reactive({
|
||||
},
|
||||
],
|
||||
visibleByView: false,
|
||||
logMsg: '',
|
||||
viewFrom: {
|
||||
neType: '',
|
||||
neId: '',
|
||||
path: '',
|
||||
action: '',
|
||||
files: [],
|
||||
content: '',
|
||||
},
|
||||
});
|
||||
|
||||
/**表格状态类型 */
|
||||
@@ -194,7 +208,7 @@ function fnGetList() {
|
||||
cmdStart: start,
|
||||
cmdStop: stop,
|
||||
taskCode: '',
|
||||
logMsg: '',
|
||||
taskFiles: [],
|
||||
data: {
|
||||
neType: item.neType,
|
||||
neId: item.neId,
|
||||
@@ -218,7 +232,7 @@ function fnSelectCmd(id: any, option: any) {
|
||||
modalState.from[id].cmdStop = option.stop;
|
||||
// 重置任务
|
||||
modalState.from[id].taskCode = '';
|
||||
modalState.from[id].logMsg = '';
|
||||
modalState.from[id].taskFiles = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -243,6 +257,7 @@ function fnRecordStart(row?: Record<string, any>) {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
const fromArr = neIDs.map(id => modalState.from[id]);
|
||||
const reqArr = fromArr.map(from => {
|
||||
from.loading = true;
|
||||
const data = Object.assign({ cmd: from.cmdStart }, from.data);
|
||||
if (from.data.neType === 'UPF' && from.cmdStart.startsWith('pcap')) {
|
||||
return traceUPF(data);
|
||||
@@ -265,12 +280,14 @@ function fnRecordStart(row?: Record<string, any>) {
|
||||
duration: 3,
|
||||
});
|
||||
} else {
|
||||
fromArr[idx].loading = false;
|
||||
message.warning({
|
||||
content: `${resV.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
fromArr[idx].loading = false;
|
||||
message.error({
|
||||
content: t('views.traceManage.pcap.startErr', { title }),
|
||||
duration: 3,
|
||||
@@ -338,7 +355,7 @@ function fnRecordStop(row?: Record<string, any>) {
|
||||
if (res.status === 'fulfilled') {
|
||||
const resV = res.value;
|
||||
fromArr[idx].loading = false;
|
||||
fromArr[idx].logMsg = '';
|
||||
fromArr[idx].taskFiles = [];
|
||||
if (fromArr[idx].cmdStop) {
|
||||
fromArr[idx].taskCode = '';
|
||||
}
|
||||
@@ -347,7 +364,7 @@ function fnRecordStop(row?: Record<string, any>) {
|
||||
if (fromArr[idx].cmdStop) {
|
||||
fromArr[idx].taskCode = resV.data;
|
||||
} else {
|
||||
fromArr[idx].logMsg = resV.msg;
|
||||
fromArr[idx].taskFiles = resV.data;
|
||||
}
|
||||
message.success({
|
||||
content: t('views.traceManage.pcap.stopOk', { title }),
|
||||
@@ -419,10 +436,15 @@ function fnDownPCAP(row?: Record<string, any>) {
|
||||
)
|
||||
);
|
||||
} else {
|
||||
const { neType, neId } = from.data;
|
||||
const path = `/tmp/omc/tcpdump/${neType.toLowerCase()}/${neId}/${taskCode}`;
|
||||
reqArr.push(
|
||||
dumpDownload(
|
||||
Object.assign({ taskCode: taskCode, delTemp: true }, from.data)
|
||||
)
|
||||
getNeDirZip({
|
||||
neType,
|
||||
neId,
|
||||
path,
|
||||
delTemp: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -494,8 +516,42 @@ function fnBatchOper(key: string) {
|
||||
function fnModalVisibleByVive(id: string | number) {
|
||||
const from = modalState.from[id];
|
||||
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.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,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -504,7 +560,8 @@ function fnModalVisibleByVive(id: string | number) {
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.visibleByView = false;
|
||||
modalState.logMsg = '';
|
||||
modalState.viewFrom.action = '';
|
||||
modalState.viewFrom.files = [];
|
||||
}
|
||||
|
||||
/**跳转文件数据页面 */
|
||||
@@ -648,7 +705,7 @@ onMounted(() => {
|
||||
placement="topRight"
|
||||
v-if="
|
||||
!modalState.from[record.id].loading &&
|
||||
!!modalState.from[record.id].logMsg
|
||||
modalState.from[record.id].taskFiles.length > 0
|
||||
"
|
||||
>
|
||||
<template #title>
|
||||
@@ -691,21 +748,39 @@ onMounted(() => {
|
||||
<!-- 日志信息框 -->
|
||||
<ProModal
|
||||
:drag="true"
|
||||
:fullscreen="true"
|
||||
:borderDraw="true"
|
||||
:width="800"
|
||||
:visible="modalState.visibleByView"
|
||||
:footer="false"
|
||||
:maskClosable="false"
|
||||
:keyboard="false"
|
||||
:body-style="{ padding: '12px' }"
|
||||
:body-style="{ padding: '0 12px 12px' }"
|
||||
:title="t('views.traceManage.pcap.textLogMsg')"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="modalState.logMsg"
|
||||
:auto-size="{ minRows: 2, maxRows: 18 }"
|
||||
:disabled="true"
|
||||
style="color: rgba(0, 0, 0, 0.85)"
|
||||
/>
|
||||
<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
|
||||
:value="modalState.viewFrom.content"
|
||||
:auto-size="{ minRows: 2 }"
|
||||
:disabled="true"
|
||||
style="color: rgba(0, 0, 0, 0.85)"
|
||||
/>
|
||||
</a-spin>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</ProModal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, computed, unref, onUpdated, watchEffect } from 'vue';
|
||||
import { reactive, ref, computed, unref, watchEffect } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
/**列表高度 */
|
||||
@@ -122,8 +122,6 @@ const onScroll = (e: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
onUpdated(() => {});
|
||||
|
||||
watchEffect(() => {
|
||||
clientData.value.forEach((_, index) => {
|
||||
const currentIndex = state.start + index;
|
||||
@@ -136,10 +134,6 @@ watchEffect(() => {
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const tableState = reactive({
|
||||
selected: false,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -167,13 +161,13 @@ const tableState = reactive({
|
||||
item.number === props.selectedFrame
|
||||
? 'blue'
|
||||
: item.bg
|
||||
? `#${item.bg.toString(16).padStart(6, '0')}`
|
||||
? `#${Number(item.bg).toString(16).padStart(6, '0')}`
|
||||
: '',
|
||||
color:
|
||||
item.number === props.selectedFrame
|
||||
? 'white'
|
||||
: item.fg
|
||||
? `#${item.fg.toString(16).padStart(6, '0')}`
|
||||
? `#${Number(item.fg).toString(16).padStart(6, '0')}`
|
||||
: '',
|
||||
}"
|
||||
@click="onSelectedFrame(item.number)"
|
||||
@@ -219,7 +213,7 @@ const tableState = reactive({
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-top: 1px #f0f0f0 solid;
|
||||
border-top: 1px #f0f0f0 solid;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tbody-item {
|
||||
|
||||
@@ -1,16 +1,522 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw } from 'vue';
|
||||
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { message, Modal } from 'ant-design-vue/lib';
|
||||
import DissectionTree from '../tshark/components/DissectionTree.vue';
|
||||
import DissectionDump from '../tshark/components/DissectionDump.vue';
|
||||
import PacketTable from '../tshark/components/PacketTable.vue';
|
||||
import {
|
||||
RESULT_CODE_ERROR,
|
||||
RESULT_CODE_SUCCESS,
|
||||
} from '@/constants/result-constants';
|
||||
import { filePullTask } from '@/api/trace/task';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import saveAs from 'file-saver';
|
||||
import {
|
||||
packetDevices,
|
||||
packetStart,
|
||||
packetStop,
|
||||
packetFilter,
|
||||
packetKeep,
|
||||
} from '@/api/trace/packet';
|
||||
const ws = new WS();
|
||||
const { t } = useI18n();
|
||||
|
||||
onMounted(() => {});
|
||||
const NO_SELECTION = { id: '', idx: 0, start: 0, length: 0 };
|
||||
|
||||
type StateType = {
|
||||
/**网卡设备列表 */
|
||||
devices: { id: string; label: string; children: any[] }[];
|
||||
/**初始化 */
|
||||
initialized: boolean;
|
||||
/**保活调度器 */
|
||||
keepTimer: any;
|
||||
/**任务 */
|
||||
task: {
|
||||
taskNo: string;
|
||||
device: string;
|
||||
filter: string;
|
||||
outputPCAP: boolean;
|
||||
};
|
||||
/**字段 */
|
||||
columns: string[];
|
||||
|
||||
/**过滤条件 */
|
||||
filter: string;
|
||||
/**过滤条件错误信息 */
|
||||
filterError: string | null;
|
||||
|
||||
/**当前选中的帧编号 */
|
||||
selectedFrame: number;
|
||||
/**当前选中的帧数据 */
|
||||
packetFrame: { tree: any[]; data_sources: any[] };
|
||||
/**pcap包帧数据 */
|
||||
packetFrameTreeMap: Map<string, any> | null;
|
||||
/**当前选中的帧数据 */
|
||||
selectedTree: typeof NO_SELECTION;
|
||||
/**选择帧的Dump数据标签 */
|
||||
selectedDataSourceIndex: number;
|
||||
|
||||
/**包总数 */
|
||||
totalPackets: number;
|
||||
/**包数据 */
|
||||
packetList: any[];
|
||||
};
|
||||
|
||||
const state = reactive<StateType>({
|
||||
devices: [],
|
||||
initialized: false,
|
||||
keepTimer: null,
|
||||
task: {
|
||||
taskNo: 'laYlTbq',
|
||||
device: '192.168.5.58',
|
||||
filter: 'tcp and (port 33030 or 8080)',
|
||||
outputPCAP: false,
|
||||
},
|
||||
columns: [
|
||||
'No.',
|
||||
'Time',
|
||||
'Source',
|
||||
'Destination',
|
||||
'Protocol',
|
||||
'Length',
|
||||
'Info',
|
||||
],
|
||||
filter: 'tcp and (port 33030 or 8080)',
|
||||
filterError: null,
|
||||
selectedFrame: 1,
|
||||
/**当前选中的帧数据 */
|
||||
packetFrame: { tree: [], data_sources: [] },
|
||||
packetFrameTreeMap: null, // 注意:Map 需要额外处理
|
||||
selectedTree: NO_SELECTION, // NO_SELECTION 需要定义
|
||||
/**选择帧的Dump数据标签 */
|
||||
selectedDataSourceIndex: 0,
|
||||
// 包数据
|
||||
totalPackets: 0,
|
||||
packetList: [],
|
||||
});
|
||||
|
||||
/**清除帧数据和报文信息状态 */
|
||||
function fnReset() {
|
||||
state.initialized = false;
|
||||
// 选择帧的数据
|
||||
state.selectedFrame = 0;
|
||||
state.packetFrame = { tree: [], data_sources: [] };
|
||||
state.packetFrameTreeMap = null;
|
||||
state.selectedTree = NO_SELECTION;
|
||||
state.selectedDataSourceIndex = 0;
|
||||
// 过滤条件
|
||||
state.filter = 'tcp and (port 33030 or 8080)';
|
||||
state.filterError = null;
|
||||
// 包数据
|
||||
state.totalPackets = 0;
|
||||
state.packetList = [];
|
||||
}
|
||||
|
||||
/**解析帧数据为简单结构 */
|
||||
function parseFrameTree(id: string, node: Record<string, any>) {
|
||||
let map = new Map();
|
||||
|
||||
if (node.tree && node.tree.length > 0) {
|
||||
for (let i = 0; i < node.tree.length; i++) {
|
||||
const subMap = parseFrameTree(`${id}-${i}`, node.tree[i]);
|
||||
subMap.forEach((value, key) => {
|
||||
map.set(key, value);
|
||||
});
|
||||
}
|
||||
} else if (node.length > 0) {
|
||||
map.set(id, {
|
||||
id: id,
|
||||
idx: node.data_source_idx,
|
||||
start: node.start,
|
||||
length: node.length,
|
||||
});
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**帧数据点击选中 */
|
||||
function handleSelectedTreeEntry(e: any) {
|
||||
console.log('fnSelectedTreeEntry', e);
|
||||
state.selectedTree = e;
|
||||
}
|
||||
/**报文数据点击选中 */
|
||||
function handleSelectedFindSelection(src_idx: number, pos: number) {
|
||||
console.log('fnSelectedFindSelection', pos);
|
||||
if (state.packetFrameTreeMap == null) return;
|
||||
// find the smallest one
|
||||
let current = null;
|
||||
for (let [k, pp] of state.packetFrameTreeMap) {
|
||||
if (pp.idx !== src_idx) continue;
|
||||
|
||||
if (pos >= pp.start && pos <= pp.start + pp.length) {
|
||||
if (
|
||||
current != null &&
|
||||
state.packetFrameTreeMap.get(current).length > pp.length
|
||||
) {
|
||||
current = k;
|
||||
} else {
|
||||
current = k;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (current != null) {
|
||||
state.selectedTree = state.packetFrameTreeMap.get(current);
|
||||
}
|
||||
}
|
||||
|
||||
/**包数据表点击选中 */
|
||||
function handleSelectedFrame(num: number) {
|
||||
console.log('fnSelectedFrame', num, state.totalPackets);
|
||||
const packet = state.packetList.find((v: any) => v.number === num);
|
||||
if (!packet) return;
|
||||
const packetFrame = packet.frame;
|
||||
state.selectedFrame = packet.number;
|
||||
state.packetFrame = packetFrame;
|
||||
state.packetFrameTreeMap = parseFrameTree('root', packetFrame);
|
||||
state.selectedTree = NO_SELECTION;
|
||||
state.selectedDataSourceIndex = 0;
|
||||
}
|
||||
/**包数据表滚动底部加载 */
|
||||
function handleScrollBottom(index: any) {
|
||||
console.log('handleScrollBottom', index);
|
||||
}
|
||||
|
||||
/**开始跟踪 */
|
||||
function fnStart() {
|
||||
// state.task.taskNo = 'laYlTbq';
|
||||
state.task.taskNo = Number(Date.now()).toString(16);
|
||||
state.task.outputPCAP = false;
|
||||
packetStart(state.task).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
fnReset();
|
||||
fnWS();
|
||||
} else {
|
||||
message.error(t('common.operateErr'), 3);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**停止跟踪 */
|
||||
function fnStop() {
|
||||
packetStop(state.task.taskNo).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
ws.close();
|
||||
state.initialized = false;
|
||||
state.filter = '';
|
||||
state.filterError = null;
|
||||
} else {
|
||||
message.warning(res.msg, 3);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**跟踪数据表过滤 */
|
||||
function handleFilterFrames() {
|
||||
packetFilter(state.task.taskNo, state.filter).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
state.task.filter = state.filter;
|
||||
} else {
|
||||
state.filterError = res.msg;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**开始跟踪 */
|
||||
function fnDevice(v: string) {
|
||||
state.task.device = v;
|
||||
}
|
||||
|
||||
/**下载触发等待 */
|
||||
let downLoading = ref<boolean>(false);
|
||||
|
||||
/**信息文件下载 */
|
||||
function fnDownloadPCAP() {
|
||||
if (downLoading.value) return;
|
||||
const fileName = `trace_packet_${state.task.taskNo}.pcap`;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.logManage.neFile.downTip', { fileName }),
|
||||
onOk() {
|
||||
downLoading.value = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
filePullTask(state.task.taskNo)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', {
|
||||
msg: t('common.downloadText'),
|
||||
}),
|
||||
duration: 2,
|
||||
});
|
||||
saveAs(res.data, `${fileName}`);
|
||||
} else {
|
||||
message.error({
|
||||
content: t('views.logManage.neFile.downTipErr'),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
downLoading.value = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**接收数据后回调 */
|
||||
function wsMessage(res: Record<string, any>) {
|
||||
const { code, requestId, data } = res;
|
||||
if (code === RESULT_CODE_ERROR) {
|
||||
console.warn(res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// 建联时发送请求
|
||||
if (!requestId && data.clientId) {
|
||||
state.initialized = true;
|
||||
state.keepTimer = setInterval(() => {
|
||||
packetKeep(state.task.taskNo, 120);
|
||||
}, 90 * 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
// 订阅组信息
|
||||
if (!data?.groupId) {
|
||||
return;
|
||||
}
|
||||
if (data.groupId === `4_${state.task.taskNo}`) {
|
||||
const packetData = data.data;
|
||||
state.totalPackets = packetData.number;
|
||||
state.packetList.push(packetData);
|
||||
}
|
||||
}
|
||||
|
||||
/**建立WS连接 */
|
||||
function fnWS() {
|
||||
const options: OptionsType = {
|
||||
url: '/ws',
|
||||
params: {
|
||||
/**订阅通道组
|
||||
*
|
||||
* 信令跟踪Packet (GroupID:4_taskNo)
|
||||
*/
|
||||
subGroupID: `4_${state.task.taskNo}`,
|
||||
},
|
||||
onmessage: wsMessage,
|
||||
onerror: (ev: any) => {
|
||||
// 接收数据后回调
|
||||
console.error(ev);
|
||||
},
|
||||
};
|
||||
//建立连接
|
||||
ws.connect(options);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
packetDevices().then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
state.devices = res.data;
|
||||
if (res.data.length === 0) return;
|
||||
state.task.device = res.data[0].id;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(state.keepTimer);
|
||||
state.keepTimer = null;
|
||||
if (ws.state() === WebSocket.OPEN) ws.close();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>JS</h1>
|
||||
<a-card :bordered="false" :body-style="{ padding: '12px' }">
|
||||
<div class="toolbar">
|
||||
<a-space :size="8" class="toolbar-oper">
|
||||
<a-dropdown-button
|
||||
type="primary"
|
||||
:disabled="state.initialized"
|
||||
@click="fnStart"
|
||||
>
|
||||
<PlayCircleOutlined />
|
||||
Start Trace
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
@click="({ key }:any) => fnDevice(key)"
|
||||
:selectedKeys="[state.task.device]"
|
||||
>
|
||||
<a-menu-item v-for="v in state.devices" :key="v.id">
|
||||
<a-popover placement="rightTop" trigger="hover">
|
||||
<template #content>
|
||||
<div v-for="c in v.children">{{ c.id }}</div>
|
||||
</template>
|
||||
<template #title>
|
||||
<span>IP Address</span>
|
||||
</template>
|
||||
<div>{{ v.label }}</div>
|
||||
</a-popover>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
<template #icon><DownOutlined /></template>
|
||||
</a-dropdown-button>
|
||||
|
||||
<a-button danger @click.prevent="fnStop()" v-if="state.initialized">
|
||||
<template #icon><CloseCircleOutlined /></template>
|
||||
Stop Trace
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
:loading="downLoading"
|
||||
@click.prevent="fnDownloadPCAP()"
|
||||
v-if="state.task.outputPCAP"
|
||||
>
|
||||
<template #icon><DownloadOutlined /></template>
|
||||
{{ t('common.downloadText') }}
|
||||
</a-button>
|
||||
<a-tag
|
||||
color="green"
|
||||
v-show="!!state.task.filter && state.initialized"
|
||||
>
|
||||
{{ state.task.filter }}
|
||||
</a-tag>
|
||||
</a-space>
|
||||
|
||||
<a-space :size="8" class="toolbar-info" v-show="state.initialized">
|
||||
<span>
|
||||
{{ t('views.traceManage.task.traceId') }}:
|
||||
<strong>{{ state.task.taskNo }}</strong>
|
||||
</span>
|
||||
<span> Packets: {{ state.totalPackets }} </span>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<!-- 包数据表过滤 -->
|
||||
<a-input-group compact v-show="state.initialized">
|
||||
<a-input
|
||||
v-model:value="state.filter"
|
||||
placeholder="display filter, example: tcp"
|
||||
:allow-clear="true"
|
||||
style="width: calc(100% - 100px)"
|
||||
@pressEnter="handleFilterFrames"
|
||||
>
|
||||
<template #prefix>
|
||||
<FilterOutlined />
|
||||
</template>
|
||||
</a-input>
|
||||
<a-button
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
style="width: 100px"
|
||||
@click="handleFilterFrames"
|
||||
>
|
||||
Filter
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<a-alert
|
||||
:message="state.filterError"
|
||||
type="error"
|
||||
v-if="state.filterError != null"
|
||||
/>
|
||||
|
||||
<!-- 包数据表 -->
|
||||
<PacketTable
|
||||
:columns="state.columns"
|
||||
:data="state.packetList"
|
||||
:selectedFrame="state.selectedFrame"
|
||||
:onSelectedFrame="handleSelectedFrame"
|
||||
:onScrollBottom="handleScrollBottom"
|
||||
></PacketTable>
|
||||
|
||||
<a-row :gutter="20">
|
||||
<a-col :lg="12" :md="12" :xs="24" class="tree">
|
||||
<!-- 帧数据 -->
|
||||
<DissectionTree
|
||||
id="root"
|
||||
:select="handleSelectedTreeEntry"
|
||||
:selected="state.selectedTree"
|
||||
:tree="state.packetFrame.tree"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24" class="dump">
|
||||
<!-- 报文数据 -->
|
||||
<a-tabs
|
||||
v-model:activeKey="state.selectedDataSourceIndex"
|
||||
:tab-bar-gutter="16"
|
||||
:tab-bar-style="{ marginBottom: '8px' }"
|
||||
>
|
||||
<a-tab-pane
|
||||
:key="idx"
|
||||
:tab="v.name"
|
||||
v-for="(v, idx) in state.packetFrame.data_sources"
|
||||
style="overflow: auto"
|
||||
>
|
||||
<DissectionDump
|
||||
:base64="v.data"
|
||||
:select="(pos:number)=>handleSelectedFindSelection(idx, pos)"
|
||||
:selected="
|
||||
idx === state.selectedTree.idx
|
||||
? state.selectedTree
|
||||
: NO_SELECTION
|
||||
"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style scoped>
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.toolbar-oper {
|
||||
flex: 1;
|
||||
}
|
||||
.toolbar-info {
|
||||
text-align: right;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.summary-item > span:first-child {
|
||||
font-weight: 600;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.tree {
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.5rem;
|
||||
padding-bottom: 0.75rem;
|
||||
padding-top: 0.75rem;
|
||||
white-space: nowrap;
|
||||
overflow-y: auto;
|
||||
user-select: none;
|
||||
height: 100%;
|
||||
}
|
||||
.tree > ul.tree {
|
||||
min-height: 15rem;
|
||||
}
|
||||
|
||||
.dump {
|
||||
padding-bottom: 0.75rem;
|
||||
padding-top: 0.75rem;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
}
|
||||
.dump .ant-tabs-tabpane {
|
||||
min-height: calc(15rem - 56px);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user