Merge branch 'main' into multi-tenant
This commit is contained in:
@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC"
|
||||
VITE_APP_CODE = "OMC"
|
||||
|
||||
# 应用版本
|
||||
VITE_APP_VERSION = "2.240927"
|
||||
VITE_APP_VERSION = "2.241028"
|
||||
|
||||
# 接口基础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.241028"
|
||||
|
||||
# 接口基础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,
|
||||
});
|
||||
}
|
||||
@@ -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',
|
||||
});
|
||||
},
|
||||
|
||||
@@ -686,11 +686,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",
|
||||
@@ -1080,7 +1081,25 @@ 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:{
|
||||
"changeLine":"Change to Line Charts",
|
||||
"changeBar":"Change to Bar Charts",
|
||||
"chooseShowMetrics":"Select the metric you want to display",
|
||||
"chooseMetrics":"Select an indicator",
|
||||
},
|
||||
},
|
||||
traceManage: {
|
||||
analysis: {
|
||||
@@ -1119,8 +1138,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",
|
||||
@@ -1296,7 +1315,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",
|
||||
@@ -2160,7 +2179,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",
|
||||
@@ -2169,13 +2188,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",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -687,10 +687,11 @@ export default {
|
||||
port: "端口",
|
||||
portPlease: "请正确填写主机端口号",
|
||||
user: "用户名",
|
||||
userPlease: "请正确填写主机登录用户",
|
||||
userPlease: "请正确填写主机用户",
|
||||
database: "数据库",
|
||||
authMode: "认证模式",
|
||||
password: "密码",
|
||||
passwordPlease: "请正确填写主机登录密码",
|
||||
passwordPlease: "请正确填写主机密码",
|
||||
privateKey: "私钥",
|
||||
privateKeyPlease: "请正确填写私钥字符内容 ~/.ssh/id_rsa",
|
||||
passPhrase: "私钥密码",
|
||||
@@ -1080,7 +1081,26 @@ export default {
|
||||
element:'元素',
|
||||
granularity:'颗粒度',
|
||||
unit:'单位',
|
||||
}
|
||||
},
|
||||
kpiKeyTarget:{
|
||||
"fullWidthLayout":"全宽布局",
|
||||
"twoColumnLayout":"两列布局",
|
||||
"saveLayout": "保存布局",
|
||||
"restoreSaved": "恢复布局",
|
||||
"saveSuccess": " {name} 保存成功",
|
||||
"restoreSavedSuccess": " {name} 恢复成功",
|
||||
"noSavedLayout": "没有找到保存的布局 {name}",
|
||||
"layout1": "布局1",
|
||||
"layout2": "布局2",
|
||||
"layout3": "布局3"
|
||||
},
|
||||
kpiOverView:{
|
||||
"changeLine":"切换为折线图",
|
||||
"changeBar":"切换为柱状图",
|
||||
"chooseShowMetrics":"选择需要显示的指标",
|
||||
"chooseMetrics":"选择指标",
|
||||
|
||||
},
|
||||
},
|
||||
traceManage: {
|
||||
analysis: {
|
||||
@@ -1119,8 +1139,8 @@ export default {
|
||||
fileUPFTip: 'UPF内部抓包分析包',
|
||||
textStart: "开始",
|
||||
textStop: "停止",
|
||||
textLog: "日志",
|
||||
textLogMsg: "日志信息",
|
||||
textLog: "日志文件",
|
||||
textLogMsg: "日志文件信息",
|
||||
textDown: "下载",
|
||||
downTip: "确认要下载 {title} 抓包数据文件吗?",
|
||||
downOk: "{title} 文件下载完成",
|
||||
@@ -1135,7 +1155,7 @@ export default {
|
||||
stopNotRun: "{title} 任务未运行",
|
||||
},
|
||||
task: {
|
||||
traceId: '跟踪编号',
|
||||
traceId: '跟踪编号',
|
||||
trackType: '跟踪类型',
|
||||
trackTypePlease: '请选择跟踪类型',
|
||||
creater: '创建人',
|
||||
@@ -1296,7 +1316,7 @@ export default {
|
||||
},
|
||||
exportFile:{
|
||||
fileName:'文件来源',
|
||||
downTip: "确认下载文件名为 【{fileName}】 文件?",
|
||||
downTip: "确认下载文件名为 【{fileName}】 文件?",
|
||||
downTipErr: "文件获取失败",
|
||||
deleteTip: "确认删除文件名为 【{fileName}】 文件?",
|
||||
deleteTipErr: "文件删除失败",
|
||||
@@ -2153,7 +2173,7 @@ export default {
|
||||
hostSelectMore: "加载更多 {num}",
|
||||
hostSelectHeader: "主机列表",
|
||||
},
|
||||
ps:{
|
||||
ps:{
|
||||
realTimeHigh:"高",
|
||||
realTimeLow:"低",
|
||||
realTimeRegular:"常规",
|
||||
@@ -2169,13 +2189,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>
|
||||
@@ -2,8 +2,7 @@
|
||||
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 { reactive, ref, onMounted, onBeforeUnmount, markRaw } from 'vue';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { TooltipComponent } from 'echarts/components';
|
||||
import { GaugeChart } from 'echarts/charts';
|
||||
@@ -15,6 +14,8 @@ 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();
|
||||
@@ -52,44 +53,55 @@ let indexColor = ref<DictType[]>([
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('views.index.object'),
|
||||
dataIndex: 'name',
|
||||
align: 'center',
|
||||
key: 'status',
|
||||
dataIndex: 'neName',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
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');
|
||||
},
|
||||
dataIndex: 'serverState',
|
||||
align: 'left',
|
||||
key: 'status',
|
||||
},
|
||||
{
|
||||
title: t('views.index.reloadTime'),
|
||||
dataIndex: 'refresh',
|
||||
align: 'center',
|
||||
dataIndex: 'serverState',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
if (opt.value?.refreshTime) return parseDateToStr(opt.value?.refreshTime);
|
||||
return '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.index.version'),
|
||||
dataIndex: 'version',
|
||||
align: 'center',
|
||||
dataIndex: 'serverState',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
return opt.value?.version || '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.index.serialNum'),
|
||||
dataIndex: 'serialNum',
|
||||
align: 'center',
|
||||
dataIndex: 'serverState',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
return opt.value?.sn || '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.index.expiryDate'),
|
||||
dataIndex: 'expiryDate',
|
||||
align: 'center',
|
||||
dataIndex: 'serverState',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
return opt.value?.expire || '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.index.ipAddress'),
|
||||
dataIndex: 'ipAddress',
|
||||
key: 'groupName',
|
||||
align: 'center',
|
||||
dataIndex: 'serverState',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
return opt.value?.neIP || '-';
|
||||
},
|
||||
},
|
||||
];
|
||||
/**表格状态类型 */
|
||||
@@ -130,12 +142,8 @@ type nfStateType = {
|
||||
hostName: string;
|
||||
/**操作系统信息 */
|
||||
osInfo: string;
|
||||
/**数据库信息 */
|
||||
dbInfo: string;
|
||||
/**IP地址 */
|
||||
ipAddress: string;
|
||||
/**端口 */
|
||||
port: number;
|
||||
/**版本 */
|
||||
version: string;
|
||||
/**CPU利用率 */
|
||||
@@ -153,11 +161,8 @@ type nfStateType = {
|
||||
/**网元详细信息 */
|
||||
let pronInfo: nfStateType = reactive({
|
||||
hostName: '5gc',
|
||||
osInfo:
|
||||
'Linux 5gc 4.15.0-112-generic #113-Ubuntu SMP Thu Jul 9 23:41:39 UTC 2020 x86_64 GNU/Linux',
|
||||
dbInfo: 'adb v1.0.1',
|
||||
osInfo: 'Linux 5gc 4.15.0-112-generic 2020 x86_64 GNU/Linux',
|
||||
ipAddress: '-',
|
||||
port: 3030,
|
||||
version: '-',
|
||||
cpuUse: '-',
|
||||
memoryUse: '-',
|
||||
@@ -170,8 +175,8 @@ let pronInfo: nfStateType = reactive({
|
||||
function fnGetList(one: boolean) {
|
||||
if (tableState.loading) return;
|
||||
one && (tableState.loading = true);
|
||||
listMain().then(res => {
|
||||
tableState.data = res;
|
||||
listAllNeInfo({ bandStatus: true }).then(res => {
|
||||
tableState.data = res.data;
|
||||
tableState.loading = false;
|
||||
var rightNum = 0;
|
||||
var errorNum = 0;
|
||||
@@ -254,17 +259,14 @@ const closeDrawer = () => {
|
||||
function rowClick(record: any, index: any) {
|
||||
return {
|
||||
onClick: (event: any) => {
|
||||
if (
|
||||
toRaw(record).status == '异常' ||
|
||||
toRaw(record).status == 'Abnormal'
|
||||
) {
|
||||
let pronData = JSON.parse(JSON.stringify(record.serverState));
|
||||
if (!pronData.online) {
|
||||
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;
|
||||
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;
|
||||
@@ -274,19 +276,17 @@ function rowClick(record: any, index: any) {
|
||||
|
||||
//渲染详细信息
|
||||
pronInfo = {
|
||||
hostName: pronData.hostName,
|
||||
osInfo: pronData.osInfo,
|
||||
dbInfo: pronData.dbInfo,
|
||||
ipAddress: pronData.ipAddress,
|
||||
port: pronData.port,
|
||||
hostName: pronData.hostname,
|
||||
osInfo: pronData.os,
|
||||
ipAddress: pronData.neIP,
|
||||
version: pronData.version,
|
||||
cpuUse:
|
||||
pronData.name +
|
||||
pronData.neName +
|
||||
':' +
|
||||
pronData.cpuUsage?.nfCpuUsage / 100 +
|
||||
pronData.cpu?.nfCpuUsage / 100 +
|
||||
'%; ' +
|
||||
'SYS:' +
|
||||
pronData.cpuUsage?.sysCpuUsage / 100 +
|
||||
pronData.cpu?.sysCpuUsage / 100 +
|
||||
'%',
|
||||
memoryUse:
|
||||
'Total:' +
|
||||
@@ -299,8 +299,8 @@ function rowClick(record: any, index: any) {
|
||||
sysMemUsageInMB +
|
||||
'MB',
|
||||
capability: pronData.capability,
|
||||
serialNum: pronData.serialNum,
|
||||
expiryDate: pronData.expiryDate,
|
||||
serialNum: pronData.sn,
|
||||
expiryDate: pronData.expire,
|
||||
};
|
||||
}
|
||||
visible.value = true;
|
||||
@@ -352,15 +352,9 @@ onBeforeUnmount(() => {
|
||||
<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>
|
||||
@@ -370,9 +364,6 @@ onBeforeUnmount(() => {
|
||||
<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>
|
||||
@@ -398,11 +389,11 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
<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 v-if="record.serverState.online">
|
||||
<a-tag color="blue">{{ t('views.index.normal') }}</a-tag>
|
||||
</div>
|
||||
<div v-else>
|
||||
<a-tag color="pink">{{ record.name }}</a-tag>
|
||||
<a-tag color="pink">{{ t('views.index.abnormal') }}</a-tag>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
@@ -410,10 +401,7 @@ onBeforeUnmount(() => {
|
||||
</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>
|
||||
<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
|
||||
|
||||
@@ -425,8 +425,6 @@ function fnGetList(pageNum?: number) {
|
||||
(queryParams.pageNum - 1) * tablePagination.pageSize &&
|
||||
queryParams.pageNum !== 1
|
||||
) {
|
||||
debugger;
|
||||
|
||||
tableState.loading = false;
|
||||
fnGetList(queryParams.pageNum - 1);
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -31,12 +31,6 @@ export default function useWS() {
|
||||
ws.send(data);
|
||||
}
|
||||
|
||||
/**接收数据后回调 */
|
||||
function wsError(ev: any) {
|
||||
// 接收数据后回调
|
||||
console.error(ev);
|
||||
}
|
||||
|
||||
/**接收数据后回调 */
|
||||
function wsMessage(res: Record<string, any>) {
|
||||
//console.log(res);
|
||||
@@ -55,7 +49,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);
|
||||
}
|
||||
@@ -74,22 +68,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;
|
||||
}
|
||||
// 订阅组信息
|
||||
@@ -104,7 +89,7 @@ export default function useWS() {
|
||||
}
|
||||
break;
|
||||
// AMF_UE会话事件
|
||||
case '1010_001':
|
||||
case '1010':
|
||||
if (data.data) {
|
||||
queue.add(() => eventItemParseAndPush('amf_ue', data.data));
|
||||
}
|
||||
@@ -125,27 +110,20 @@ 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}`,
|
||||
type: 'upf_tf',
|
||||
data: {
|
||||
neType: 'UPF',
|
||||
neId: upfWhoId.value,
|
||||
day: day,
|
||||
neId: '001',
|
||||
day: Number(day),
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -154,7 +132,7 @@ export default function useWS() {
|
||||
function userActivitySend() {
|
||||
// AMF_UE会话事件
|
||||
ws.send({
|
||||
requestId: 'amf_1010_001',
|
||||
requestId: 'amf_1010',
|
||||
type: 'amf_ue',
|
||||
data: {
|
||||
neType: 'AMF',
|
||||
@@ -211,14 +189,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_' + rmUid + ',1010_001,1011_001,1005_001',
|
||||
},
|
||||
onmessage: wsMessage,
|
||||
onerror: wsError,
|
||||
onerror: (ev: any) => {
|
||||
console.error(ev);
|
||||
},
|
||||
};
|
||||
ws.connect(options);
|
||||
}
|
||||
|
||||
@@ -170,19 +170,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);
|
||||
|
||||
@@ -203,9 +205,13 @@ function fnSelectNe(value: any, option: any) {
|
||||
queryParams.neRealId = value;
|
||||
upfWhoId.value = value;
|
||||
reSendUPF(option.rmUid);
|
||||
upfTotalFlow.value.map((item: any) => {
|
||||
item.requestFlag = false;
|
||||
});
|
||||
// upfTotalFlow.value.map((item: any) => {
|
||||
// item.requestFlag = false;
|
||||
// });
|
||||
|
||||
for (var key in upfTotalFlow.value) {
|
||||
upfTotalFlow.value[key].requestFlag = false;
|
||||
}
|
||||
loadData();
|
||||
}
|
||||
|
||||
@@ -483,12 +489,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;
|
||||
}
|
||||
"
|
||||
>
|
||||
@@ -508,14 +514,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>
|
||||
|
||||
@@ -611,18 +611,17 @@ function fnShowSet() {
|
||||
});
|
||||
}
|
||||
|
||||
// key替换中文title
|
||||
// key替换中文title
|
||||
function mapKeysWithReduce(data: any[], titleMapping: Record<string, string>) {
|
||||
return data.map((item: any) => {
|
||||
if (typeof item !== 'object' || item === null) {
|
||||
return item;
|
||||
return item; // 如果不是对象,直接返回原值
|
||||
}
|
||||
return Object.keys(item).reduce((newItem: Record<string, any>, key: string) => {
|
||||
const title = titleMapping[key] || key;
|
||||
newItem[title] = item[key];
|
||||
return newItem;
|
||||
}, {});
|
||||
}, {}); // 确保初始值是一个空对象
|
||||
});
|
||||
}
|
||||
|
||||
@@ -677,6 +676,7 @@ function fnExportAll() {
|
||||
|
||||
return filteredObj;
|
||||
});
|
||||
console.log(mappArr);
|
||||
|
||||
res.data = mapKeysWithReduce(res.data, mappArr);
|
||||
writeSheet(res.data, 'alarm', sortData).then(fileBlob => {
|
||||
|
||||
@@ -426,7 +426,6 @@ function fnCancelConfirm() {
|
||||
});
|
||||
}
|
||||
|
||||
// key替换中文title
|
||||
// key替换中文title
|
||||
function mapKeysWithReduce(data: any[], titleMapping: Record<string, string>) {
|
||||
return data.map((item: any) => {
|
||||
|
||||
@@ -80,7 +80,6 @@ function fnDesign(container: HTMLElement | undefined, option: EChartsOption) {
|
||||
|
||||
//渲染速率图
|
||||
function handleRanderChart() {
|
||||
console.log(upfFlowData.value);
|
||||
const { lineXTime, lineYUp, lineYDown } = upfFlowData.value;
|
||||
var yAxisSeries: any = [
|
||||
{
|
||||
@@ -258,7 +257,6 @@ function fnGetInitData() {
|
||||
|
||||
for (const item of res.data) {
|
||||
if (item.rmUID === selectRmUid.value) {
|
||||
console.log(item);
|
||||
upfFlowParse(item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: '',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -517,7 +517,7 @@ function fnExportList(type: string) {
|
||||
if (!neId) return;
|
||||
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
exportUDMAuth({ ...queryParams, ...{ 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);
|
||||
@@ -555,6 +555,9 @@ function fnLoadData() {
|
||||
fnQueryReset();
|
||||
}, timerS * 1000);
|
||||
} else {
|
||||
modalState.loadDataLoading = false;
|
||||
tableState.loading = false; // 表格loading
|
||||
fnQueryReset();
|
||||
message.error({
|
||||
content: t('common.getInfoFail'),
|
||||
duration: 3,
|
||||
@@ -734,6 +737,7 @@ onMounted(() => {
|
||||
v-model:value="queryParams.neId"
|
||||
:options="neOtions"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
@change="fnGetList(1)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
@@ -814,8 +818,8 @@ onMounted(() => {
|
||||
<template #icon><ImportOutlined /></template>
|
||||
{{ t('views.neUser.auth.import') }}
|
||||
</a-button>
|
||||
<!--
|
||||
<a-popconfirm
|
||||
|
||||
<!-- <a-popconfirm
|
||||
:title="t('views.neUser.auth.exportConfirm')"
|
||||
placement="topRight"
|
||||
ok-text="TXT"
|
||||
|
||||
@@ -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(); //重置表单
|
||||
@@ -1021,7 +1034,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 +1045,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 +1058,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 +1068,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 +1081,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 +1112,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>
|
||||
|
||||
@@ -125,12 +125,6 @@ let tableColumns = ref<ColumnsType>([
|
||||
align: 'center',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'RAT',
|
||||
dataIndex: 'rat',
|
||||
align: 'center',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title: 'Forbidden Areas',
|
||||
dataIndex: 'arfb',
|
||||
@@ -267,19 +261,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],
|
||||
@@ -288,9 +287,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: {
|
||||
@@ -644,6 +646,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)
|
||||
@@ -849,46 +855,27 @@ function fnRecordDelete(imsi: string) {
|
||||
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 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},${row.tenantName}\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();
|
||||
});
|
||||
}
|
||||
|
||||
/**列表导出 */
|
||||
@@ -902,7 +889,12 @@ function fnExportList(type: string) {
|
||||
if (!neId) return;
|
||||
const key = 'exportSub';
|
||||
message.loading({ content: t('common.loading'), key });
|
||||
exportUDMSub({ ...queryParams, ...{ type, imsi: realImsi } }).then(res => {
|
||||
|
||||
exportUDMSub({
|
||||
...queryParams,
|
||||
imsi: realImsi,
|
||||
type: type,
|
||||
}).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: t('common.export') }),
|
||||
@@ -944,6 +936,9 @@ function fnLoadData() {
|
||||
fnQueryReset();
|
||||
}, timerS * 1000);
|
||||
} else {
|
||||
modalState.loadDataLoading = false;
|
||||
tableState.loading = false; // 表格loading
|
||||
fnQueryReset();
|
||||
message.error({
|
||||
content: t('common.getInfoFail'),
|
||||
duration: 3,
|
||||
@@ -976,7 +971,10 @@ function fnGetList(pageNum?: number) {
|
||||
pageSize: queryParams.pageSize,
|
||||
};
|
||||
|
||||
listUDMSub(toBack).then(res => {
|
||||
listUDMSub({
|
||||
...queryParams,
|
||||
imsi: imsiMatchRule[queryParams.imsiMatch],
|
||||
}).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
@@ -1175,6 +1173,7 @@ onMounted(() => {
|
||||
v-model:value="queryParams.neId"
|
||||
:options="neOtions"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
@change="fnGetList(1)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
@@ -1336,9 +1335,6 @@ onMounted(() => {
|
||||
ok-text="TXT"
|
||||
ok-type="default"
|
||||
@confirm="fnRecordExport('txt')"
|
||||
:show-cancel="false"
|
||||
cancel-text="CSV"
|
||||
@cancel="fnRecordExport('csv')"
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
>
|
||||
<a-button
|
||||
@@ -1423,7 +1419,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')
|
||||
}}
|
||||
@@ -1689,7 +1685,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>
|
||||
@@ -1843,7 +1839,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>
|
||||
@@ -1854,9 +1850,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"
|
||||
@@ -1876,9 +1882,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"
|
||||
|
||||
@@ -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
712
src/views/perfManage/kpiOverView/index.vue
Normal file
712
src/views/perfManage/kpiOverView/index.vue
Normal file
@@ -0,0 +1,712 @@
|
||||
<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 } 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();
|
||||
};
|
||||
|
||||
// 修改 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: '网元指标概览',
|
||||
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: {
|
||||
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: {
|
||||
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;
|
||||
}
|
||||
|
||||
// 存储指标列信
|
||||
const kpiColumns = ref<KPIColumn[]>([]);
|
||||
// 添加选中标的的状态
|
||||
const selectedKPIs = ref<string[]>([]);
|
||||
// 添加对话框可见性状态
|
||||
const isModalVisible = ref(false);
|
||||
|
||||
// 打开对话框
|
||||
const showKPISelector = () => {
|
||||
isModalVisible.value = true;
|
||||
};
|
||||
|
||||
// 添加保存选中指标到 localStorage 的方法
|
||||
const saveSelectedKPIs = () => {
|
||||
localStorage.setItem('selectedKPIs', JSON.stringify(selectedKPIs.value));
|
||||
};
|
||||
|
||||
// 窗口确认按钮
|
||||
const handleModalOk = () => {
|
||||
saveSelectedKPIs(); // 保存选择
|
||||
updateChart();
|
||||
isModalVisible.value = false;
|
||||
};
|
||||
|
||||
// 取消按钮
|
||||
const handleModalCancel = () => {
|
||||
isModalVisible.value = false;
|
||||
};
|
||||
|
||||
// 获取网元指标
|
||||
const fetchSpecificKPI = async () => {
|
||||
const language = currentLocale.value.split('_')[0] === 'zh' ? 'cn' : currentLocale.value.split('_')[0];
|
||||
|
||||
try {
|
||||
let allKPIs: KPIColumn[] = [];
|
||||
|
||||
// 使用 ALL_NE_TYPES 遍历网元类型
|
||||
for (const neType of ALL_NE_TYPES) {
|
||||
|
||||
const res = await getKPITitle(neType);
|
||||
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
// 过滤当前网元类型的指标
|
||||
const filteredKPIs = res.data.filter(item =>
|
||||
TARGET_KPI_IDS[neType].some(targetId =>
|
||||
item.kpiId === targetId || // 完全匹配
|
||||
item.kpiId.startsWith(targetId) // 前缀匹配
|
||||
)
|
||||
);
|
||||
|
||||
// 转换并添加到总指标列表
|
||||
const formattedKPIs = filteredKPIs.map(item => ({
|
||||
title: item[`${language}Title`],
|
||||
dataIndex: item.kpiId,
|
||||
key: item.kpiId,
|
||||
kpiId: item.kpiId
|
||||
}));
|
||||
allKPIs = [...allKPIs, ...formattedKPIs];
|
||||
}
|
||||
}
|
||||
|
||||
// 更新指标列信息
|
||||
kpiColumns.value = allKPIs;
|
||||
|
||||
// 尝试加载保存的选择
|
||||
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 {
|
||||
// 如果没有有效的保存选择,则默认全选
|
||||
selectedKPIs.value = kpiColumns.value.map(col => col.kpiId);
|
||||
}
|
||||
} else {
|
||||
// 如果没有保存的选择,则默认全选
|
||||
selectedKPIs.value = kpiColumns.value.map(col => col.kpiId);
|
||||
}
|
||||
|
||||
if (kpiColumns.value.length === 0) {
|
||||
console.warn('No matching KPIs found');
|
||||
} else {
|
||||
console.log(`Found ${kpiColumns.value.length} total KPIs:`,
|
||||
kpiColumns.value.map(kpi => kpi.kpiId));
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// 修改 updateChartData 方法,只更新数据而不重新渲染整个图表
|
||||
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.some(targetId =>
|
||||
kpi.kpiId === targetId || // 完全匹配
|
||||
kpi.kpiId.startsWith(targetId) // 前缀匹配
|
||||
)
|
||||
);
|
||||
});
|
||||
return groups;
|
||||
});
|
||||
|
||||
</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="t('views.perfManage.kpiOverView.chooseShowMetrics')"
|
||||
@ok="handleModalOk"
|
||||
@cancel="handleModalCancel"
|
||||
width="800px"
|
||||
:bodyStyle="{ maxHeight: '600px', overflow: 'auto' }"
|
||||
>
|
||||
<a-checkbox-group v-model:value="selectedKPIs">
|
||||
<div class="kpi-checkbox-list">
|
||||
<div v-for="neType in ALL_NE_TYPES" :key="neType" class="ne-type-group">
|
||||
<div class="ne-type-title">{{ neType.toUpperCase() }}</div>
|
||||
<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>
|
||||
</div>
|
||||
</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-group {
|
||||
margin-bottom: 24px;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.ne-type-title {
|
||||
padding: 12px 16px;
|
||||
font-weight: bold;
|
||||
background-color: #fafafa;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.ne-type-items {
|
||||
padding: 16px;
|
||||
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;
|
||||
}
|
||||
|
||||
/* 深度选择器样式 */
|
||||
:deep(.ant-modal-body) {
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #ccc;
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
background: #999;
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 组件高度统一样式 */
|
||||
: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-form-item-label) {
|
||||
padding-right: 8px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
:deep(.ant-checkbox-wrapper) {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</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>
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { Modal, message } from 'ant-design-vue/lib';
|
||||
import { onMounted, reactive, toRaw } from 'vue';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { listMenu } from '@/api/system/menu';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { getConfig, getConfigKey, changeValue } from '@/api/system/config';
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t, optionsLocale } = useI18n();
|
||||
import { getConfigKey, changeValue } from '@/api/system/config';
|
||||
const { t } = useI18n();
|
||||
|
||||
type StateType = {
|
||||
edite: boolean;
|
||||
|
||||
@@ -899,47 +899,27 @@ function fnRecordDelete(imsi: string) {
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
/**列表导出 */
|
||||
@@ -953,7 +933,12 @@ function fnExportList(type: string) {
|
||||
if (!neID) return;
|
||||
const key = 'exportSub';
|
||||
message.loading({ content: t('common.loading'), key });
|
||||
exportUDMSub({ ...queryParams, ...{ type, imsi: realImsi } }).then(res => {
|
||||
Object.assign({ type: type }, queryParams);
|
||||
exportUDMSub({
|
||||
...queryParams,
|
||||
imsi: realImsi,
|
||||
type: type,
|
||||
}).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: t('common.export') }),
|
||||
@@ -1028,7 +1013,10 @@ function fnGetList(pageNum?: number) {
|
||||
pageSize: queryParams.pageSize,
|
||||
};
|
||||
|
||||
listUDMSub(toBack).then(res => {
|
||||
listUDMSub({
|
||||
...queryParams,
|
||||
ismi: imsiMatchRule[queryParams.imsiMatch],
|
||||
}).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
@@ -1357,9 +1345,6 @@ onMounted(() => {
|
||||
ok-text="TXT"
|
||||
ok-type="default"
|
||||
@confirm="fnRecordExport('txt')"
|
||||
:show-cancel="false"
|
||||
cancel-text="CSV"
|
||||
@cancel="fnRecordExport('csv')"
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
>
|
||||
<a-button
|
||||
|
||||
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>
|
||||
|
||||
@@ -18,8 +18,8 @@ import {
|
||||
packetStart,
|
||||
packetStop,
|
||||
packetFilter,
|
||||
packetKeep,
|
||||
} from '@/api/trace/packet';
|
||||
import { s } from 'vite/dist/node/types.d-aGj9QkWt';
|
||||
const ws = new WS();
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -30,6 +30,8 @@ type StateType = {
|
||||
devices: { id: string; label: string; children: any[] }[];
|
||||
/**初始化 */
|
||||
initialized: boolean;
|
||||
/**保活调度器 */
|
||||
keepTimer: any;
|
||||
/**任务 */
|
||||
task: {
|
||||
taskNo: string;
|
||||
@@ -65,6 +67,7 @@ type StateType = {
|
||||
const state = reactive<StateType>({
|
||||
devices: [],
|
||||
initialized: false,
|
||||
keepTimer: null,
|
||||
task: {
|
||||
taskNo: 'laYlTbq',
|
||||
device: '192.168.5.58',
|
||||
@@ -275,6 +278,9 @@ function wsMessage(res: Record<string, any>) {
|
||||
// 建联时发送请求
|
||||
if (!requestId && data.clientId) {
|
||||
state.initialized = true;
|
||||
state.keepTimer = setInterval(() => {
|
||||
packetKeep(state.task.taskNo, 120);
|
||||
}, 90 * 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -321,7 +327,9 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
ws.close();
|
||||
clearInterval(state.keepTimer);
|
||||
state.keepTimer = null;
|
||||
if (ws.state() === WebSocket.OPEN) ws.close();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -366,6 +374,7 @@ onBeforeUnmount(() => {
|
||||
type="primary"
|
||||
:loading="downLoading"
|
||||
@click.prevent="fnDownloadPCAP()"
|
||||
v-if="state.task.outputPCAP"
|
||||
>
|
||||
<template #icon><DownloadOutlined /></template>
|
||||
{{ t('common.downloadText') }}
|
||||
@@ -378,7 +387,7 @@ onBeforeUnmount(() => {
|
||||
</a-tag>
|
||||
</a-space>
|
||||
|
||||
<a-space :size="8" class="toolbar-info">
|
||||
<a-space :size="8" class="toolbar-info" v-show="state.initialized">
|
||||
<span>
|
||||
{{ t('views.traceManage.task.traceId') }}:
|
||||
<strong>{{ state.task.taskNo }}</strong>
|
||||
|
||||
Reference in New Issue
Block a user