Merge remote-tracking branch 'origin/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.240815"
|
||||
VITE_APP_VERSION = "2.240920"
|
||||
|
||||
# 接口基础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.240815"
|
||||
VITE_APP_VERSION = "2.240920"
|
||||
|
||||
# 接口基础URL地址-不带/后缀
|
||||
VITE_API_BASE_URL = "/omc-api"
|
||||
|
||||
BIN
public/wiregasm/test_ethernet.pcap
Normal file
BIN
public/wiregasm/test_ethernet.pcap
Normal file
Binary file not shown.
BIN
public/wiregasm/wiregasm.data.gz
Normal file
BIN
public/wiregasm/wiregasm.data.gz
Normal file
Binary file not shown.
9768
public/wiregasm/wiregasm.js
Normal file
9768
public/wiregasm/wiregasm.js
Normal file
File diff suppressed because one or more lines are too long
BIN
public/wiregasm/wiregasm.wasm.gz
Normal file
BIN
public/wiregasm/wiregasm.wasm.gz
Normal file
Binary file not shown.
166
public/wiregasm/wiregasm_new.js
Normal file
166
public/wiregasm/wiregasm_new.js
Normal file
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* Wraps the WiregasmLib lib functionality and manages a single DissectSession
|
||||
*/
|
||||
class Wiregasm {
|
||||
constructor() {
|
||||
this.initialized = false;
|
||||
this.session = null;
|
||||
}
|
||||
/**
|
||||
* Initialize the wrapper and the Wiregasm module
|
||||
*
|
||||
* @param loader Loader function for the Emscripten module
|
||||
* @param overrides Overrides
|
||||
*/
|
||||
async init(loader, overrides = {}, beforeInit = null) {
|
||||
if (this.initialized) {
|
||||
return;
|
||||
}
|
||||
this.lib = await loader(overrides);
|
||||
this.uploadDir = this.lib.getUploadDirectory();
|
||||
this.pluginsDir = this.lib.getPluginsDirectory();
|
||||
if (beforeInit !== null) {
|
||||
await beforeInit(this.lib);
|
||||
}
|
||||
this.lib.init();
|
||||
this.initialized = true;
|
||||
}
|
||||
list_modules() {
|
||||
return this.lib.listModules();
|
||||
}
|
||||
list_prefs(module) {
|
||||
return this.lib.listPreferences(module);
|
||||
}
|
||||
apply_prefs() {
|
||||
this.lib.applyPreferences();
|
||||
}
|
||||
set_pref(module, key, value) {
|
||||
const ret = this.lib.setPref(module, key, value);
|
||||
if (ret.code != PrefSetResult.PREFS_SET_OK) {
|
||||
const message =
|
||||
ret.error != '' ? ret.error : preferenceSetCodeToError(ret.code);
|
||||
throw new Error(
|
||||
`Failed to set preference (${module}.${key}): ${message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
get_pref(module, key) {
|
||||
const response = this.lib.getPref(module, key);
|
||||
if (response.code != 0) {
|
||||
throw new Error(`Failed to get preference (${module}.${key})`);
|
||||
}
|
||||
return response.data;
|
||||
}
|
||||
/**
|
||||
* Check the validity of a filter expression.
|
||||
*
|
||||
* @param filter A display filter expression
|
||||
*/
|
||||
test_filter(filter) {
|
||||
return this.lib.checkFilter(filter);
|
||||
}
|
||||
complete_filter(filter) {
|
||||
const out = this.lib.completeFilter(filter);
|
||||
return {
|
||||
err: out.err,
|
||||
fields: vectorToArray(out.fields),
|
||||
};
|
||||
}
|
||||
reload_lua_plugins() {
|
||||
this.lib.reloadLuaPlugins();
|
||||
}
|
||||
add_plugin(name, data, opts = {}) {
|
||||
const path = this.pluginsDir + '/' + name;
|
||||
this.lib.FS.writeFile(path, data, opts);
|
||||
}
|
||||
/**
|
||||
* Load a packet trace file for analysis.
|
||||
*
|
||||
* @returns Response containing the status and summary
|
||||
*/
|
||||
load(name, data, opts = {}) {
|
||||
if (this.session != null) {
|
||||
this.session.delete();
|
||||
}
|
||||
const path = this.uploadDir + '/' + name;
|
||||
this.lib.FS.writeFile(path, data, opts);
|
||||
this.session = new this.lib.DissectSession(path);
|
||||
return this.session.load();
|
||||
}
|
||||
/**
|
||||
* Get Packet List information for a range of packets.
|
||||
*
|
||||
* @param filter Output those frames that pass this filter expression
|
||||
* @param skip Skip N frames
|
||||
* @param limit Limit the output to N frames
|
||||
*/
|
||||
frames(filter, skip = 0, limit = 0) {
|
||||
return this.session.getFrames(filter, skip, limit);
|
||||
}
|
||||
/**
|
||||
* Get full information about a frame including the protocol tree.
|
||||
*
|
||||
* @param number Frame number
|
||||
*/
|
||||
frame(num) {
|
||||
return this.session.getFrame(num);
|
||||
}
|
||||
follow(follow, filter) {
|
||||
return this.session.follow(follow, filter);
|
||||
}
|
||||
destroy() {
|
||||
if (this.initialized) {
|
||||
if (this.session !== null) {
|
||||
this.session.delete();
|
||||
this.session = null;
|
||||
}
|
||||
this.lib.destroy();
|
||||
this.initialized = false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns the column headers
|
||||
*/
|
||||
columns() {
|
||||
const vec = this.lib.getColumns();
|
||||
// convert it from a vector to array
|
||||
return vectorToArray(vec);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Vector to a JS array
|
||||
*
|
||||
* @param vec Vector
|
||||
* @returns JS array of the Vector contents
|
||||
*/
|
||||
function vectorToArray(vec) {
|
||||
return new Array(vec.size()).fill(0).map((_, id) => vec.get(id));
|
||||
}
|
||||
function preferenceSetCodeToError(code) {
|
||||
switch (code) {
|
||||
case PrefSetResult.PREFS_SET_SYNTAX_ERR:
|
||||
return 'Syntax error in string';
|
||||
case PrefSetResult.PREFS_SET_NO_SUCH_PREF:
|
||||
return 'No such preference';
|
||||
case PrefSetResult.PREFS_SET_OBSOLETE:
|
||||
return 'Preference used to exist but no longer does';
|
||||
default:
|
||||
return 'Unknown error';
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof exports === 'object' && typeof module === 'object') {
|
||||
module.exports = Wiregasm;
|
||||
module.exports = vectorToArray;
|
||||
} else if (typeof define === 'function' && define['amd']) {
|
||||
define([], function () {
|
||||
return Wiregasm;
|
||||
});
|
||||
define([], function () {
|
||||
return vectorToArray;
|
||||
});
|
||||
} else if (typeof exports === 'object') {
|
||||
exports['loadWiregasm'] = Wiregasm;
|
||||
exports['vectorToArray'] = vectorToArray;
|
||||
}
|
||||
159
public/wiregasm/worker.js
Normal file
159
public/wiregasm/worker.js
Normal file
@@ -0,0 +1,159 @@
|
||||
// load the Wiregasm library
|
||||
importScripts(
|
||||
'/wiregasm/wiregasm_new.js',
|
||||
'/wiregasm/wiregasm.js'
|
||||
// 'https://cdn.jsdelivr.net/npm/@goodtools/wiregasm/dist/wiregasm.js'
|
||||
);
|
||||
|
||||
const wg = new Wiregasm();
|
||||
|
||||
const inflateRemoteBuffer = async url => {
|
||||
const res = await fetch(url);
|
||||
return await res.arrayBuffer();
|
||||
};
|
||||
|
||||
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'
|
||||
),
|
||||
await inflateRemoteBuffer(
|
||||
'/wiregasm/wiregasm.data.gz'
|
||||
// 'https://cdn.jsdelivr.net/npm/@goodtools/wiregasm/dist/wiregasm.data.gz'
|
||||
),
|
||||
]);
|
||||
|
||||
return { wasmBuffer, dataBuffer };
|
||||
};
|
||||
|
||||
// Load the Wiregasm Wasm data
|
||||
fetchPackages()
|
||||
.then(({ wasmBuffer, dataBuffer }) => {
|
||||
return wg.init(loadWiregasm, {
|
||||
wasmBinary: wasmBuffer,
|
||||
getPreloadedPackage() {
|
||||
return dataBuffer;
|
||||
},
|
||||
handleStatus: (type, status) => {
|
||||
postMessage({ type: 'status', code: type, status: status });
|
||||
},
|
||||
printErr: error => {
|
||||
postMessage({ type: 'error', error: error });
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
postMessage({ type: 'init' });
|
||||
})
|
||||
.catch(e => {
|
||||
postMessage({ type: 'error', error: e });
|
||||
});
|
||||
|
||||
/**Converts a Vector to a JS array */
|
||||
function replacer(key, value) {
|
||||
if (value.constructor.name.startsWith('Vector')) {
|
||||
return vectorToArray(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// Event listener to receive messages from the main script
|
||||
this.onmessage = ev => {
|
||||
const data = ev.data;
|
||||
switch (data.type) {
|
||||
case 'columns':
|
||||
const columns = wg.columns();
|
||||
if (Array.isArray(columns)) {
|
||||
this.postMessage({ type: 'columns', data: columns });
|
||||
}
|
||||
break;
|
||||
case 'select': // select a frame
|
||||
const number = data.number;
|
||||
const frameData = wg.frame(number);
|
||||
const frameDataToJSON = JSON.parse(JSON.stringify(frameData, replacer));
|
||||
this.postMessage({
|
||||
type: 'selected',
|
||||
data: frameDataToJSON,
|
||||
});
|
||||
break;
|
||||
case 'frames': // get frames list
|
||||
const skip = data.skip;
|
||||
const limit = data.limit;
|
||||
const filter = data.filter;
|
||||
const framesData = wg.frames(filter, skip, limit);
|
||||
const framesDataToJSON = JSON.parse(JSON.stringify(framesData, replacer));
|
||||
this.postMessage({
|
||||
type: 'frames',
|
||||
data: framesDataToJSON,
|
||||
});
|
||||
break;
|
||||
case 'process-data':
|
||||
const loadData = wg.load(data.name, new Uint8Array(data.data));
|
||||
this.postMessage({ type: 'processed', data: loadData });
|
||||
break;
|
||||
case 'process':
|
||||
const f = data.file;
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('load', event => {
|
||||
// XXX: this blocks the worker thread
|
||||
const loadData = wg.load(f.name, new Uint8Array(event.target.result));
|
||||
postMessage({ type: 'processed', data: loadData });
|
||||
});
|
||||
reader.readAsArrayBuffer(f);
|
||||
break;
|
||||
case 'check-filter':
|
||||
const filterStr = data.filter;
|
||||
const checkFilterRes = wg.lib.checkFilter(filterStr);
|
||||
this.postMessage({ type: 'filter', data: checkFilterRes });
|
||||
break;
|
||||
}
|
||||
|
||||
if (data.type === 'reload-quick') {
|
||||
if (wg.session) {
|
||||
// TODO: this is a hack, we should be able to reload the session
|
||||
const name = data.name;
|
||||
const res = wg.session.load();
|
||||
|
||||
postMessage({ type: 'processed', name: name, data: res });
|
||||
}
|
||||
} else if (data.type === 'module-tree') {
|
||||
const res = wg.list_modules();
|
||||
// send it to the correct port
|
||||
event.ports[0].postMessage({
|
||||
result: JSON.parse(JSON.stringify(res, replacer)),
|
||||
});
|
||||
} else if (data.type === 'module-prefs') {
|
||||
const res = wg.list_prefs(data.name);
|
||||
// send it to the correct port
|
||||
event.ports[0].postMessage({
|
||||
result: JSON.parse(JSON.stringify(res, replacer)),
|
||||
});
|
||||
} else if (data.type === 'upload-file') {
|
||||
const f = data.file;
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('load', e => {
|
||||
// XXX: this blocks the worker thread
|
||||
const path = '/uploads/' + f.name;
|
||||
wg.lib.FS.writeFile(path, Buffer.from(e.target.result));
|
||||
event.ports[0].postMessage({ result: path });
|
||||
});
|
||||
reader.readAsArrayBuffer(f);
|
||||
} else if (data.type === 'update-pref') {
|
||||
try {
|
||||
console.log(`set_pref(${data.module}, ${data.key}, ${data.value})`);
|
||||
wg.set_pref(data.module, data.key, data.value);
|
||||
event.ports[0].postMessage({ result: 'ok' });
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`set_pref(${data.module}, ${data.key}, ${data.value}) failed: ${e.message}`
|
||||
);
|
||||
event.ports[0].postMessage({ error: e.message });
|
||||
}
|
||||
} else if (data.type === 'apply-prefs') {
|
||||
console.log(`apply_prefs()`);
|
||||
wg.apply_prefs();
|
||||
event.ports[0].postMessage({ result: 'ok' });
|
||||
}
|
||||
};
|
||||
@@ -241,6 +241,7 @@ export function listSync() {
|
||||
return request({
|
||||
url: `/api/rest/faultManagement/v1/elementType/all/objectType/alarms`,
|
||||
method: 'get',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
* @param query 查询参数
|
||||
@@ -28,8 +26,6 @@ export async function listAct(query: Record<string, any>) {
|
||||
querySQL += ` and pv_flag = '${query.pvFlag}' `;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (query.neId) {
|
||||
querySQL += ` and ne_id like '%${query.neId}%' `;
|
||||
}
|
||||
@@ -69,7 +65,6 @@ export async function listAct(query: Record<string, any>) {
|
||||
msg: result.msg,
|
||||
};
|
||||
result.data.data.forEach((item: any) => {
|
||||
console.log(item)
|
||||
const itemData = item['alarm_event'];
|
||||
if (Array.isArray(itemData)) {
|
||||
if (itemData.length === 1 && itemData[0]['total'] >= 0) {
|
||||
@@ -84,12 +79,6 @@ export async function listAct(query: Record<string, any>) {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 事件告警导出
|
||||
* @param query 查询参数
|
||||
@@ -133,7 +122,3 @@ export async function exportAll(query: Record<string, any>) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
53
src/api/logManage/exportFile.ts
Normal file
53
src/api/logManage/exportFile.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 获取下拉框数据
|
||||
* @returns object
|
||||
*/
|
||||
export function getBakFile() {
|
||||
return request({
|
||||
url: '/lm/table/list',
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对应类型的文件列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function getBakFileList(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/lm/file/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载远端文件
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function downFile(query: Record<string, any>) {
|
||||
return request({
|
||||
url: `/lm/file/${query.fileName}`,
|
||||
method: 'get',
|
||||
params: query,
|
||||
responseType: 'blob',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除远端获取文件
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function delFile(query: Record<string, any>) {
|
||||
return request({
|
||||
url: `/lm/file/${query.fileName}`,
|
||||
method: 'delete',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
@@ -40,3 +40,16 @@ export function exportSMFDataCDR(data: Record<string, any>) {
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* SMF-在线订阅用户列表信息
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listSMFSubscribers(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/smf/subscribers',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
19
src/api/perfManage/customData.ts
Normal file
19
src/api/perfManage/customData.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
|
||||
/**
|
||||
* 新 查询自定义指标数据
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export async function listCustomData(query: Record<string, any>) {
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/pm/kpiC/report`,
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
@@ -8,57 +8,72 @@ import { parseDateToStr } from '@/utils/date-utils';
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export async function listCustom(query: Record<string, any>) {
|
||||
let totalSQL = 'select count(*) as total from pm_custom_title where 1=1 ';
|
||||
let rowsSQL = 'select * from pm_custom_title where 1=1 ';
|
||||
// export async function listCustom(query: Record<string, any>) {
|
||||
// let totalSQL = 'select count(*) as total from pm_custom_title where 1=1 ';
|
||||
// let rowsSQL = 'select * from pm_custom_title where 1=1 ';
|
||||
|
||||
// 查询
|
||||
let querySQL = '';
|
||||
if (query.neType) {
|
||||
querySQL += ` and ne_type like '%${query.neType}%' `;
|
||||
}
|
||||
// // 查询
|
||||
// let querySQL = '';
|
||||
// if (query.neType) {
|
||||
// querySQL += ` and ne_type like '%${query.neType}%' `;
|
||||
// }
|
||||
|
||||
// 排序
|
||||
let sortSql = ' order by update_time ';
|
||||
if (query.sortOrder === 'asc') {
|
||||
sortSql += ' asc ';
|
||||
} else {
|
||||
sortSql += ' desc ';
|
||||
}
|
||||
// 分页
|
||||
const pageNum = (query.pageNum - 1) * query.pageSize;
|
||||
const limtSql = ` limit ${pageNum},${query.pageSize} `;
|
||||
// // 排序
|
||||
// let sortSql = ' order by update_time ';
|
||||
// if (query.sortOrder === 'asc') {
|
||||
// sortSql += ' asc ';
|
||||
// } else {
|
||||
// sortSql += ' desc ';
|
||||
// }
|
||||
// // 分页
|
||||
// const pageNum = (query.pageNum - 1) * query.pageSize;
|
||||
// const limtSql = ` limit ${pageNum},${query.pageSize} `;
|
||||
|
||||
// // 发起请求
|
||||
// const result = await request({
|
||||
// url: `/api/rest/databaseManagement/v1/select/omc_db/pm_custom_title`,
|
||||
// method: 'get',
|
||||
// params: {
|
||||
// totalSQL: totalSQL + querySQL,
|
||||
// rowsSQL: rowsSQL + querySQL + sortSql + 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['pm_custom_title'];
|
||||
// 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 query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export async function listCustom(query?: Record<string, any>) {
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/select/omc_db/pm_custom_title`,
|
||||
url: `/pm/kpiC/title/totalList`,
|
||||
method: 'get',
|
||||
params: {
|
||||
totalSQL: totalSQL + querySQL,
|
||||
rowsSQL: rowsSQL + querySQL + sortSql + limtSql,
|
||||
},
|
||||
params: query,
|
||||
});
|
||||
|
||||
// 解析数据
|
||||
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['pm_custom_title'];
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -68,22 +83,10 @@ export async function listCustom(query: Record<string, any>) {
|
||||
* @returns object
|
||||
*/
|
||||
export async function getCustom(id: string | number) {
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/select/omc_db/pm_custom_title`,
|
||||
return request({
|
||||
url: `/pm/kpiC/title/${id}`,
|
||||
method: 'get',
|
||||
params: {
|
||||
SQL: `select * from pm_custom_title where id = ${id}`,
|
||||
},
|
||||
});
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
|
||||
let data = result.data.data[0];
|
||||
return Object.assign(result, {
|
||||
data: parseObjLineToHump(data['pm_custom_title'][0]),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,21 +95,10 @@ export async function getCustom(id: string | number) {
|
||||
* @returns object
|
||||
*/
|
||||
export function addCustom(data: Record<string, any>) {
|
||||
let obj: any = {
|
||||
title: data.title,
|
||||
ne_type: data.neType,
|
||||
kpi_id: data.kpiId,
|
||||
object_type: data.objectType,
|
||||
expression: data.expression,
|
||||
period: data.period,
|
||||
description: data.description,
|
||||
kpi_set: data.kpiSet,
|
||||
};
|
||||
|
||||
return request({
|
||||
url: `/api/rest/databaseManagement/v1/omc_db/pm_custom_title`,
|
||||
url: `/pm/kpiC/title`,
|
||||
method: 'post',
|
||||
data: { 'data': [obj] },
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -116,20 +108,10 @@ export function addCustom(data: Record<string, any>) {
|
||||
* @returns object
|
||||
*/
|
||||
export function updateCustom(data: Record<string, any>) {
|
||||
let obj: any = {
|
||||
title: data.title,
|
||||
ne_type: data.neType,
|
||||
kpi_id: data.kpiId,
|
||||
object_type: data.objectType,
|
||||
expression: data.expression,
|
||||
period: data.period,
|
||||
description: data.description,
|
||||
kpi_set: data.kpiSet,
|
||||
};
|
||||
return request({
|
||||
url: `/api/rest/databaseManagement/v1/omc_db/pm_custom_title?WHERE=id=${data.id}`,
|
||||
url: `/pm/kpiC/title/${data.id}`,
|
||||
method: 'put',
|
||||
data: { data: obj },
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -139,8 +121,7 @@ export function updateCustom(data: Record<string, any>) {
|
||||
*/
|
||||
export async function delCustom(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/api/rest/databaseManagement/v1/omc_db/pm_custom_title?WHERE=id=${data.id}`,
|
||||
url: `/pm/kpiC/title/${data.id}`,
|
||||
method: 'delete',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,21 @@ export function dumpStop(data: Record<string, string>) {
|
||||
});
|
||||
}
|
||||
|
||||
// 网元抓包PACP 下载
|
||||
export function dumpDownload(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/trace/tcpdump/download',
|
||||
method: 'get',
|
||||
params: data,
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
// UPF标准版内部抓包
|
||||
export function traceUPF(data: Record<string, string>) {
|
||||
return request({
|
||||
url: '/trace/tcpdump/traceUPF',
|
||||
url: '/trace/tcpdump/upf',
|
||||
method: 'post',
|
||||
data: data,
|
||||
});
|
||||
89
src/api/trace/task.ts
Normal file
89
src/api/trace/task.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
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 listTraceTask(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/trace/task/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询跟踪任务信息
|
||||
* @param id 网元ID
|
||||
* @returns object
|
||||
*/
|
||||
export async function getTraceTask(id: string | number) {
|
||||
return request({
|
||||
url: `/trace/task/${id}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增任务
|
||||
* @param data 网元对象
|
||||
* @returns object
|
||||
*/
|
||||
export function addTraceTask(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/trace/task`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改任务
|
||||
* @param data 网元对象
|
||||
* @returns object
|
||||
*/
|
||||
export function updateTraceTask(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/trace/task`,
|
||||
method: 'put',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 跟踪任务删除
|
||||
* @param ids ID多个逗号分隔
|
||||
* @returns object
|
||||
*/
|
||||
export async function delTraceTask(ids: string) {
|
||||
return request({
|
||||
url: `/trace/task/${ids}`,
|
||||
method: 'delete',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取网元跟踪接口列表
|
||||
* @returns object
|
||||
*/
|
||||
export async function getNeTraceInterfaceAll() {
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/elementType/omc_db/objectType/ne_info`,
|
||||
method: 'get',
|
||||
params: {
|
||||
SQL: `SELECT ne_type,interface FROM trace_info GROUP BY ne_type,interface`,
|
||||
},
|
||||
});
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
|
||||
let data = result.data.data[0];
|
||||
return Object.assign(result, {
|
||||
data: parseObjLineToHump(data['trace_info']),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
83
src/api/trace/taskHLR.ts
Normal file
83
src/api/trace/taskHLR.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 查询跟踪任务列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listTaskHLR(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/trace/task/hlr/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 跟踪任务删除
|
||||
* @param ids 任务ID
|
||||
* @returns object
|
||||
*/
|
||||
export function delTaskHLR(ids: string | number) {
|
||||
return request({
|
||||
url: `/trace/task/hlr/${ids}`,
|
||||
method: 'delete',
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 跟踪任务创建
|
||||
* @param data 对象
|
||||
* @returns object
|
||||
*/
|
||||
export function startTaskHLR(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/trace/task/hlr/start',
|
||||
method: 'post',
|
||||
data: data,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 跟踪任务停止
|
||||
* @param data 对象
|
||||
* @returns object
|
||||
*/
|
||||
export function stopTaskHLR(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/trace/task/hlr/stop',
|
||||
method: 'post',
|
||||
data: data,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 跟踪任务文件
|
||||
* @param data 对象
|
||||
* @returns object
|
||||
*/
|
||||
export function fileTaskHLR(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/trace/task/hlr/file',
|
||||
method: 'post',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 跟踪任务文件从网元到本地
|
||||
* @param query 对象
|
||||
* @returns object
|
||||
*/
|
||||
export function filePullTaskHLR(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/trace/task/hlr/filePull',
|
||||
method: 'get',
|
||||
params: query,
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
@@ -1,154 +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 listTraceTask(query: Record<string, any>) {
|
||||
let totalSQL = 'select count(*) as total from trace_task where 1=1 ';
|
||||
let rowsSQL = 'select * from trace_task where 1=1 ';
|
||||
|
||||
// 查询
|
||||
let querySQL = '';
|
||||
if (query.imsi) {
|
||||
querySQL += ` and imsi like '%${query.imsi}%' `;
|
||||
}
|
||||
if (query.beginTime) {
|
||||
querySQL += ` and start_time >= '${query.beginTime}' `;
|
||||
}
|
||||
if (query.endTime) {
|
||||
querySQL += ` and end_time <= '${query.endTime}' `;
|
||||
}
|
||||
|
||||
// 分页
|
||||
const pageNum = (query.pageNum - 1) * query.pageSize;
|
||||
const limtSql = ` limit ${pageNum},${query.pageSize} `;
|
||||
|
||||
// 排序
|
||||
let sortSql = ' order by start_time ';
|
||||
if (query.sortOrder === 'asc') {
|
||||
sortSql += ' asc ';
|
||||
} else {
|
||||
sortSql += ' desc ';
|
||||
}
|
||||
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/select/omc_db/trace_task`,
|
||||
method: 'get',
|
||||
params: {
|
||||
totalSQL: totalSQL + querySQL,
|
||||
rowsSQL: rowsSQL + querySQL + sortSql + 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['trace_task'];
|
||||
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 id 网元ID
|
||||
* @returns object
|
||||
*/
|
||||
export async function getTraceTask(id: string | number) {
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/select/omc_db/trace_task`,
|
||||
method: 'get',
|
||||
params: {
|
||||
SQL: `select * from trace_task where id = ${id}`,
|
||||
},
|
||||
});
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
|
||||
let data = result.data.data[0];
|
||||
return Object.assign(result, {
|
||||
data: parseObjLineToHump(data['trace_task'][0]),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增任务
|
||||
* @param data 网元对象
|
||||
* @returns object
|
||||
*/
|
||||
export function addTraceTask(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/api/rest/traceManagement/v1/subscriptions`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改任务
|
||||
* @param data 网元对象
|
||||
* @returns object
|
||||
*/
|
||||
export function updateTraceTask(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/api/rest/traceManagement/v1/subscriptions`,
|
||||
method: 'put',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除任务
|
||||
* @param noticeId 网元ID
|
||||
* @returns object
|
||||
*/
|
||||
export async function delTraceTask(id: string) {
|
||||
return request({
|
||||
url: `/api/rest/traceManagement/v1/subscriptions?id=${id}`,
|
||||
method: 'delete',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取网元跟踪接口列表
|
||||
* @returns object
|
||||
*/
|
||||
export async function getNeTraceInterfaceAll() {
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/elementType/omc_db/objectType/ne_info`,
|
||||
method: 'get',
|
||||
params: {
|
||||
SQL: `SELECT ne_type,interface FROM trace_info GROUP BY ne_type,interface`,
|
||||
},
|
||||
});
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
|
||||
let data = result.data.data[0];
|
||||
return Object.assign(result, {
|
||||
data: parseObjLineToHump(data['trace_info']),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
11
src/assets/js/wiregasm_worker.ts
Normal file
11
src/assets/js/wiregasm_worker.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* worker文件-静态资源文件路径
|
||||
*/
|
||||
const baseUrl = import.meta.env.VITE_HISTORY_BASE_URL;
|
||||
export const scriptUrl = `${
|
||||
baseUrl.length === 1 && baseUrl.indexOf('/') === 0
|
||||
? ''
|
||||
: baseUrl.indexOf('/') === -1
|
||||
? '/' + baseUrl
|
||||
: baseUrl
|
||||
}/wiregasm/worker.js`;
|
||||
@@ -119,7 +119,7 @@ function fnAutoCompleteKeydown(evt: any) {
|
||||
ws.send({
|
||||
requestId: `telnet_${props.hostId}`,
|
||||
type: 'telnet',
|
||||
data: `${cmdStr}\r\n'`,
|
||||
data: `${cmdStr}\r\n`,
|
||||
});
|
||||
terminalState.text = ' ';
|
||||
|
||||
|
||||
204
src/components/VirtualList/index.vue
Normal file
204
src/components/VirtualList/index.vue
Normal file
@@ -0,0 +1,204 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, computed, unref, onUpdated, watchEffect } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
/**列表高度 */
|
||||
height: {
|
||||
type: Number,
|
||||
default: 300,
|
||||
},
|
||||
/**列表项高度 */
|
||||
itemHeight: {
|
||||
type: Number,
|
||||
default: 30,
|
||||
},
|
||||
/**数据 */
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
/**预先兜底缓存数量 */
|
||||
cache: {
|
||||
type: Number,
|
||||
default: 2,
|
||||
},
|
||||
/**是否动态加载 */
|
||||
dynamic: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const state = reactive<any>({
|
||||
start: 0,
|
||||
end: 10,
|
||||
scrollOffset: 0,
|
||||
cacheData: [],
|
||||
});
|
||||
const virtualListRef = ref();
|
||||
|
||||
const getWrapperStyle = computed(() => {
|
||||
const { height } = props;
|
||||
return {
|
||||
height: `${height}px`,
|
||||
};
|
||||
});
|
||||
|
||||
const getInnerStyle = computed(() => {
|
||||
return {
|
||||
height: `${unref(getTotalHeight)}px`,
|
||||
width: '100%',
|
||||
};
|
||||
});
|
||||
|
||||
const getListStyle = computed(() => {
|
||||
return {
|
||||
willChange: 'transform',
|
||||
transform: `translateY(${state.scrollOffset}px)`,
|
||||
};
|
||||
});
|
||||
|
||||
// 数据数量
|
||||
const total = computed(() => {
|
||||
return props.data.length;
|
||||
});
|
||||
|
||||
// 总体高度
|
||||
const getTotalHeight = computed(() => {
|
||||
if (!props.dynamic) return unref(total) * props.itemHeight;
|
||||
return getCurrentTop(unref(total));
|
||||
});
|
||||
|
||||
// 当前屏幕显示的数量
|
||||
const clientCount = computed(() => {
|
||||
return Math.ceil(props.height / props.itemHeight);
|
||||
});
|
||||
|
||||
// 当前屏幕显示的数据
|
||||
const clientData = computed(() => {
|
||||
return props.data.slice(state.start, state.end);
|
||||
});
|
||||
|
||||
const onScroll = (e: any) => {
|
||||
const { scrollTop } = e.target;
|
||||
if (state.scrollOffset === scrollTop) return;
|
||||
const { cache, dynamic, itemHeight } = props;
|
||||
const cacheCount = Math.max(1, cache);
|
||||
|
||||
let startIndex = dynamic
|
||||
? getStartIndex(scrollTop)
|
||||
: Math.floor(scrollTop / itemHeight);
|
||||
|
||||
const endIndex = Math.max(
|
||||
0,
|
||||
Math.min(unref(total), startIndex + unref(clientCount) + cacheCount)
|
||||
);
|
||||
|
||||
if (startIndex > cacheCount) {
|
||||
startIndex = startIndex - cacheCount;
|
||||
}
|
||||
|
||||
// 偏移量
|
||||
const offset = dynamic
|
||||
? getCurrentTop(startIndex)
|
||||
: scrollTop - (scrollTop % itemHeight);
|
||||
|
||||
Object.assign(state, {
|
||||
start: startIndex,
|
||||
end: endIndex,
|
||||
scrollOffset: offset,
|
||||
});
|
||||
};
|
||||
|
||||
// 二分法去查找对应的index
|
||||
const getStartIndex = (scrollTop = 0): number => {
|
||||
let low = 0;
|
||||
let high = state.cacheData.length - 1;
|
||||
while (low <= high) {
|
||||
const middle = low + Math.floor((high - low) / 2);
|
||||
const middleTopValue = getCurrentTop(middle);
|
||||
const middleBottomValue = getCurrentTop(middle + 1);
|
||||
|
||||
if (middleTopValue <= scrollTop && scrollTop <= middleBottomValue) {
|
||||
return middle;
|
||||
} else if (middleBottomValue < scrollTop) {
|
||||
low = middle + 1;
|
||||
} else if (middleBottomValue > scrollTop) {
|
||||
high = middle - 1;
|
||||
}
|
||||
}
|
||||
return Math.min(
|
||||
unref(total) - unref(clientCount),
|
||||
Math.floor(scrollTop / props.itemHeight)
|
||||
);
|
||||
};
|
||||
|
||||
const getCurrentTop = (index: number) => {
|
||||
const lastIndex = state.cacheData.length - 1;
|
||||
|
||||
if (Object.hasOwn(state.cacheData, index)) {
|
||||
return state.cacheData[index].top;
|
||||
} else if (Object.hasOwn(state.cacheData, index - 1)) {
|
||||
return state.cacheData[index - 1].bottom;
|
||||
} else if (index > lastIndex) {
|
||||
return (
|
||||
state.cacheData[lastIndex].bottom +
|
||||
Math.max(0, index - state.cacheData[lastIndex].index) * props.itemHeight
|
||||
);
|
||||
} else {
|
||||
return index * props.itemHeight;
|
||||
}
|
||||
};
|
||||
|
||||
onUpdated(() => {
|
||||
if (!props.dynamic) return;
|
||||
const childrenList = virtualListRef.value.children || [];
|
||||
[...childrenList].forEach((node: any, index: number) => {
|
||||
const height = node.getBoundingClientRect().height;
|
||||
const currentIndex = state.start + index;
|
||||
if (state.cacheData[currentIndex].height === height) return;
|
||||
|
||||
state.cacheData[currentIndex].height = height;
|
||||
state.cacheData[currentIndex].top = getCurrentTop(currentIndex);
|
||||
state.cacheData[currentIndex].bottom =
|
||||
state.cacheData[currentIndex].top + state.cacheData[currentIndex].height;
|
||||
});
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
clientData.value.forEach((_, index) => {
|
||||
const currentIndex = state.start + index;
|
||||
if (Object.hasOwn(state.cacheData, currentIndex)) return;
|
||||
state.cacheData[currentIndex] = {
|
||||
top: currentIndex * props.itemHeight,
|
||||
height: props.itemHeight,
|
||||
bottom: (currentIndex + 1) * props.itemHeight,
|
||||
index: currentIndex,
|
||||
};
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="virtual-list-wrapper"
|
||||
ref="wrapperRef"
|
||||
:style="getWrapperStyle"
|
||||
@scroll="onScroll"
|
||||
>
|
||||
<div class="virtual-list-inner" ref="innerRef" :style="getInnerStyle">
|
||||
<div class="virtual-list" :style="getListStyle" ref="virtualListRef">
|
||||
<div v-for="(item, index) in clientData" :key="index + state.start">
|
||||
<slot name="default" :item="item"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.virtual-list-wrapper {
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -16,6 +16,7 @@ export const NE_TYPE_LIST = [
|
||||
'N3IWF',
|
||||
'MOCNGW',
|
||||
'SMSC',
|
||||
'CBC',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -88,7 +88,7 @@ export default {
|
||||
day33: "days",
|
||||
day4: "Designated day (optional)",
|
||||
day5: "Last day of the month",
|
||||
hour1: "Hourly",
|
||||
hour1: "Hourly",
|
||||
hour21: "Execute every",
|
||||
hour22: "hours, starting from the",
|
||||
hour23: "hour",
|
||||
@@ -149,6 +149,7 @@ export default {
|
||||
page403: 'No Access',
|
||||
page404: 'Match Page Not Found',
|
||||
helpDoc: 'System User Documentation',
|
||||
traceTaskHLR: 'Tracking Tasks HLR',
|
||||
lockScreen: 'Lock Screen',
|
||||
account: {
|
||||
index: "Personal Center",
|
||||
@@ -185,11 +186,11 @@ export default {
|
||||
helpDoc: 'Doc',
|
||||
},
|
||||
rightContent: {
|
||||
alarm: "Active Alarms",
|
||||
lock: "Lock Screen",
|
||||
lockTip: "Confirmation of the lock screen?",
|
||||
lockPasswd: "Unlock Password",
|
||||
lockPasswdTip: "No password can be set",
|
||||
helpDoc: "System User Documentation",
|
||||
fullscreen: "Full Screen",
|
||||
logout: "Logout",
|
||||
profile: "Profile",
|
||||
@@ -239,7 +240,7 @@ export default {
|
||||
ttile: 'No access',
|
||||
subTitle:'Please do not perform illegal operations, you can go back to the main page or return to the',
|
||||
backHome: 'Back to Home',
|
||||
back: 'Back',
|
||||
back: 'Back',
|
||||
},
|
||||
err404: {
|
||||
ttile: 'Match page not found',
|
||||
@@ -296,10 +297,10 @@ export default {
|
||||
newPassword: "New Password",
|
||||
newPasswordTip: "Password contains at least upper and lower case letters, numbers, special symbols, and not less than 6 digits",
|
||||
newPassworddPleace: "Please enter a new password",
|
||||
confirmPassword: "Confirm new password",
|
||||
confirmPassword: "Confirm new password",
|
||||
confirmPasswordPleace: "Please confirm the new password",
|
||||
submit: "Submit changes",
|
||||
reset: "Reset",
|
||||
reset: "Reset",
|
||||
submitTip: "Are you sure you want to submit a password change?",
|
||||
submitOkTip: "Congratulations, {num} account password changed successfully!",
|
||||
submitOk: "Sign in again",
|
||||
@@ -525,7 +526,7 @@ export default {
|
||||
up:'Uplink',
|
||||
down:'Downlink'
|
||||
},
|
||||
upfFlowTotal:{
|
||||
upfFlowTotal:{
|
||||
title:'Traffic Summary',
|
||||
up:'Uplink',
|
||||
down:'Downlink'
|
||||
@@ -606,7 +607,7 @@ export default {
|
||||
neIdTip: 'Fill in the unique identifier of the network element binding',
|
||||
rmUid: 'Resource Unique ID',
|
||||
rmUidPlease: 'Please enter a resource unique ID',
|
||||
rmUidTip: "Tagging for data reporting of network element logs, alarms, metrics, etc.",
|
||||
rmUidTip: "Tagging for data reporting of network element logs, alarms, metrics, etc.",
|
||||
neName: 'NE Name',
|
||||
neNamePlease: 'Please enter the name of the network element',
|
||||
ipAddr: 'IP Addr',
|
||||
@@ -630,9 +631,9 @@ export default {
|
||||
log: 'Logs',
|
||||
},
|
||||
neInfo: {
|
||||
version: "Version",
|
||||
version: "Version",
|
||||
state: "State",
|
||||
serviceState: "Service Status",
|
||||
serviceState: "Service Status",
|
||||
info: 'Status Message',
|
||||
resourceInfo: 'Resource Situation',
|
||||
sysMem: "SYS Mem",
|
||||
@@ -666,8 +667,8 @@ export default {
|
||||
backConf: {
|
||||
export: 'Config Export',
|
||||
import: 'Config Import',
|
||||
title: 'Configuration File Import',
|
||||
importType: 'Source of File',
|
||||
title: 'Configuration File Import',
|
||||
importType: 'Source of File',
|
||||
server:'Server File',
|
||||
local:'Local File',
|
||||
localUpload:'Local Upload',
|
||||
@@ -743,17 +744,17 @@ export default {
|
||||
rollback: 'Switch to previous version',
|
||||
rollbackTip: "Confirm switching to the previous version?",
|
||||
rollbackTipEmpty: "There is currently no previous version available",
|
||||
rollbackTipEqual: 'The current version is the same as the previous version',
|
||||
rollbackTipEqual: 'The current version is the same as the previous version',
|
||||
version: "Current Version",
|
||||
preVersion: "Previous Version",
|
||||
newVersion: "New Version",
|
||||
status: "Revision Status",
|
||||
upgradeBatch: "Batch Upgrade",
|
||||
upgradeBatchTip: "Do you perform new version upgrades on checked records?",
|
||||
upgradeNotNewVer: 'No new version found',
|
||||
upgradeOMCVer: 'Rejection of batch operation upgrades OMC',
|
||||
upgradeDone: 'Update complete, service being reloaded',
|
||||
upgradeFail: 'The update fails, please check whether the software file exists and whether the service terminal environment is available!',
|
||||
upgradeNotNewVer: 'No new version found',
|
||||
upgradeOMCVer: 'Rejection of batch operation upgrades OMC',
|
||||
upgradeDone: 'Update complete, service being reloaded',
|
||||
upgradeFail: 'The update fails, please check whether the software file exists and whether the service terminal environment is available!',
|
||||
upgradeModal: 'Network Element Version Updates',
|
||||
},
|
||||
neLicense: {
|
||||
@@ -773,7 +774,7 @@ export default {
|
||||
uploadChangeOk: 'Network Element renewed license successfully and is being calibrated in the background!',
|
||||
uploadChangeFail: "Some network elements failed to update the license, please check whether the service terminal environment is available!",
|
||||
},
|
||||
neConfig: {
|
||||
neConfig: {
|
||||
treeTitle: "Navigation Configuration",
|
||||
treeSelectTip: "Select configuration item information in the left configuration navigation!",
|
||||
neType: 'NE Type',
|
||||
@@ -877,12 +878,13 @@ export default {
|
||||
imsiTip3: 'MSIN = Mobile Subscriber Identification Number, consisting of 10 equal digits.',
|
||||
amfTip: 'Authentication management field, maximum parameter length is 4',
|
||||
algoIndexTip: 'Algorithm index, between 0 and 15',
|
||||
kiTip: 'User signing key information, the maximum length of 32',
|
||||
kiTip: 'User signing key information, the length can only be 32',
|
||||
opcTip: 'The authentication key, OPC, is calculated from Ki and OP, OP is the root key of the operator, ki is the authentication key, and the maximum length is 32.',
|
||||
delSure:'Are you sure you want to delete the user with IMSI number: {imsi} ?',
|
||||
imsiConfirm:'The length of the IMSI must be 15',
|
||||
},
|
||||
sub: {
|
||||
subInfo:' Subscription Info',
|
||||
subInfo:' Subscription Info',
|
||||
neType: 'UDM Object',
|
||||
export: 'Export',
|
||||
exportConfirm: 'Are you sure to export all signed user data?',
|
||||
@@ -940,7 +942,7 @@ export default {
|
||||
batchAddText: 'Batch Add',
|
||||
batchDelText: 'Batch Delete',
|
||||
batchUpdateText: 'Batch Modify',
|
||||
batchNum: 'Number of batches',
|
||||
batchNum: 'Number of batches',
|
||||
imsiTip: 'IMSI=MCC+MNC+MSIN',
|
||||
imsiTip1: 'MCC=Mobile Country Code, consisting of three digits.',
|
||||
imsiTip2: 'MNC = Mobile Network Number, consisting of two digits',
|
||||
@@ -967,10 +969,10 @@ export default {
|
||||
n3iwf: {
|
||||
neType: 'N3IWF Object',
|
||||
},
|
||||
ue: {
|
||||
ue: {
|
||||
neType: 'SMF Object',
|
||||
},
|
||||
ims: {
|
||||
ims: {
|
||||
neType: 'IMS Object',
|
||||
},
|
||||
nssf:{
|
||||
@@ -1059,6 +1061,7 @@ export default {
|
||||
},
|
||||
customTarget:{
|
||||
kpiId:' Custom Indicator',
|
||||
kpiIdTip:'This Ne has no custom indicators',
|
||||
period:' Granularity',
|
||||
title:' Custom Indicator Title',
|
||||
objectType:' Object type',
|
||||
@@ -1070,6 +1073,13 @@ export default {
|
||||
addCustom:' Add custom indicator',
|
||||
editCustom:' Edit Custom indicator',
|
||||
errorCustomInfo: 'Failed to get information',
|
||||
status: 'Status',
|
||||
active:'Active',
|
||||
inactive:'Inactive',
|
||||
symbol:'Symbol',
|
||||
element:'Element',
|
||||
granularity:'Granularity',
|
||||
unit:'Unit',
|
||||
}
|
||||
},
|
||||
traceManage: {
|
||||
@@ -1095,18 +1105,23 @@ export default {
|
||||
pcap: {
|
||||
capArgPlease: 'Please enter tcpdump -i any support parameter',
|
||||
cmd: 'Command',
|
||||
execCmd: "Generic tcpdump packet capture command",
|
||||
execCmdsSctp: "Generic tcpdump filter sctp and port commands",
|
||||
execUPFCmdA: 'Suitable for anomalous packet capture of other NE',
|
||||
execUPFCmdB: 'Suitable for UPF anomaly packet capture analysis',
|
||||
execCmd: "Common Command Options",
|
||||
execCmd2: "Filter Protocol Port Command",
|
||||
execCmd3: "File Split By Time units of seconds (-G 10), Generated Max File Number (-W 7)",
|
||||
execUPFCmdA: 'Standard Edition - UPF with other NE anomalous packet capture analysis',
|
||||
execUPFCmdB: 'Standard Edition - UPF anomalies requiring packet capture analysis',
|
||||
batchOper: 'Batch Operations',
|
||||
batchStartText: 'Batch Start',
|
||||
batchStopText: 'Batch Stop',
|
||||
batchDownText: 'Batch Download',
|
||||
fileView: 'Historical Packet Capture Files',
|
||||
fileUPF: 'Standard Edition',
|
||||
fileUPFTip: 'UPF internal packet capture and analysis packet',
|
||||
textStart: "Start",
|
||||
textStartBatch: "Batch Start",
|
||||
textStop: "Stop",
|
||||
textStopBatch: "Batch Stop",
|
||||
textLog: "Log",
|
||||
textLogMsg: "Log Info",
|
||||
textDown: "Download",
|
||||
textDownBatch: "Batch Download",
|
||||
downTip: "Are you sure you want to download the {title} capture data file?",
|
||||
downOk: "{title} file download complete",
|
||||
downErr: "{title} file download exception",
|
||||
@@ -1126,6 +1141,9 @@ export default {
|
||||
trackType: 'Tracing Type',
|
||||
trackTypePlease: 'Please select a tracing type',
|
||||
creater: 'Created by',
|
||||
textStop: "Stop",
|
||||
status: 'Status',
|
||||
time: 'Time',
|
||||
startTime: 'Start Time',
|
||||
endTime: 'End Time',
|
||||
msisdn: 'MSISDN',
|
||||
@@ -1135,26 +1153,30 @@ export default {
|
||||
imsiPlease: 'Please enter IMSI',
|
||||
imsiTip: 'Mobile communication IMSI number',
|
||||
srcIp: 'Source IP Address',
|
||||
srcIpPlease: 'Please enter the source IP address',
|
||||
srcIpPlease: 'Please enter the IP address',
|
||||
srcIpTip: 'Current sender IPv4 address',
|
||||
dstIp: 'Destination IP Address',
|
||||
dstIpPlease: 'Please enter the destination IP address',
|
||||
dstIpPlease: 'Please enter the IP address',
|
||||
dstIpTip: 'IPv4 address of the receiving end of the other party',
|
||||
interfaces: 'Signaling Interface',
|
||||
interfacesPlease: 'Please enter the signaling interface',
|
||||
signalPort: 'Signal Port',
|
||||
signalPortPlease: 'Please enter the signaling port',
|
||||
signalPortTip: 'Port corresponding to the interface',
|
||||
signalPortTip: 'Port of the side corresponding to the destination IP address or source IP address',
|
||||
rangePicker: 'Start/End Time',
|
||||
rangePickerPlease: 'Please select the start and end time of the task',
|
||||
comment: 'Task Description',
|
||||
commentPlease: 'Task description can be entered',
|
||||
remark: 'Remark',
|
||||
remarkPlease: 'Task description can be entered',
|
||||
addTask: 'Add Task',
|
||||
editTask: 'Modify Task',
|
||||
viewTask: 'View Task',
|
||||
errorTaskInfo: 'Failed to obtain task information',
|
||||
delTask: 'Successfully deleted task {num}',
|
||||
delTaskTip: 'Are you sure to delete the data item with record number {num}?',
|
||||
delTaskTip: 'Are you sure to delete the data item with record ID {id} ?',
|
||||
stopTask: 'Successful cessation of tasks {id}',
|
||||
stopTaskTip: 'Confirm stopping the task with record ID {id} ?',
|
||||
traceFile: "Tracking File",
|
||||
errMsg: "Error Message",
|
||||
imsiORmsisdn: "imsi or msisdn is null, cannot start task",
|
||||
},
|
||||
},
|
||||
faultManage: {
|
||||
@@ -1273,6 +1295,14 @@ export default {
|
||||
tailChar: 'End Characters',
|
||||
tailLines: 'End Lines',
|
||||
},
|
||||
exportFile:{
|
||||
fileName:'File Source',
|
||||
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",
|
||||
selectTip:"Please select File Name",
|
||||
}
|
||||
},
|
||||
monitor: {
|
||||
session: {
|
||||
@@ -1590,7 +1620,7 @@ export default {
|
||||
edgeTypeLineAnimateState: "Straight line with state animations",
|
||||
edgeLabelPositionStart: "Start",
|
||||
edgeLabelPositionMiddle: "Middle",
|
||||
edgeLabelPositionEnd: "End",
|
||||
edgeLabelPositionEnd: "End",
|
||||
nodeTypeCircle: "Circle",
|
||||
nodeTypeRect: "Rect",
|
||||
nodeTypeEllipse: "Ellipse",
|
||||
@@ -1949,7 +1979,7 @@ export default {
|
||||
componentTip:' The component path accessed by views in the page component directory, such as: system/user/index Note: The routing address without the .vue file suffix is the network address and can be filled in as a link',
|
||||
perms:`Permission identification example: monitor:server:query Permission identification is used in the back-end controller, such as:['monitor:server:query'] Permission identification is used in the front-end Vue page, such as: v-perms:has="['monitor:server:query']"`
|
||||
},
|
||||
dict:{
|
||||
dict:{
|
||||
dictInfo:'Dictionary Type Information',
|
||||
dictId:'Dictionary Number',
|
||||
dictName:'Dictionary Name',
|
||||
@@ -2028,9 +2058,9 @@ export default {
|
||||
stepInstallText: 'Select Install',
|
||||
stepInstallTip: 'Confirm the installation of the new version of the chosen Net Meta?',
|
||||
stepInstallModal: 'Network Element For Install',
|
||||
stepInstallNotNewVer: 'No new version found',
|
||||
stepInstallDone: 'Installation complete, service initialized',
|
||||
stepInstallFail: 'Installation fails, check if the service terminal environment is available!',
|
||||
stepInstallNotNewVer: 'No new version found',
|
||||
stepInstallDone: 'Installation complete, service initialized',
|
||||
stepInstallFail: 'Installation fails, check if the service terminal environment is available!',
|
||||
stepLicenseTitle: "Service License",
|
||||
stepLicenseDesc: "Obtaining a license activation code for authorization authentication",
|
||||
stepLicenseReload: 'Select Refresh',
|
||||
@@ -2119,6 +2149,30 @@ export default {
|
||||
hostSelectMore: "Load More {num}",
|
||||
hostSelectHeader: "Host List",
|
||||
},
|
||||
ps:{
|
||||
realTimeHigh:"High",
|
||||
realTimeLow:"Low",
|
||||
realTimeRegular:"Regular",
|
||||
realTimeStop:"Stop",
|
||||
realTime:"Real Time Speed",
|
||||
pid:"PID",
|
||||
name:"APP Name",
|
||||
username:"User Name",
|
||||
runTime:"Run Time",
|
||||
numThreads:"Thread",
|
||||
cpuPercent:"CPU Percent",
|
||||
diskRead:"Disk Read",
|
||||
diskWrite:"Disk Write",
|
||||
},
|
||||
net:{
|
||||
PID:"PID",
|
||||
name:"name",
|
||||
localAddr:"localAddr",
|
||||
remoteAddr:"remoteAddr",
|
||||
status:"status",
|
||||
type:"type",
|
||||
port:"port",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -149,6 +149,7 @@ export default {
|
||||
page403: '没有访问权限',
|
||||
page404: '找不到匹配页面',
|
||||
helpDoc: '系统使用文档',
|
||||
traceTaskHLR: '跟踪任务 HLR',
|
||||
lockScreen: '锁屏',
|
||||
account: {
|
||||
index: "个人中心",
|
||||
@@ -185,11 +186,11 @@ export default {
|
||||
helpDoc: '使用手册',
|
||||
},
|
||||
rightContent: {
|
||||
alarm: "活动告警",
|
||||
lock: "锁屏",
|
||||
lockTip: "确认要进行锁屏吗?",
|
||||
lockPasswd: "解锁密码",
|
||||
lockPasswdTip: "可不设置密码",
|
||||
helpDoc: "系统使用文档",
|
||||
fullscreen: "全屏显示",
|
||||
logout: "退出登录",
|
||||
profile: "个人中心",
|
||||
@@ -296,10 +297,10 @@ export default {
|
||||
newPassword: "新密码",
|
||||
newPasswordTip: "密码至少包含大小写字母、数字、特殊符号,且不少于6位",
|
||||
newPassworddPleace: "请输入新密码",
|
||||
confirmPassword: "确认新密码",
|
||||
confirmPassword: "确认新密码",
|
||||
confirmPasswordPleace: "请确认新密码",
|
||||
submit: "提交修改",
|
||||
reset: "重置",
|
||||
reset: "重置",
|
||||
submitTip: "确认要提交修改密码吗?",
|
||||
submitOkTip: "恭喜您,{num} 账号密码修改成功!",
|
||||
submitOk: "重新登录",
|
||||
@@ -606,7 +607,7 @@ export default {
|
||||
neIdTip: '填写网元绑定的唯一标识',
|
||||
rmUid: '资源唯一标识',
|
||||
rmUidPlease: '请输入资源唯一标识',
|
||||
rmUidTip: "用于网元日志、告警、指标等数据上报的标记",
|
||||
rmUidTip: "用于网元日志、告警、指标等数据上报的标记",
|
||||
neName: '网元名称',
|
||||
neNamePlease: '请输入网元名称',
|
||||
ipAddr: '服务IP',
|
||||
@@ -630,9 +631,9 @@ export default {
|
||||
log: '日志',
|
||||
},
|
||||
neInfo: {
|
||||
version: "网元版本",
|
||||
version: "网元版本",
|
||||
state: "网元状态",
|
||||
serviceState: "服务状态",
|
||||
serviceState: "服务状态",
|
||||
info: '状态信息',
|
||||
resourceInfo: '资源情况',
|
||||
sysMem: "系统内存",
|
||||
@@ -666,8 +667,8 @@ export default {
|
||||
backConf: {
|
||||
export: '配置导出',
|
||||
import: '配置导入',
|
||||
title: '配置文件导入',
|
||||
importType: '文件来源',
|
||||
title: '配置文件导入',
|
||||
importType: '文件来源',
|
||||
server:'服务器文件',
|
||||
local:'本地文件',
|
||||
localUpload:'本地上传',
|
||||
@@ -750,11 +751,11 @@ export default {
|
||||
status: "版本状态",
|
||||
upgradeBatch: "批量更新",
|
||||
upgradeBatchTip: "对勾选的记录进行新版本升级吗?",
|
||||
upgradeNotNewVer: '没有发现新版本',
|
||||
upgradeOMCVer: '拒绝批量操作升级OMC',
|
||||
upgradeDone: '更新完成,服务正在重载',
|
||||
upgradeFail: '更新失败,请检查软件文件是否存在且服务终端环境是否可用!',
|
||||
upgradeModal: '网元版本更新',
|
||||
upgradeNotNewVer: '没有发现新版本',
|
||||
upgradeOMCVer: '拒绝批量操作升级OMC',
|
||||
upgradeDone: '更新完成,服务正在重载',
|
||||
upgradeFail: '更新失败,请检查软件文件是否存在且服务终端环境是否可用!',
|
||||
upgradeModal: '网元版本更新',
|
||||
},
|
||||
neLicense: {
|
||||
status: "许可证状态",
|
||||
@@ -877,9 +878,10 @@ export default {
|
||||
imsiTip3: 'MSIN=移动客户识别码,采用等长10位数字构成',
|
||||
amfTip: '鉴权管理域,参数最大长度为 4',
|
||||
algoIndexTip: '算法索引,介于0到15之间',
|
||||
kiTip: '用户签权密钥信息,最大长度为32',
|
||||
kiTip: '用户签权密钥信息,长度只能是32',
|
||||
opcTip: '鉴权秘钥,OPC是由Ki和OP经过计算得来的,OP为运营商的根秘钥,ki是鉴权秘钥,最大长度为32',
|
||||
delSure:'确认删除IMSI编号为: {imsi} 的用户吗?',
|
||||
imsiConfirm:'IMSI的长度必须为15',
|
||||
},
|
||||
sub: {
|
||||
subInfo:'签约信息',
|
||||
@@ -940,7 +942,7 @@ export default {
|
||||
batchAddText: '批量新增',
|
||||
batchDelText: '批量删除',
|
||||
batchUpdateText: '批量更新',
|
||||
batchNum: '批量个数',
|
||||
batchNum: '批量个数',
|
||||
imsiTip: 'IMSI=MCC+MNC+MSIN',
|
||||
imsiTip1: 'MCC=移动国家号码, 由三位数字组成',
|
||||
imsiTip2: 'MNC=移动网络号,由两位数字组成',
|
||||
@@ -1059,6 +1061,7 @@ export default {
|
||||
},
|
||||
customTarget:{
|
||||
kpiId:'自定义指标项',
|
||||
kpiIdTip:'该网元没有自定义指标',
|
||||
period:'颗粒度',
|
||||
title:'自定义指标项标题',
|
||||
objectType:'对象类型',
|
||||
@@ -1069,7 +1072,14 @@ export default {
|
||||
delCustom:'成功删除记录编号为 {num} 自定义指标',
|
||||
addCustom:'添加自定义指标',
|
||||
editCustom:'编辑自定义指标',
|
||||
errorCustomInfo: '获取信息失败',
|
||||
errorCustomInfo: '获取信息失败',
|
||||
status: '状态',
|
||||
active:'正常',
|
||||
inactive:'停用',
|
||||
symbol:"符号",
|
||||
element:'元素',
|
||||
granularity:'颗粒度',
|
||||
unit:'单位',
|
||||
}
|
||||
},
|
||||
traceManage: {
|
||||
@@ -1095,18 +1105,23 @@ export default {
|
||||
pcap: {
|
||||
capArgPlease: '请输入tcpdump -i any支持参数',
|
||||
cmd: '命令',
|
||||
execCmd: "通用tcpdump抓包命令",
|
||||
execCmdsSctp: "过滤sctp和port命令",
|
||||
execUPFCmdA: '适合其他网元异常,UPF配合抓包的情况',
|
||||
execUPFCmdB: '适合UPF异常需要抓包分析的情况',
|
||||
execCmd: "通用命令选项",
|
||||
execCmd2: "过滤协议端口命令",
|
||||
execCmd3: "分割文件按时间单位秒 (-G 10 ),最多生成文件数量 (-W 7)",
|
||||
execUPFCmdA: '标准版-UPF配合其他网元异常抓包分析',
|
||||
execUPFCmdB: '标准版-UPF异常需要抓包分析',
|
||||
batchOper: '批量操作',
|
||||
batchStartText: '批量开始',
|
||||
batchStopText: '批量停止',
|
||||
batchDownText: '批量下载',
|
||||
fileView: '历史抓包文件',
|
||||
fileUPF: '标准版',
|
||||
fileUPFTip: 'UPF内部抓包分析包',
|
||||
textStart: "开始",
|
||||
textStartBatch: "批量开始",
|
||||
textStop: "停止",
|
||||
textStopBatch: "批量停止",
|
||||
textLog: "日志",
|
||||
textLogMsg: "日志信息",
|
||||
textDown: "下载",
|
||||
textDownBatch: "批量下载",
|
||||
downTip: "确认要下载 {title} 抓包数据文件吗?",
|
||||
downOk: "{title} 文件下载完成",
|
||||
downErr: "{title} 文件下载异常",
|
||||
@@ -1126,6 +1141,9 @@ export default {
|
||||
trackType: '跟踪类型',
|
||||
trackTypePlease: '请选择跟踪类型',
|
||||
creater: '创建人',
|
||||
textStop: "停止",
|
||||
status: '状态',
|
||||
time: '时间',
|
||||
startTime: '开始时间',
|
||||
endTime: '结束时间',
|
||||
msisdn: 'MSISDN',
|
||||
@@ -1144,17 +1162,21 @@ export default {
|
||||
interfacesPlease: '请输入信令接口',
|
||||
signalPort: '信令端口',
|
||||
signalPortPlease: '请输入信令端口',
|
||||
signalPortTip: '接口对应的端口',
|
||||
signalPortTip: '目标IP地址或源IP地址对应一方的端口',
|
||||
rangePicker: '开始结束时间',
|
||||
rangePickerPlease: '请选择任务时间开始结束时间',
|
||||
comment: '任务说明',
|
||||
commentPlease: '可输入任务说明',
|
||||
remark: '说明',
|
||||
remarkPlease: '可输入任务说明',
|
||||
addTask: '添加任务',
|
||||
editTask: '修改任务',
|
||||
viewTask: '查看任务',
|
||||
errorTaskInfo: '获取任务信息失败',
|
||||
delTask: '成功删除任务 {num}',
|
||||
delTaskTip: '确认删除记录编号为 {num} 的数据项?',
|
||||
delTaskTip: '确认删除记录编号为 {id} 的数据项?',
|
||||
stopTask: '成功停止任务 {id}',
|
||||
stopTaskTip: '确认停止记录编号为 {id} 的任务?',
|
||||
traceFile: "跟踪文件",
|
||||
errMsg: "错误信息",
|
||||
imsiORmsisdn: "imsi 或 msisdn 是空值,不能开始任务",
|
||||
},
|
||||
},
|
||||
faultManage: {
|
||||
@@ -1273,7 +1295,15 @@ export default {
|
||||
tailChar: '末尾字数',
|
||||
tailLines: '末尾行数',
|
||||
},
|
||||
},
|
||||
exportFile:{
|
||||
fileName:'文件来源',
|
||||
downTip: "确认下载文件名为 【{fileName}】 文件?",
|
||||
downTipErr: "文件获取失败",
|
||||
deleteTip: "确认删除文件名为 【{fileName}】 文件?",
|
||||
deleteTipErr: "文件删除失败",
|
||||
selectTip:"请选择文件名",
|
||||
}
|
||||
},
|
||||
monitor: {
|
||||
session: {
|
||||
userName: "登录账号",
|
||||
@@ -1590,7 +1620,7 @@ export default {
|
||||
edgeTypeLineAnimateState: "直线,含有状态动画",
|
||||
edgeLabelPositionStart: "开头",
|
||||
edgeLabelPositionMiddle: "中间",
|
||||
edgeLabelPositionEnd: "末尾",
|
||||
edgeLabelPositionEnd: "末尾",
|
||||
nodeTypeCircle: "圆形",
|
||||
nodeTypeRect: "矩形",
|
||||
nodeTypeEllipse: "椭圆",
|
||||
@@ -1916,8 +1946,8 @@ export default {
|
||||
unlockSure:'确认解锁用户 【{username}】 数据项?',
|
||||
},
|
||||
},
|
||||
menu:{
|
||||
menuInfo:'菜单信息',
|
||||
menu:{
|
||||
menuInfo:'菜单信息',
|
||||
menuName:'菜单名称',
|
||||
menuId:'菜单编号',
|
||||
menuSort:'菜单排序',
|
||||
@@ -1949,7 +1979,7 @@ export default {
|
||||
componentTip:' 页面组件目录 views 访问的组件路径,如:system/user/index 注意:不带 .vue 文件后缀路由地址是网络地址可填入链接',
|
||||
perms:`权限标识示例:monitor:server:query 后端控制器中使用权限标识,如:['monitor:server:query'] 前端vue页面中使用权限标识,如:v-perms:has="['monitor:server:query']"`
|
||||
},
|
||||
dict:{
|
||||
dict:{
|
||||
dictInfo:'字典类型信息',
|
||||
dictId:'字典编号',
|
||||
dictName:'字典名称',
|
||||
@@ -2028,9 +2058,9 @@ export default {
|
||||
stepInstallText: '选择安装',
|
||||
stepInstallTip: '确认安装选择的网元新版本吗?',
|
||||
stepInstallModal: '网元进行安装',
|
||||
stepInstallNotNewVer: '没有发现新版本',
|
||||
stepInstallDone: '安装完成,服务进入初始化',
|
||||
stepInstallFail: '安装失败,请检查服务终端环境是否可用!',
|
||||
stepInstallNotNewVer: '没有发现新版本',
|
||||
stepInstallDone: '安装完成,服务进入初始化',
|
||||
stepInstallFail: '安装失败,请检查服务终端环境是否可用!',
|
||||
stepLicenseTitle: "网元许可授权",
|
||||
stepLicenseDesc: "获取网元许可激活码进行授权认证",
|
||||
stepLicenseReload: '选择刷新许可证',
|
||||
@@ -2119,6 +2149,30 @@ export default {
|
||||
hostSelectMore: "加载更多 {num}",
|
||||
hostSelectHeader: "主机列表",
|
||||
},
|
||||
ps:{
|
||||
realTimeHigh:"高",
|
||||
realTimeLow:"低",
|
||||
realTimeRegular:"常规",
|
||||
realTimeStop:"已暂停",
|
||||
realTime:"实时更新速度",
|
||||
pid:"PID",
|
||||
name:"应用名称",
|
||||
username:"用户名",
|
||||
runTime:"运行时间",
|
||||
numThreads:"线程数",
|
||||
cpuPercent:"CPU使用率",
|
||||
diskRead:"磁盘读取",
|
||||
diskWrite:"磁盘写入",
|
||||
},
|
||||
net:{
|
||||
PID:"PID",
|
||||
name:"名称",
|
||||
localAddr:"localAddr",
|
||||
remoteAddr:"remoteAddr",
|
||||
status:"状态",
|
||||
type:"类型",
|
||||
port:"接口",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -59,7 +59,7 @@ watch(
|
||||
// 路由地址含有内嵌地址标识又是隐藏菜单需要处理打开高亮菜单栏
|
||||
if (v.path.includes(MENU_PATH_INLINE) && v.meta.hideInMenu) {
|
||||
const idx = v.path.lastIndexOf(MENU_PATH_INLINE);
|
||||
layoutState.openKeys.splice(-1);
|
||||
layoutState.openKeys = layoutState.selectedKeys.slice(0, -1);
|
||||
layoutState.selectedKeys[matched.length - 1] = v.path.slice(0, idx);
|
||||
}
|
||||
},
|
||||
@@ -435,7 +435,8 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
& #serverTimeDom {
|
||||
color: #00000085;
|
||||
color: inherit;
|
||||
opacity: 0.85;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,16 +64,6 @@ function fnClickAlarm() {
|
||||
router.push({ name: 'ActiveAlarm_2088' });
|
||||
}
|
||||
|
||||
/**系统使用手册跳转 */
|
||||
function fnClickHelpDoc(language?: string) {
|
||||
const routeData = router.resolve({ name: 'HelpDoc' });
|
||||
let href = routeData.href;
|
||||
if (language) {
|
||||
href = `${routeData.href}?language=${language}`;
|
||||
}
|
||||
window.open(href, '_blank');
|
||||
}
|
||||
|
||||
/**改变多语言 */
|
||||
function fnChangeLocale(e: any) {
|
||||
changeLocale(e.key);
|
||||
@@ -82,23 +72,23 @@ function fnChangeLocale(e: any) {
|
||||
|
||||
<template>
|
||||
<a-space :size="12" align="center">
|
||||
<a-button
|
||||
type="text"
|
||||
style="color: inherit"
|
||||
@click="fnClickAlarm"
|
||||
v-roles:has="[TENANTADMIN_ROLE_KEY]"
|
||||
>
|
||||
<template #icon>
|
||||
<a-badge
|
||||
:count="useAlarmStore().activeAlarmTotal"
|
||||
:overflow-count="99"
|
||||
status="warning"
|
||||
style="color: inherit"
|
||||
>
|
||||
<BellOutlined />
|
||||
</a-badge>
|
||||
</template>
|
||||
</a-button>
|
||||
<span v-roles:has="[TENANTADMIN_ROLE_KEY]">
|
||||
<a-tooltip placement="bottom">
|
||||
<template #title>{{ t('loayouts.rightContent.alarm') }}</template>
|
||||
<a-button type="text" style="color: inherit" @click="fnClickAlarm">
|
||||
<template #icon>
|
||||
<a-badge
|
||||
:count="useAlarmStore().activeAlarmTotal"
|
||||
:overflow-count="99"
|
||||
status="warning"
|
||||
style="color: inherit"
|
||||
>
|
||||
<BellOutlined />
|
||||
</a-badge>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
|
||||
<!-- 锁屏操作 -->
|
||||
<span v-perms:has="['system:setting:lock']">
|
||||
@@ -138,23 +128,6 @@ function fnChangeLocale(e: any) {
|
||||
</a-tooltip>
|
||||
</span>
|
||||
|
||||
<!-- 用户帮助手册 -->
|
||||
<span v-perms:has="['system:setting:doc']">
|
||||
<a-tooltip placement="bottom">
|
||||
<template #title>{{ t('loayouts.rightContent.helpDoc') }}</template>
|
||||
<a-button
|
||||
type="text"
|
||||
style="color: inherit"
|
||||
@click="fnClickHelpDoc()"
|
||||
v-roles:has="[TENANTADMIN_ROLE_KEY]"
|
||||
>
|
||||
<template #icon>
|
||||
<QuestionCircleOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
|
||||
<a-tooltip placement="bottom">
|
||||
<template #title>{{ t('loayouts.rightContent.fullscreen') }}</template>
|
||||
<a-button type="text" style="color: inherit" @click="toggle">
|
||||
|
||||
@@ -196,6 +196,8 @@ function beforeRequest(options: OptionsType): OptionsType | Promise<any> {
|
||||
}
|
||||
}
|
||||
|
||||
if (options.method === 'get') return options;
|
||||
|
||||
// 非get参数提交
|
||||
let body = options.data
|
||||
if (body instanceof FormData) {
|
||||
|
||||
124
src/plugins/wk-worker.ts
Normal file
124
src/plugins/wk-worker.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
/**连接参数类型 */
|
||||
export type OptionsType = {
|
||||
/**
|
||||
* 线路工作服务地址
|
||||
*
|
||||
* self.onmessage = error => {} - 接收消息
|
||||
*
|
||||
* self.postMessage() -回复消息
|
||||
*/
|
||||
url: string;
|
||||
/**message事件的回调函数 */
|
||||
onmessage: (data: any) => void;
|
||||
/**error事件的回调函数 */
|
||||
onerror: Function;
|
||||
/**close事件的回调函数 */
|
||||
onclose?: (code: number) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Web Worker 使用方法
|
||||
*
|
||||
* import { WK, OptionsType } from '@/plugins/wk-worker';
|
||||
*
|
||||
* const wk = new WK();
|
||||
*
|
||||
* 创建连接
|
||||
* const options: OptionsType = { };
|
||||
* wk.connect(options);
|
||||
*
|
||||
* 手动关闭
|
||||
* wk.close();
|
||||
*/
|
||||
export class WK {
|
||||
/**wk 实例 */
|
||||
private wk: Worker | null = null;
|
||||
/**wk 连接参数 */
|
||||
private options: OptionsType | null = null;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param {object} params 构造函数参数
|
||||
*/
|
||||
constructor(options?: OptionsType) {
|
||||
if (!window.Worker) {
|
||||
// 检测浏览器支持
|
||||
console.error('Sorry! Browser does not support Web Workers');
|
||||
return;
|
||||
}
|
||||
options && this.connect(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建链接
|
||||
* @param {object} options 连接参数
|
||||
*/
|
||||
public connect(options: OptionsType) {
|
||||
this.options = options;
|
||||
try {
|
||||
const wk = new Worker(options.url);
|
||||
// 用于收到来自其消息时的回调函数。
|
||||
wk.onmessage = ev => {
|
||||
// 解析文本消息
|
||||
if (ev.type === 'message') {
|
||||
try {
|
||||
if (typeof options.onmessage === 'function') {
|
||||
options.onmessage(ev.data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('worker message formatting error', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
// 用于发生错误关闭后的回调函数。
|
||||
wk.onmessageerror = ev => {
|
||||
console.error('worker message error anomaly', ev);
|
||||
|
||||
if (typeof options.onclose === 'function') {
|
||||
options.onerror(ev);
|
||||
}
|
||||
};
|
||||
// 用于发生错误后的回调函数。
|
||||
wk.onerror = ev => {
|
||||
console.error('worker error anomaly', ev);
|
||||
|
||||
if (typeof options.onerror === 'function') {
|
||||
options.onerror(ev);
|
||||
}
|
||||
};
|
||||
this.wk = wk;
|
||||
} catch (error) {
|
||||
if (typeof options.onerror === 'function') {
|
||||
options.onerror(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
* @param data JSON数据
|
||||
* @returns
|
||||
*/
|
||||
public send(data: Record<string, any>): boolean {
|
||||
if (!this.wk) {
|
||||
console.warn('worker unavailable');
|
||||
return false;
|
||||
}
|
||||
|
||||
this.wk.postMessage(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 手动关闭会被立即终止
|
||||
public close() {
|
||||
if (!this.wk) {
|
||||
console.warn('worker unavailable');
|
||||
return;
|
||||
}
|
||||
this.wk.terminate();
|
||||
// 用于关闭后的回调函数。
|
||||
if (this.options && typeof this.options.onclose === 'function') {
|
||||
this.options.onclose(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ export type OptionsType = {
|
||||
/**
|
||||
* WebSocket 使用方法
|
||||
*
|
||||
* import WS from '@/plugins/ws-websocket.ts';
|
||||
* import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
*
|
||||
* const ws = new WS();
|
||||
*
|
||||
|
||||
@@ -87,6 +87,12 @@ const constantRoutes: RouteRecordRaw[] = [
|
||||
meta: { title: 'router.helpDoc' },
|
||||
component: () => import('@/views/tool/help/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/trace-task-hlr',
|
||||
name: 'TraceTaskHLR',
|
||||
meta: { title: 'router.traceTaskHLR' },
|
||||
component: () => import('@/views/traceManage/task-hlr/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/quick-start',
|
||||
name: 'QuickStart',
|
||||
@@ -154,6 +160,7 @@ const WHITE_LIST: string[] = [
|
||||
'/help',
|
||||
'/register',
|
||||
'/quick-start',
|
||||
'/trace-task-hlr',
|
||||
];
|
||||
|
||||
/**全局路由-前置守卫 */
|
||||
|
||||
@@ -2,7 +2,7 @@ import { defineStore } from 'pinia';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { listAllNeInfo } from '@/api/ne/neInfo';
|
||||
import { parseDataToOptions } from '@/utils/parse-tree-utils';
|
||||
import { getNeTraceInterfaceAll } from '@/api/traceManage/task';
|
||||
import { getNeTraceInterfaceAll } from '@/api/trace/task';
|
||||
import { getNePerformanceList } from '@/api/perfManage/taskManage';
|
||||
|
||||
/**网元信息类型 */
|
||||
|
||||
@@ -37,7 +37,7 @@ export function parseStrLineToHump(str: string): string {
|
||||
*/
|
||||
export function parseStrHumpToLine(str: string): string {
|
||||
if (!str) return str;
|
||||
return str.replace(/([A-Z])/g, '_$1').toLowerCase();
|
||||
return str.replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,6 +63,9 @@ export function parseObjHumpToLine(obj: any): any {
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
if (typeof obj === 'string') {
|
||||
return parseStrHumpToLine(obj);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -89,9 +92,28 @@ export function parseObjLineToHump(obj: any): any {
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
if (typeof obj === 'string') {
|
||||
return parseStrLineToHump(obj);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化文件大小
|
||||
* @param bytes 字节数
|
||||
* @param decimalPlaces 保留小数位,默认2位
|
||||
* @returns 单位 xB
|
||||
*/
|
||||
export function parseSizeFromFile(bytes: number, decimalPlaces: number = 2) {
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
let i = 0;
|
||||
while (bytes >= 1024 && i < units.length - 1) {
|
||||
bytes /= 1024;
|
||||
i++;
|
||||
}
|
||||
return `${bytes.toFixed(decimalPlaces || 1)} ${units[i]}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换磁盘容量
|
||||
* @param size 数值大小
|
||||
|
||||
@@ -429,7 +429,12 @@ onMounted(() => {
|
||||
dict.ueAauthCode = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.ueEventType = resArr[1].value;
|
||||
dict.ueEventType = resArr[1].value.map(item => {
|
||||
if (item.value === 'cm-state') {
|
||||
item.label = item.label.replace('CM', 'ECM');
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
if (resArr[2].status === 'fulfilled') {
|
||||
dict.ueEventCmState = resArr[2].value;
|
||||
|
||||
@@ -251,36 +251,27 @@ function fnChangeData(data: any[], itemID: string) {
|
||||
let nfCpuUsage = 0;
|
||||
if (info.neState.cpu) {
|
||||
nfCpuUsage = info.neState.cpu.nfCpuUsage;
|
||||
const nfCpu = +(info.neState.cpu.nfCpuUsage / 100);
|
||||
nfCpuUsage = +nfCpu.toFixed(2);
|
||||
if (nfCpuUsage > 100) {
|
||||
const nfCpu = +(info.neState.cpu.nfCpuUsage / 100);
|
||||
if (nfCpu > 100) {
|
||||
nfCpuUsage = 100;
|
||||
} else {
|
||||
nfCpuUsage = +nfCpu.toFixed(2);
|
||||
}
|
||||
nfCpuUsage = 100;
|
||||
}
|
||||
|
||||
sysCpuUsage = info.neState.cpu.sysCpuUsage;
|
||||
let sysCpu = +(info.neState.cpu.sysCpuUsage / 100);
|
||||
sysCpuUsage = +sysCpu.toFixed(2);
|
||||
if (sysCpuUsage > 100) {
|
||||
const sysCpu = +(info.neState.cpu.sysCpuUsage / 100);
|
||||
if (sysCpu > 100) {
|
||||
sysCpuUsage = 100;
|
||||
} else {
|
||||
sysCpuUsage = +sysCpu.toFixed(2);
|
||||
}
|
||||
sysCpuUsage = 100;
|
||||
}
|
||||
}
|
||||
|
||||
let sysMemUsage = 0;
|
||||
if (info.neState.mem) {
|
||||
let men = info.neState.mem.sysMemUsage;
|
||||
if (men > 100) {
|
||||
men = +(men / 100).toFixed(2);
|
||||
const men = info.neState.mem.sysMemUsage;
|
||||
sysMemUsage = +(men / 100).toFixed(2);
|
||||
if (sysMemUsage > 100) {
|
||||
sysMemUsage = 100;
|
||||
}
|
||||
if (men > 100) {
|
||||
men = 100;
|
||||
}
|
||||
sysMemUsage = men;
|
||||
}
|
||||
|
||||
let sysDiskUsage = 0;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { message, Modal } 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 useDictStore from '@/store/modules/dict';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import {
|
||||
RESULT_CODE_ERROR,
|
||||
@@ -16,14 +17,23 @@ import {
|
||||
exportSMSCDataCDR,
|
||||
listSMSCDataCDR,
|
||||
} from '@/api/neData/smsc';
|
||||
import { parseDateToStr, parseDuration } from '@/utils/date-utils';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import saveAs from 'file-saver';
|
||||
import PQueue from 'p-queue';
|
||||
const { getDict } = useDictStore();
|
||||
const { t } = useI18n();
|
||||
const ws = new WS();
|
||||
const queue = new PQueue({ concurrency: 1, autoStart: true });
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**CDR 响应原因代码类别类型 */
|
||||
cdrCauseCode: DictType[];
|
||||
} = reactive({
|
||||
cdrCauseCode: [],
|
||||
});
|
||||
|
||||
/**网元可选 */
|
||||
let neOtions = ref<Record<string, any>[]>([]);
|
||||
|
||||
@@ -158,12 +168,6 @@ let tableColumns: ColumnsType = [
|
||||
key: 'cause',
|
||||
align: 'left',
|
||||
width: 120,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
return +cdrJSON.result
|
||||
? t('views.dashboard.cdr.resultOk')
|
||||
: t('views.dashboard.cdr.resultFail');
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.time'),
|
||||
@@ -426,6 +430,12 @@ function wsMessage(res: Record<string, any>) {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('cdr_cause_code')]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.cdrCauseCode = resArr[0].value;
|
||||
}
|
||||
});
|
||||
// 获取网元网元列表
|
||||
useNeInfoStore()
|
||||
.fnNelist()
|
||||
@@ -661,6 +671,18 @@ onBeforeUnmount(() => {
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'cause'">
|
||||
<span v-if="record.cdrJSON.result === 0">
|
||||
<DictTag
|
||||
:options="dict.cdrCauseCode"
|
||||
:value="record.cdrJSON.cause"
|
||||
value-default="0"
|
||||
/>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ t('views.dashboard.cdr.resultOk') }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
@@ -711,12 +733,15 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.result') }}: </span>
|
||||
<span>
|
||||
{{
|
||||
+record.cdrJSON.result
|
||||
? t('views.dashboard.cdr.resultOk')
|
||||
: t('views.dashboard.cdr.resultFail')
|
||||
}}
|
||||
<span v-if="record.cdrJSON.result === 0">
|
||||
<DictTag
|
||||
:options="dict.cdrCauseCode"
|
||||
:value="record.cdrJSON.cause"
|
||||
value-default="0"
|
||||
/>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ t('views.dashboard.cdr.resultOk') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -611,6 +611,17 @@ function fnShowSet() {
|
||||
});
|
||||
}
|
||||
|
||||
// key替换中文title
|
||||
function mapKeysWithReduce(data: any, titleMapping: any) {
|
||||
return data.map((item:any) => {
|
||||
return Object.keys(item).reduce((newItem:any, key:any) => {
|
||||
const title = titleMapping[key] || key;
|
||||
newItem[title] = item[key];
|
||||
return newItem;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出全部
|
||||
*/
|
||||
@@ -622,39 +633,56 @@ function fnExportAll() {
|
||||
const key = 'exportAlarm';
|
||||
message.loading({ content: t('common.loading'), key });
|
||||
let sortArr: any = [];
|
||||
let writeSheetOpt: any = [];
|
||||
let mappArr: any = {};
|
||||
tableColumnsDnd.value.forEach((item: any) => {
|
||||
if (item.dataIndex) sortArr.push(item.dataIndex);
|
||||
if (item.dataIndex) {
|
||||
sortArr.push(item.dataIndex);
|
||||
writeSheetOpt.push(item.title);
|
||||
mappArr[item.dataIndex] = item.title;
|
||||
}
|
||||
});
|
||||
|
||||
// 排序字段
|
||||
//提供给writeSheet排序参数
|
||||
const sortData = {
|
||||
header: sortArr,
|
||||
header: writeSheetOpt,
|
||||
};
|
||||
|
||||
exportAll(queryParams).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
res.data = res.data.map((objA: any) => {
|
||||
let filteredObj: any = {};
|
||||
//sort Data
|
||||
sortArr.forEach((key: any) => {
|
||||
if (objA.hasOwnProperty(key)) {
|
||||
filteredObj[key] = objA[key];
|
||||
}
|
||||
});
|
||||
dict.activeAckState.map((item: any) => {
|
||||
if (item.value === `${filteredObj.ackState}`)
|
||||
filteredObj.ackState = item.label;
|
||||
});
|
||||
const mapProps = (dict: any, key: string) => {
|
||||
const item = dict.find(
|
||||
(item: any) => item.value === `${filteredObj[key]}`
|
||||
);
|
||||
if (item) {
|
||||
filteredObj[key] = item.label;
|
||||
}
|
||||
};
|
||||
|
||||
mapProps(dict.activeAckState, 'ackState');
|
||||
mapProps(dict.activeClearType, 'clearType');
|
||||
mapProps(dict.activeAlarmSeverity, 'origSeverity');
|
||||
mapProps(dict.activeAlarmType, 'alarmType');
|
||||
|
||||
return filteredObj;
|
||||
});
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: t('common.export') }),
|
||||
key,
|
||||
duration: 3,
|
||||
|
||||
res.data = mapKeysWithReduce(res.data, mappArr);
|
||||
writeSheet(res.data, 'alarm', sortData).then(fileBlob => {
|
||||
saveAs(fileBlob, `alarm_${Date.now()}.xlsx`);
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: t('common.export') }),
|
||||
key,
|
||||
duration: 3,
|
||||
});
|
||||
});
|
||||
writeSheet(res.data, 'alarm', sortData).then(fileBlob =>
|
||||
saveAs(fileBlob, `alarm_${Date.now()}.xlsx`)
|
||||
);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
|
||||
@@ -308,6 +308,17 @@ const onSelectChange = (
|
||||
state.selectedRow = record;
|
||||
};
|
||||
|
||||
// key替换中文title
|
||||
function mapKeysWithReduce(data: any, titleMapping: any) {
|
||||
return data.map((item:any) => {
|
||||
return Object.keys(item).reduce((newItem:any, key:any) => {
|
||||
const title = titleMapping[key] || key;
|
||||
newItem[title] = item[key];
|
||||
return newItem;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出全部
|
||||
*/
|
||||
@@ -319,13 +330,18 @@ function fnExportAll() {
|
||||
const key = 'exportAlarm';
|
||||
message.loading({ content: t('common.loading'), key });
|
||||
let sortArr: any = [];
|
||||
let writeSheetOpt: any = [];
|
||||
let mappArr: any = {};
|
||||
tableColumnsDnd.value.forEach((item: any) => {
|
||||
if (item.dataIndex) sortArr.push(item.dataIndex);
|
||||
if (item.dataIndex) {
|
||||
sortArr.push(item.dataIndex);
|
||||
writeSheetOpt.push(item.title);
|
||||
mappArr[item.dataIndex] = item.title;
|
||||
}
|
||||
});
|
||||
|
||||
// 排序字段
|
||||
const sortData = {
|
||||
header: sortArr,
|
||||
header: writeSheetOpt,
|
||||
};
|
||||
|
||||
exportAll(queryParams).then(res => {
|
||||
@@ -337,21 +353,20 @@ function fnExportAll() {
|
||||
filteredObj[key] = objA[key];
|
||||
}
|
||||
});
|
||||
dict.activeAckState.map((item: any) => {
|
||||
if (item.value === `${filteredObj.ackState}`)
|
||||
filteredObj.ackState = item.label;
|
||||
});
|
||||
|
||||
return filteredObj;
|
||||
});
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: t('common.export') }),
|
||||
key,
|
||||
duration: 3,
|
||||
res.data = mapKeysWithReduce(res.data, mappArr);
|
||||
|
||||
writeSheet(res.data, 'alarm', sortData).then(fileBlob => {
|
||||
saveAs(fileBlob, `evnet_${Date.now()}.xlsx`);
|
||||
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: t('common.export') }),
|
||||
key,
|
||||
duration: 3,
|
||||
});
|
||||
});
|
||||
writeSheet(res.data, 'alarm', sortData).then(fileBlob =>
|
||||
saveAs(fileBlob, `evnet_${Date.now()}.xlsx`)
|
||||
);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
@@ -625,11 +640,6 @@ onMounted(() => {
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:row-selection="{
|
||||
columnWidth: 2,
|
||||
selectedRowKeys: state.selectedRowKeys,
|
||||
onChange: onSelectChange,
|
||||
}"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: 2500, y: 400 }"
|
||||
>
|
||||
|
||||
@@ -426,6 +426,17 @@ function fnCancelConfirm() {
|
||||
});
|
||||
}
|
||||
|
||||
// key替换中文title
|
||||
function mapKeysWithReduce(data: any, titleMapping: any) {
|
||||
return data.map((item:any) => {
|
||||
return Object.keys(item).reduce((newItem:any, key:any) => {
|
||||
const title = titleMapping[key] || key;
|
||||
newItem[title] = item[key];
|
||||
return newItem;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出全部
|
||||
*/
|
||||
@@ -437,12 +448,18 @@ function fnExportAll() {
|
||||
const key = 'exportAlarmHis';
|
||||
message.loading({ content: t('common.loading'), key });
|
||||
let sortArr: any = [];
|
||||
let writeSheetOpt: any = [];
|
||||
let mappArr: any = {};
|
||||
tableColumnsDnd.value.forEach((item: any) => {
|
||||
if (item.dataIndex) sortArr.push(item.dataIndex);
|
||||
if (item.dataIndex) {
|
||||
sortArr.push(item.dataIndex);
|
||||
writeSheetOpt.push(item.title);
|
||||
mappArr[item.dataIndex] = item.title;
|
||||
}
|
||||
});
|
||||
// 排序字段
|
||||
//提供给writeSheet排序参数
|
||||
const sortData = {
|
||||
header: sortArr,
|
||||
header: writeSheetOpt,
|
||||
};
|
||||
exportAll(queryParams).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
@@ -470,14 +487,17 @@ function fnExportAll() {
|
||||
|
||||
return filteredObj;
|
||||
});
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: t('common.export') }),
|
||||
key,
|
||||
duration: 3,
|
||||
|
||||
res.data = mapKeysWithReduce(res.data, mappArr);
|
||||
|
||||
writeSheet(res.data, 'alarm', sortData).then(fileBlob => {
|
||||
saveAs(fileBlob, `history-alarm_${Date.now()}.xlsx`);
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: t('common.export') }),
|
||||
key,
|
||||
duration: 3,
|
||||
});
|
||||
});
|
||||
writeSheet(res.data, 'alarm', sortData).then(fileBlob =>
|
||||
saveAs(fileBlob, `history-alarm_${Date.now()}.xlsx`)
|
||||
);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
|
||||
362
src/views/logManage/exportFile/index.vue
Normal file
362
src/views/logManage/exportFile/index.vue
Normal file
@@ -0,0 +1,362 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, toRaw } 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 { Modal, message } from 'ant-design-vue/lib';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import {
|
||||
getBakFile,
|
||||
getBakFileList,
|
||||
downFile,
|
||||
delFile,
|
||||
} from '@/api/logManage/exportFile';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import saveAs from 'file-saver';
|
||||
const { t } = useI18n();
|
||||
|
||||
/**网元参数 */
|
||||
let logSelect = ref<string[]>([]);
|
||||
|
||||
/**文件列表 */
|
||||
let fileList = ref<any>([]);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**读取路径 */
|
||||
path: '',
|
||||
/**表名 */
|
||||
tableName: '',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'small',
|
||||
data: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('views.logManage.neFile.fileMode'),
|
||||
dataIndex: 'fileMode',
|
||||
align: 'center',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.logManage.neFile.owner'),
|
||||
dataIndex: 'owner',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.logManage.neFile.group'),
|
||||
dataIndex: 'group',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.logManage.neFile.size'),
|
||||
dataIndex: 'size',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.logManage.neFile.modifiedTime'),
|
||||
dataIndex: 'modifiedTime',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value * 1000);
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.logManage.neFile.fileName'),
|
||||
dataIndex: 'fileName',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'fileName',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
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();
|
||||
},
|
||||
});
|
||||
|
||||
/**下载触发等待 */
|
||||
let downLoading = ref<boolean>(false);
|
||||
|
||||
/**删除触发等待 */
|
||||
let delLoading = ref<boolean>(false);
|
||||
|
||||
/**信息文件下载 */
|
||||
function fnDownloadFile(row: Record<string, any>) {
|
||||
if (downLoading.value) return;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.logManage.exportFile.downTip', {
|
||||
fileName: row.fileName,
|
||||
}),
|
||||
onOk() {
|
||||
downLoading.value = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
downFile({
|
||||
path: queryParams.path,
|
||||
fileName: row.fileName,
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', {
|
||||
msg: t('common.downloadText'),
|
||||
}),
|
||||
duration: 2,
|
||||
});
|
||||
saveAs(res.data, `${row.fileName}`);
|
||||
} else {
|
||||
message.error({
|
||||
content: t('views.logManage.exportFile.downTipErr'),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
downLoading.value = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function fnRecordDelete(row: Record<string, any>) {
|
||||
if (delLoading.value) return;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.logManage.exportFile.deleteTip', {
|
||||
fileName: row.fileName,
|
||||
}),
|
||||
onOk() {
|
||||
const key = 'delFile';
|
||||
delLoading.value = true;
|
||||
message.loading({ content: t('common.loading'), key });
|
||||
delFile({
|
||||
fileName: row.fileName,
|
||||
path: queryParams.path,
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('views.system.user.delSuss'),
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key: key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
delLoading.value = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**网元类型选择对应修改 */
|
||||
function fnNeChange(keys: any, opt: any) {
|
||||
queryParams.tableName = keys;
|
||||
queryParams.path = opt.path;
|
||||
fnGetList(1);
|
||||
}
|
||||
|
||||
/**查询备份信息列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (queryParams.tableName === '') {
|
||||
message.warning({
|
||||
content: t('views.logManage.exportFile.selectTip'),
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
getBakFileList(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
tablePagination.total = res.total;
|
||||
tableState.data = res.data;
|
||||
if (
|
||||
tablePagination.total <=
|
||||
(queryParams.pageNum - 1) * tablePagination.pageSize &&
|
||||
queryParams.pageNum !== 1
|
||||
) {
|
||||
tableState.loading = false;
|
||||
fnGetList(queryParams.pageNum - 1);
|
||||
}
|
||||
} else {
|
||||
message.error(res.msg, 3);
|
||||
tablePagination.total = 0;
|
||||
tableState.data = [];
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getBakFile().then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
res.data.forEach((item: any) => {
|
||||
fileList.value.push({
|
||||
value: item.tableName,
|
||||
label: item.tableDisplay,
|
||||
path: item.filePath,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
// .finally(() => {
|
||||
// fnGetList();
|
||||
// });
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.logManage.exportFile.fileName')"
|
||||
name="fileName"
|
||||
style="margin-bottom: 0"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="logSelect"
|
||||
:options="fileList"
|
||||
@change="fnNeChange"
|
||||
:allow-clear="false"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="16" :md="18" :xs="24" v-if="queryParams.path">
|
||||
<a-form-item
|
||||
:label="t('views.logManage.neFile.nePath')"
|
||||
name="configName"
|
||||
style="margin-bottom: 0"
|
||||
>
|
||||
<a-breadcrumb>
|
||||
<a-breadcrumb-item>
|
||||
{{ queryParams.path }}
|
||||
</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="fileName"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: 800 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'fileName'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-button
|
||||
type="link"
|
||||
:loading="downLoading"
|
||||
@click.prevent="fnDownloadFile(record)"
|
||||
v-if="record.fileType === 'file'"
|
||||
>
|
||||
<template #icon><DownloadOutlined /></template>
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
:loading="delLoading"
|
||||
@click.prevent="fnRecordDelete(record)"
|
||||
v-if="record.fileType === 'file'"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -148,6 +148,7 @@ function fnDownloadFile(row: Record<string, any>) {
|
||||
neId: queryParams.neId,
|
||||
path: queryParams.path,
|
||||
fileName: row.fileName,
|
||||
delTemp: true,
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
|
||||
@@ -67,6 +67,22 @@ export default function useConfigArray({
|
||||
row[v.name] = Object.assign({}, v);
|
||||
}
|
||||
|
||||
// 特殊SMF-upfid选择
|
||||
if (neTypeSelect.value[0] === 'SMF' && Reflect.has(row, 'upfId')) {
|
||||
const v = row.upfId.value;
|
||||
if (typeof v === 'string') {
|
||||
if (v === '') {
|
||||
row.upfId.value = [];
|
||||
} else if (v.includes(';')) {
|
||||
row.upfId.value = v.split(';');
|
||||
} else if (v.includes(',')) {
|
||||
row.upfId.value = v.split(',');
|
||||
} else {
|
||||
row.upfId.value = [v];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modalState.from = row;
|
||||
modalState.type = 'arrayEdit';
|
||||
modalState.title = `${treeState.selectNode.paramDisplay} ${from.title}`;
|
||||
@@ -87,6 +103,15 @@ export default function useConfigArray({
|
||||
/**多列表编辑确认 */
|
||||
function arrayEditOk(from: Record<string, any>) {
|
||||
const loc = `${from['index']['value']}`;
|
||||
|
||||
// 特殊SMF-upfid选择
|
||||
if (neTypeSelect.value[0] === 'SMF' && Reflect.has(from, 'upfId')) {
|
||||
const v = from.upfId.value;
|
||||
if (Array.isArray(v)) {
|
||||
from.upfId.value = v.join(';');
|
||||
}
|
||||
}
|
||||
|
||||
// 遍历提取属性和值
|
||||
let data: Record<string, any> = {};
|
||||
for (const key in from) {
|
||||
@@ -187,6 +212,22 @@ export default function useConfigArray({
|
||||
row[v.name] = Object.assign({}, v);
|
||||
}
|
||||
|
||||
// 特殊SMF-upfid选择
|
||||
if (neTypeSelect.value[0] === 'SMF' && Reflect.has(row, 'upfId')) {
|
||||
const v = row.upfId.value;
|
||||
if (typeof v === 'string') {
|
||||
if (v === '') {
|
||||
row.upfId.value = [];
|
||||
} else if (v.includes(';')) {
|
||||
row.upfId.value = v.split(';');
|
||||
} else if (v.includes(',')) {
|
||||
row.upfId.value = v.split(',');
|
||||
} else {
|
||||
row.upfId.value = [v];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modalState.from = row;
|
||||
modalState.type = 'arrayAdd';
|
||||
modalState.title = `${treeState.selectNode.paramDisplay} ${from.title}`;
|
||||
@@ -197,6 +238,14 @@ export default function useConfigArray({
|
||||
|
||||
/**多列表新增单行确认 */
|
||||
function arrayAddOk(from: Record<string, any>) {
|
||||
// 特殊SMF-upfid选择
|
||||
if (neTypeSelect.value[0] === 'SMF' && Reflect.has(from, 'upfId')) {
|
||||
const v = from.upfId.value;
|
||||
if (Array.isArray(v)) {
|
||||
from.upfId.value = v.join(';');
|
||||
}
|
||||
}
|
||||
|
||||
// 遍历提取属性和值
|
||||
let data: Record<string, any> = {};
|
||||
for (const key in from) {
|
||||
@@ -321,6 +370,22 @@ export default function useConfigArray({
|
||||
if ('bool' === row.type) {
|
||||
row.value = Boolean(row.value);
|
||||
}
|
||||
|
||||
// 特殊SMF-upfid选择
|
||||
if (neTypeSelect.value[0] === 'SMF' && row.name === 'upfId') {
|
||||
const v = row.value;
|
||||
if (typeof v === 'string') {
|
||||
if (v === '') {
|
||||
row.value = [];
|
||||
} else if (v.includes(';')) {
|
||||
row.value = v.split(';');
|
||||
} else if (v.includes(',')) {
|
||||
row.value = v.split(',');
|
||||
} else {
|
||||
row.value = [v];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ruleFrom;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ const { ruleVerification, smfByUPFIdLoadData, smfByUPFIdOptions } = useOptions({
|
||||
t,
|
||||
});
|
||||
|
||||
/**网元参数 */
|
||||
/**网元类型_多neId */
|
||||
let neCascaderOptions = ref<Record<string, any>[]>([]);
|
||||
|
||||
/**网元类型选择 type,id */
|
||||
@@ -371,7 +371,7 @@ onMounted(() => {
|
||||
// 过滤不可用的网元
|
||||
neCascaderOptions.value = neInfoStore.getNeSelectOtions.filter(
|
||||
(item: any) => {
|
||||
return !['OMC', 'LMF', 'NEF'].includes(item.value);
|
||||
return !['LMF', 'NEF'].includes(item.value);
|
||||
}
|
||||
);
|
||||
if (neCascaderOptions.value.length === 0) {
|
||||
@@ -589,7 +589,7 @@ onMounted(() => {
|
||||
@dblclick="listEdit(record)"
|
||||
>
|
||||
<template v-if="record['type'] === 'enum'">
|
||||
{{ JSON.parse(record['filter'])[text] }}
|
||||
{{ JSON.parse(record['filter'])[text] || ' ' }}
|
||||
</template>
|
||||
<template v-else>{{ `${text}` || ' ' }}</template>
|
||||
<EditOutlined
|
||||
@@ -870,6 +870,9 @@ onMounted(() => {
|
||||
v-model:value="modalState.from[item.name]['value']"
|
||||
:options="smfByUPFIdOptions"
|
||||
:disabled="['read-only', 'read', 'ro'].includes(item.access)"
|
||||
:token-separators="[',', ';']"
|
||||
mode="multiple"
|
||||
:max-tag-count="5"
|
||||
:allow-clear="true"
|
||||
style="width: 100%"
|
||||
>
|
||||
@@ -934,7 +937,7 @@ onMounted(() => {
|
||||
}
|
||||
&__text-wrapper {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
color: inherit;
|
||||
}
|
||||
&__text-wrapper:hover &__icon {
|
||||
display: inline-block;
|
||||
|
||||
@@ -281,9 +281,7 @@ function fnModalCancel() {
|
||||
emit('update:visible', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单修改网元类型
|
||||
*/
|
||||
/**表单修改网元类型 */
|
||||
function fnNeTypeChange(v: any) {
|
||||
const hostsLen = modalState.from.hosts.length;
|
||||
// 网元默认只含22和4100
|
||||
@@ -305,11 +303,18 @@ function fnNeTypeChange(v: any) {
|
||||
remark: '',
|
||||
});
|
||||
}
|
||||
|
||||
modalState.from.rmUid = `4400HX${v}${modalState.from.neId}`; // 4400HX1AMF001
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单修改网元IP
|
||||
*/
|
||||
/**表单修改网元neId */
|
||||
function fnNeIdChange(e: any) {
|
||||
const v = e.target.value;
|
||||
if (v.length < 1) return;
|
||||
modalState.from.rmUid = `4400HX${modalState.from.neType}${v}`; // 4400HX1AMF001
|
||||
}
|
||||
|
||||
/**表单修改网元IP */
|
||||
function fnNeIPChange(e: any) {
|
||||
const v = e.target.value;
|
||||
if (v.length < 7) return;
|
||||
@@ -428,6 +433,7 @@ onMounted(() => {
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="32"
|
||||
@change="fnNeIdChange"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
|
||||
@@ -390,33 +390,27 @@ function parseResouresUsage(neState: Record<string, any>) {
|
||||
let nfCpuUsage = 0;
|
||||
if (neState.cpu) {
|
||||
nfCpuUsage = neState.cpu.nfCpuUsage;
|
||||
const nfCpu = +(nfCpuUsage / 100);
|
||||
nfCpuUsage = +nfCpu.toFixed(2);
|
||||
if (nfCpuUsage > 100) {
|
||||
const nfCpu = +(neState.cpu.nfCpuUsage / 100);
|
||||
if (nfCpu > 100) {
|
||||
nfCpuUsage = 100;
|
||||
} else {
|
||||
nfCpuUsage = +nfCpu.toFixed(2);
|
||||
}
|
||||
nfCpuUsage = 100;
|
||||
}
|
||||
|
||||
sysCpuUsage = neState.cpu.sysCpuUsage;
|
||||
const sysCpu = +(sysCpuUsage / 100);
|
||||
sysCpuUsage = +sysCpu.toFixed(2);
|
||||
if (sysCpuUsage > 100) {
|
||||
const sysCpu = +(neState.cpu.sysCpuUsage / 100);
|
||||
if (sysCpu > 100) {
|
||||
sysCpuUsage = 100;
|
||||
} else {
|
||||
sysCpuUsage = +sysCpu.toFixed(2);
|
||||
}
|
||||
sysCpuUsage = 100;
|
||||
}
|
||||
}
|
||||
|
||||
let sysMemUsage = 0;
|
||||
if (neState.mem) {
|
||||
let men = neState.mem.sysMemUsage;
|
||||
if (men > 100) {
|
||||
men = +(men / 100).toFixed(2);
|
||||
const men = neState.mem.sysMemUsage;
|
||||
sysMemUsage = +(men / 100).toFixed(2);
|
||||
if (sysMemUsage > 100) {
|
||||
sysMemUsage = 100;
|
||||
}
|
||||
sysMemUsage = men;
|
||||
}
|
||||
|
||||
let sysDiskUsage = 0;
|
||||
|
||||
@@ -194,8 +194,6 @@ type ModalStateType = {
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**表单数据 */
|
||||
BatchForm: Record<string, any>;
|
||||
/**表单数据 */
|
||||
BatchDelForm: Record<string, any>;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
@@ -210,15 +208,8 @@ let modalState: ModalStateType = reactive({
|
||||
visibleByBatchDel: false,
|
||||
title: 'UDM鉴权用户',
|
||||
from: {
|
||||
id: '',
|
||||
imsi: '',
|
||||
amf: '8000',
|
||||
ki: '',
|
||||
algoIndex: 0,
|
||||
opc: '',
|
||||
},
|
||||
BatchForm: {
|
||||
num: 1,
|
||||
id: '',
|
||||
imsi: '',
|
||||
amf: '8000',
|
||||
ki: '',
|
||||
@@ -236,19 +227,6 @@ let modalState: ModalStateType = reactive({
|
||||
/**对话框内表单属性和校验规则 */
|
||||
const modalStateFrom = Form.useForm(
|
||||
modalState.from,
|
||||
reactive({
|
||||
imsi: [{ required: true, message: 'IMSI' + t('common.unableNull') }],
|
||||
amf: [{ required: true, message: 'AMF' + t('common.unableNull') }],
|
||||
ki: [{ required: true, message: 'KI' + t('common.unableNull') }],
|
||||
algoIndex: [
|
||||
{ required: true, message: 'algoIndex' + t('common.unableNull') },
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**对话框内批量添加表单属性和校验规则 */
|
||||
const modalStateBatchFrom = Form.useForm(
|
||||
modalState.BatchForm,
|
||||
reactive({
|
||||
num: [
|
||||
{
|
||||
@@ -256,9 +234,15 @@ const modalStateBatchFrom = Form.useForm(
|
||||
message: t('views.neUser.auth.numAdd') + t('common.unableNull'),
|
||||
},
|
||||
],
|
||||
imsi: [{ required: true, message: 'IMSI' + t('common.unableNull') }],
|
||||
imsi: [
|
||||
{ required: true, message: 'IMSI' + t('common.unableNull') },
|
||||
{ min: 15, max: 15, message: t('views.neUser.auth.imsiConfirm') },
|
||||
],
|
||||
amf: [{ required: true, message: 'AMF' + t('common.unableNull') }],
|
||||
ki: [{ required: true, message: 'KI' + t('common.unableNull') }],
|
||||
ki: [
|
||||
{ required: true, message: 'KI' + t('common.unableNull') },
|
||||
{ min: 32, max: 32, message: t('views.neUser.auth.kiTip') },
|
||||
],
|
||||
algoIndex: [
|
||||
{ required: true, message: 'algoIndex' + t('common.unableNull') },
|
||||
],
|
||||
@@ -312,21 +296,14 @@ function fnModalVisibleByEdit(row?: Record<string, any>) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 批量新增,批量删除
|
||||
* 对话框弹出显示为 批量删除
|
||||
* @param noticeId 网元id, 不传为新增
|
||||
*/
|
||||
function fnModalVisibleByBatch(batchFlag?: number) {
|
||||
if (batchFlag) {
|
||||
modalStateBatchFrom.resetFields(); //重置表单
|
||||
modalState.title =
|
||||
t('views.neUser.auth.batchAddText') + t('views.neUser.auth.authInfo');
|
||||
modalState.visibleByBatch = true;
|
||||
} else {
|
||||
modalStateBatchFrom.resetFields(); //重置表单
|
||||
modalState.title =
|
||||
t('views.neUser.auth.batchDelText') + t('views.neUser.auth.authInfo');
|
||||
modalState.visibleByBatchDel = true;
|
||||
}
|
||||
function fnModalVisibleByBatch() {
|
||||
modalStateBatchDelFrom.resetFields(); //重置表单
|
||||
modalState.title =
|
||||
t('views.neUser.auth.batchDelText') + t('views.neUser.auth.authInfo');
|
||||
modalState.visibleByBatchDel = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -341,23 +318,52 @@ function fnModalOk() {
|
||||
const from = toRaw(modalState.from);
|
||||
from.neId = queryParams.neId || '-';
|
||||
from.algoIndex = `${from.algoIndex}`;
|
||||
const result = from.id ? updateUDMAuth(from) : addUDMAuth(from);
|
||||
const result = from.id
|
||||
? updateUDMAuth(from)
|
||||
: from.num === 1
|
||||
? addUDMAuth(from)
|
||||
: batchAddUDMAuth(from, from.num);
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
result
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: modalState.title }),
|
||||
duration: 3,
|
||||
});
|
||||
if (from.num === 1) {
|
||||
//新增时
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: modalState.title }),
|
||||
duration: 3,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
//批量新增时
|
||||
const timerS = Math.max(
|
||||
Math.ceil(+from.num / 500),
|
||||
`${from.num}`.length
|
||||
);
|
||||
notification.success({
|
||||
message: modalState.title,
|
||||
description: t('common.operateOk'),
|
||||
duration: timerS,
|
||||
});
|
||||
setTimeout(() => {
|
||||
fnGetList(1);
|
||||
}, timerS * 1000);
|
||||
}
|
||||
modalState.visibleByEdit = false;
|
||||
modalStateFrom.resetFields();
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
if (from.num === 1) {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
} else {
|
||||
notification.error({
|
||||
message: modalState.title,
|
||||
description: res.msg,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -370,51 +376,6 @@ function fnModalOk() {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出 批量新增操作确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnBatchModalOk() {
|
||||
modalStateBatchFrom
|
||||
.validate()
|
||||
.then(e => {
|
||||
modalState.confirmLoading = true;
|
||||
const from = toRaw(modalState.BatchForm);
|
||||
from.neId = queryParams.neId || '-';
|
||||
from.algoIndex = `${from.algoIndex}`;
|
||||
const result = batchAddUDMAuth(from, from.num);
|
||||
result.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
const timerS = Math.max(
|
||||
Math.ceil(+from.num / 500),
|
||||
`${from.num}`.length
|
||||
);
|
||||
notification.success({
|
||||
message: modalState.title,
|
||||
description: t('common.operateOk'),
|
||||
duration: timerS,
|
||||
});
|
||||
setTimeout(() => {
|
||||
modalState.confirmLoading = false;
|
||||
modalState.visibleByBatch = false;
|
||||
modalStateBatchFrom.resetFields();
|
||||
fnGetList(1);
|
||||
}, timerS * 1000);
|
||||
} else {
|
||||
modalState.confirmLoading = false;
|
||||
notification.error({
|
||||
message: modalState.title,
|
||||
description: res.msg,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出 批量删除确认执行函数
|
||||
* 进行表达规则校验
|
||||
@@ -455,15 +416,6 @@ function fnBatchDelModalOk() {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnBatchModalCancel() {
|
||||
modalState.visibleByBatch = false;
|
||||
modalStateBatchFrom.resetFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
@@ -831,29 +783,11 @@ onMounted(() => {
|
||||
{{ t('common.addText') }}
|
||||
</a-button>
|
||||
|
||||
<a-button type="primary" @click.prevent="fnModalVisibleByBatch(1)">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
{{ t('views.neUser.auth.batchAddText') }}
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
:loading="modalState.loadDataLoading"
|
||||
@click.prevent="fnRecordDelete('0')"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
{{ t('views.neUser.auth.checkDel') }}
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
type="primary"
|
||||
danger
|
||||
ghost
|
||||
@click.prevent="fnModalVisibleByBatch(0)"
|
||||
@click.prevent="fnModalVisibleByBatch()"
|
||||
>
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
@@ -900,6 +834,17 @@ onMounted(() => {
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
:loading="modalState.loadDataLoading"
|
||||
@click.prevent="fnRecordDelete('0')"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
{{ t('views.neUser.auth.checkDel') }}
|
||||
</a-button>
|
||||
|
||||
<a-popconfirm
|
||||
:title="t('views.neUser.auth.checkExportConfirm')"
|
||||
placement="topRight"
|
||||
@@ -1040,6 +985,24 @@ onMounted(() => {
|
||||
:label-col="{ span: 6 }"
|
||||
:labelWrap="true"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.neUser.auth.numAdd')"
|
||||
name="num"
|
||||
v-bind="modalStateFrom.validateInfos.num"
|
||||
v-show="!modalState.from.id"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="modalState.from.num"
|
||||
style="width: 100%"
|
||||
:min="1"
|
||||
:max="10000"
|
||||
placeholder="<=10000"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
@@ -1175,175 +1138,6 @@ onMounted(() => {
|
||||
</a-form>
|
||||
</ProModal>
|
||||
|
||||
<!-- 批量新增框 -->
|
||||
<ProModal
|
||||
:drag="true"
|
||||
:width="800"
|
||||
:destroyOnClose="true"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:visible="modalState.visibleByBatch"
|
||||
:title="modalState.title"
|
||||
:confirm-loading="modalState.confirmLoading"
|
||||
@ok="fnBatchModalOk"
|
||||
@cancel="fnBatchModalCancel"
|
||||
>
|
||||
<a-form
|
||||
name="modalStateBatchFrom"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 6 }"
|
||||
:labelWrap="true"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.neUser.auth.numAdd')"
|
||||
name="num"
|
||||
v-bind="modalStateBatchFrom.validateInfos.num"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="modalState.BatchForm.num"
|
||||
style="width: 100%"
|
||||
:min="1"
|
||||
:max="10000"
|
||||
placeholder="<=10000"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
label="IMSI"
|
||||
name="imsi"
|
||||
v-bind="modalStateBatchFrom.validateInfos.imsi"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.BatchForm.imsi"
|
||||
allow-clear
|
||||
:maxlength="15"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
{{ t('views.neUser.auth.imsiTip') }}<br />
|
||||
{{ t('views.neUser.auth.imsiTip1') }}<br />
|
||||
{{ t('views.neUser.auth.imsiTip2') }}<br />
|
||||
{{ t('views.neUser.auth.imsiTip3') }}
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="Status" name="status">
|
||||
<a-select value="1">
|
||||
<a-select-option value="1">Active</a-select-option>
|
||||
<a-select-option value="0">Inactive</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
label="AMF"
|
||||
name="amf"
|
||||
v-bind="modalStateBatchFrom.validateInfos.amf"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.BatchForm.amf"
|
||||
allow-clear
|
||||
:maxlength="4"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
{{ t('views.neUser.auth.amfTip') }}
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
label="Algo Index"
|
||||
name="algo"
|
||||
v-bind="modalStateBatchFrom.validateInfos.algoIndex"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="modalState.BatchForm.algoIndex"
|
||||
style="width: 100%"
|
||||
:min="0"
|
||||
:max="15"
|
||||
placeholder="0 ~ 15"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
{{ t('views.neUser.auth.algoIndexTip') }}
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item
|
||||
label="KI"
|
||||
name="ki"
|
||||
v-bind="modalStateBatchFrom.validateInfos.ki"
|
||||
:label-col="{ span: 3 }"
|
||||
:labelWrap="true"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.BatchForm.ki"
|
||||
allow-clear
|
||||
:maxlength="32"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
{{ t('views.neUser.auth.kiTip') }}
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="OPC"
|
||||
name="opc"
|
||||
v-bind="modalStateBatchFrom.validateInfos.opc"
|
||||
:label-col="{ span: 3 }"
|
||||
:labelWrap="true"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.BatchForm.opc"
|
||||
allow-clear
|
||||
:maxlength="32"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
{{ t('views.neUser.auth.opcTip') }}
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ProModal>
|
||||
|
||||
<!-- 批量删除框 -->
|
||||
<ProModal
|
||||
:drag="true"
|
||||
|
||||
@@ -85,6 +85,15 @@ let tableColumns: ColumnsType = [
|
||||
align: 'center',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'NE Name',
|
||||
dataIndex: 'neName',
|
||||
align: 'left',
|
||||
resizable: true,
|
||||
width: 200,
|
||||
minWidth: 150,
|
||||
maxWidth: 400,
|
||||
},
|
||||
{
|
||||
title: 'UE Number',
|
||||
dataIndex: 'ueNum',
|
||||
@@ -158,10 +167,11 @@ function fnGet45GList(pageNum?: number) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
if (!queryParams.neType) {
|
||||
tableState.data = [];
|
||||
promises.value = [];
|
||||
//同时获取45G基站信息 且在每条信息中添加45G字段(原始数据没有) 已经筛选后的
|
||||
neCascaderOptions.value.map((item: any) => {
|
||||
item.children.forEach((child: any) => {
|
||||
item.children.forEach((child: any) => {
|
||||
promises.value.push(
|
||||
listBase5G({
|
||||
neId: child.neId,
|
||||
@@ -169,6 +179,14 @@ function fnGet45GList(pageNum?: number) {
|
||||
nbId: queryParams.id,
|
||||
pageNum: queryParams.pageNum,
|
||||
pageSize: 10000,
|
||||
}).then(res => {
|
||||
// 添加 neName 字段到每一项数据
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||
res.rows.forEach(row => {
|
||||
row.neName = `${child.neType}_${child.neId}`;
|
||||
});
|
||||
}
|
||||
return res;
|
||||
})
|
||||
);
|
||||
});
|
||||
@@ -202,6 +220,7 @@ function fnGet45GList(pageNum?: number) {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ import { 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 { listUEInfoBySMF } from '@/api/neUser/smf';
|
||||
import { listSMFSubscribers } from '@/api/neData/smf';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
@@ -59,8 +59,6 @@ type TabeStateType = {
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
@@ -69,7 +67,6 @@ let tableState: TabeStateType = reactive({
|
||||
size: 'middle',
|
||||
seached: true,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
@@ -77,7 +74,7 @@ let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: 'IMSI',
|
||||
dataIndex: 'imsi',
|
||||
align: 'center',
|
||||
align: 'left',
|
||||
sorter: (a: any, b: any) => Number(a.imsi) - Number(b.imsi),
|
||||
customRender(opt) {
|
||||
const idx = opt.value.lastIndexOf('-');
|
||||
@@ -91,7 +88,7 @@ let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: 'MSISDN',
|
||||
dataIndex: 'msisdn',
|
||||
align: 'center',
|
||||
align: 'left',
|
||||
sorter: (a: any, b: any) => Number(a.msisdn) - Number(b.msisdn),
|
||||
customRender(opt) {
|
||||
const idx = opt.value.lastIndexOf('-');
|
||||
@@ -105,7 +102,7 @@ let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: 'RAT Type',
|
||||
dataIndex: 'ratType',
|
||||
align: 'center',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
@@ -124,7 +121,7 @@ let tableColumns: ColumnsType = [
|
||||
}
|
||||
return '';
|
||||
},
|
||||
width: 150,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'Tenant Name',
|
||||
@@ -137,6 +134,12 @@ let tableColumns: ColumnsType = [
|
||||
title: t('common.operate'),
|
||||
key: 'imsi',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'Remark',
|
||||
dataIndex: 'remark',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -238,12 +241,8 @@ function fnGetList(pageNum?: number) {
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
listUEInfoBySMF(toRaw(queryParams)).then(res => {
|
||||
listSMFSubscribers(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 (
|
||||
@@ -321,7 +320,7 @@ onMounted(() => {
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.neUser.ue.neType')" name="neId ">
|
||||
<a-select
|
||||
v-model:value="queryParams.neId"
|
||||
@@ -330,12 +329,12 @@ onMounted(() => {
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="IMSI" name="imsi">
|
||||
<a-input v-model:value="queryParams.imsi" allow-clear></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="MSISDN" name="msisdn">
|
||||
<a-input v-model:value="queryParams.msisdn" allow-clear></a-input>
|
||||
</a-form-item>
|
||||
@@ -434,7 +433,7 @@ onMounted(() => {
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: 1000, y: 400 }"
|
||||
:scroll="{ x: true, y: 400 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'imsi'">
|
||||
|
||||
@@ -7,8 +7,8 @@ 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 useI18n from '@/hooks/useI18n';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||
import {
|
||||
addCustom,
|
||||
delCustom,
|
||||
@@ -16,15 +16,17 @@ import {
|
||||
listCustom,
|
||||
updateCustom,
|
||||
} from '@/api/perfManage/customTarget';
|
||||
const { getDict } = useDictStore();
|
||||
import { getKPITitle } from '@/api/perfManage/goldTarget';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
const { t, currentLocale } = useI18n();
|
||||
const { getDict } = useDictStore();
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**原始严重程度 */
|
||||
activeAlarmSeverity: DictType[];
|
||||
/**状态 */
|
||||
sysNormalDisable: DictType[];
|
||||
} = reactive({
|
||||
activeAlarmSeverity: [],
|
||||
sysNormalDisable: [],
|
||||
});
|
||||
|
||||
/**查询参数 */
|
||||
@@ -85,15 +87,22 @@ let tableColumns: ColumnsType = [
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.perfManage.customTarget.kpiSet'),
|
||||
dataIndex: 'kpiSet',
|
||||
title: t('views.perfManage.customTarget.title'),
|
||||
dataIndex: 'title',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.perfManage.customTarget.period'),
|
||||
dataIndex: 'threshold',
|
||||
title: t('views.perfManage.customTarget.description'),
|
||||
dataIndex: 'description',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.perfManage.customTarget.status'),
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
@@ -175,13 +184,14 @@ function fnGetList(pageNum?: number) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
listCustom(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
tablePagination.total = res.total;
|
||||
tableState.data = res.rows;
|
||||
tableState.data = res.data;
|
||||
|
||||
if (
|
||||
tablePagination.total <=
|
||||
(queryParams.pageNum - 1) * tablePagination.pageSize &&
|
||||
@@ -207,8 +217,6 @@ type ModalStateType = {
|
||||
neType: string[];
|
||||
/**网元类型性能测量集 */
|
||||
neTypPerformance: Record<string, any>[];
|
||||
/**网元类型对象类型集 */
|
||||
objectTypeArr: Record<string, any>[];
|
||||
/**已选择性能测量项 */
|
||||
selectedPre: string[];
|
||||
/**表单数据 */
|
||||
@@ -224,21 +232,32 @@ let modalState: ModalStateType = reactive({
|
||||
title: '',
|
||||
neType: [],
|
||||
neTypPerformance: [],
|
||||
objectTypeArr: [],
|
||||
selectedPre: [],
|
||||
from: {
|
||||
id: '',
|
||||
neType: '',
|
||||
objectType: '',
|
||||
expression: '',
|
||||
kpiSet: '',
|
||||
title: '',
|
||||
id: undefined,
|
||||
neType: 'UDM',
|
||||
kpiId: '',
|
||||
period: 900,
|
||||
title: '',
|
||||
expression: '',
|
||||
status: 'Active',
|
||||
unit: '',
|
||||
description: '',
|
||||
},
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**表单中多选的OPTION */
|
||||
const modalStateFromOption = reactive({
|
||||
symbolJson: [
|
||||
{ label: '(', value: '(' },
|
||||
{ label: ')', value: ')' },
|
||||
{ label: '+', value: '+' },
|
||||
{ label: '-', value: '-' },
|
||||
{ label: '*', value: '*' },
|
||||
{ label: '/', value: '/' },
|
||||
],
|
||||
});
|
||||
|
||||
/**对话框内表单属性和校验规则 */
|
||||
const modalStateFrom = Form.useForm(
|
||||
modalState.from,
|
||||
@@ -249,14 +268,6 @@ const modalStateFrom = Form.useForm(
|
||||
message: t('views.traceManage.task.neTypePlease'),
|
||||
},
|
||||
],
|
||||
objectType: [
|
||||
{
|
||||
required: true,
|
||||
message:
|
||||
t('views.perfManage.customTarget.objectType') +
|
||||
t('common.unableNull'),
|
||||
},
|
||||
],
|
||||
expression: [
|
||||
{
|
||||
required: true,
|
||||
@@ -279,60 +290,45 @@ const modalStateFrom = Form.useForm(
|
||||
t('views.perfManage.customTarget.title') + t('common.unableNull'),
|
||||
},
|
||||
],
|
||||
period: [
|
||||
unit: [
|
||||
{
|
||||
required: true,
|
||||
message:
|
||||
t('views.perfManage.customTarget.period') + t('common.unableNull'),
|
||||
t('views.perfManage.customTarget.unit') + t('common.unableNull'),
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**性能测量数据集选择初始 */
|
||||
/**性能测量数据集选择初始 value:neType*/
|
||||
function fnSelectPerformanceInit(value: any) {
|
||||
const performance = useNeInfoStore().perMeasurementList.filter(
|
||||
i => i.neType === value
|
||||
);
|
||||
if (modalState.from.objectType) modalState.from.objectType = '';
|
||||
if (modalState.selectedPre.length > 0) modalState.selectedPre = [];
|
||||
|
||||
modalState.from.expression = '';
|
||||
const arrSet = new Set<string>();
|
||||
performance.forEach((data: any) => {
|
||||
arrSet.add(data.objectType);
|
||||
});
|
||||
// 组装对象类型options
|
||||
modalState.objectTypeArr = Array.from(arrSet).map((value: any) => ({
|
||||
label: value,
|
||||
value: value,
|
||||
}));
|
||||
|
||||
//进行分组选择
|
||||
const groupedData = performance.reduce((groups: any, item: any) => {
|
||||
const { kpiCode, ...rest } = item;
|
||||
if (!groups[kpiCode]) {
|
||||
groups[kpiCode] = [];
|
||||
modalState.neTypPerformance = [
|
||||
{
|
||||
value: 'granularity',
|
||||
label: t('views.perfManage.customTarget.granularity'),
|
||||
},
|
||||
];
|
||||
// 当前语言
|
||||
var language = currentLocale.value.split('_')[0];
|
||||
if (language === 'zh') language = 'cn';
|
||||
// 获取表头文字
|
||||
getKPITitle(value).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
for (const item of res.data) {
|
||||
const kpiDisplay = item[`${language}Title`];
|
||||
const kpiValue = item[`kpiId`];
|
||||
modalState.neTypPerformance.push({
|
||||
value: kpiValue,
|
||||
label: kpiDisplay,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('common.getInfoFail'),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
groups[kpiCode].push(rest);
|
||||
return groups;
|
||||
}, {});
|
||||
|
||||
//渲染出性能测量集的选择项
|
||||
modalState.neTypPerformance = Object.keys(groupedData).map(kpiCode => {
|
||||
return {
|
||||
label: kpiCode,
|
||||
options: groupedData[kpiCode].map((item: any) => {
|
||||
return {
|
||||
value: item.kpiId,
|
||||
label:
|
||||
currentLocale.value === 'zh_CN'
|
||||
? JSON.parse(item.titleJson).cn
|
||||
: JSON.parse(item.titleJson).en,
|
||||
kpiCode: kpiCode,
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -340,30 +336,17 @@ function fnSelectPerformanceInit(value: any) {
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param noticeId 网元id, 不传为新增
|
||||
*/
|
||||
function fnModalVisibleByEdit(id?: string) {
|
||||
function fnModalVisibleByEdit(row?: any, id?: any) {
|
||||
if (!id) {
|
||||
modalStateFrom.resetFields();
|
||||
modalState.title = t('views.perfManage.customTarget.addCustom');
|
||||
modalState.visibleByEdit = true;
|
||||
fnSelectPerformanceInit(modalState.from.neType);
|
||||
} else {
|
||||
if (modalState.confirmLoading) return;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
modalState.confirmLoading = true;
|
||||
getCustom(id).then(res => {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
fnSelectPerformanceInit(res.data.neType);
|
||||
modalState.selectedPre = res.data.kpiSet
|
||||
? res.data.kpiSet.split(',')
|
||||
: [];
|
||||
modalState.from = Object.assign(modalState.from, res.data);
|
||||
modalState.title = t('views.perfManage.customTarget.editCustom');
|
||||
modalState.visibleByEdit = true;
|
||||
} else {
|
||||
message.error(t('views.perfManage.customTarget.errorCustomInfo'), 3);
|
||||
}
|
||||
});
|
||||
fnSelectPerformanceInit(row.neType);
|
||||
modalState.from = Object.assign(modalState.from, row);
|
||||
modalState.title = t('views.perfManage.customTarget.editCustom');
|
||||
modalState.visibleByEdit = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,13 +358,6 @@ function fnModalOk() {
|
||||
modalStateFrom
|
||||
.validate()
|
||||
.then(e => {
|
||||
// if (modalState.selectedPre.length === 0) {
|
||||
// message.error({
|
||||
// content: `${res.msg}`,
|
||||
// duration: 3,
|
||||
// });
|
||||
// }
|
||||
modalState.from.kpiSet = modalState.selectedPre.join(',');
|
||||
const from = toRaw(modalState.from);
|
||||
//return false;
|
||||
modalState.confirmLoading = true;
|
||||
@@ -424,46 +400,57 @@ function fnModalCancel() {
|
||||
modalStateFrom.resetFields();
|
||||
modalState.neType = [];
|
||||
modalState.neTypPerformance = [];
|
||||
modalState.selectedPre = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择性能指标,填充进当前计算公式的值
|
||||
*/
|
||||
function fnSelectPer(s: any, option: any) {
|
||||
modalState.from.expression += `'${s}'`;
|
||||
}
|
||||
|
||||
function fnSelectSymbol(s: any) {
|
||||
modalState.from.expression += s;
|
||||
}
|
||||
|
||||
/**
|
||||
* 多选框取消性能指标 表达式的变化
|
||||
*/
|
||||
function fnDelPer(s: any, option: any) {
|
||||
modalState.from.expression = modalState.from.expression.replace(s, '');
|
||||
}
|
||||
|
||||
// function checkText(e:any){
|
||||
// console.log(e);
|
||||
// const reg = /^[*+-/]*$/;
|
||||
|
||||
// }
|
||||
|
||||
/**网元参数 */
|
||||
let neCascaderOptions = ref<Record<string, any>[]>([]);
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('active_alarm_severity')]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.activeAlarmSeverity = resArr[0].value;
|
||||
}
|
||||
});
|
||||
|
||||
Promise.allSettled([
|
||||
// 获取网元网元列表
|
||||
getDict('sys_normal_disable'),
|
||||
useNeInfoStore().fnNelist(),
|
||||
// 获取性能测量集列表
|
||||
useNeInfoStore().fnNeTaskPerformance(),
|
||||
]).finally(() => {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
])
|
||||
.then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.sysNormalDisable = resArr[0].value;
|
||||
}
|
||||
|
||||
if (
|
||||
resArr[1].status === 'fulfilled' &&
|
||||
Array.isArray(resArr[1].value.data)
|
||||
) {
|
||||
if (resArr[1].value.data.length > 0) {
|
||||
// 过滤不可用的网元
|
||||
neCascaderOptions.value =
|
||||
useNeInfoStore().getNeCascaderOptions.filter((item: any) => {
|
||||
return !['OMC', 'NSSF', 'NEF', 'NRF', 'LMF', 'N3IWF'].includes(
|
||||
item.value
|
||||
);
|
||||
});
|
||||
if (neCascaderOptions.value.length === 0) {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -484,7 +471,7 @@ onMounted(() => {
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="queryParams.neType"
|
||||
:options="useNeInfoStore().getNeSelectOtions"
|
||||
:options="neCascaderOptions"
|
||||
allow-clear
|
||||
:placeholder="t('views.traceManage.task.neTypePlease')"
|
||||
/>
|
||||
@@ -580,7 +567,7 @@ onMounted(() => {
|
||||
<template #title>{{ t('common.editText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByEdit(record.id)"
|
||||
@click.prevent="fnModalVisibleByEdit(record, record.id)"
|
||||
>
|
||||
<template #icon><FormOutlined /></template>
|
||||
</a-button>
|
||||
@@ -593,6 +580,24 @@ onMounted(() => {
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'status'">
|
||||
<DictTag
|
||||
:options="[
|
||||
{
|
||||
label: t('views.perfManage.customTarget.active'),
|
||||
value: 'Active',
|
||||
tagType: 'success',
|
||||
},
|
||||
{
|
||||
label: t('views.perfManage.customTarget.inactive'),
|
||||
value: 'Inactive',
|
||||
tagType: 'error',
|
||||
},
|
||||
]"
|
||||
:value="record.status"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
@@ -610,7 +615,12 @@ onMounted(() => {
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form name="modalStateFrom" layout="horizontal">
|
||||
<a-form
|
||||
name="modalStateFrom"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 6 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
@@ -620,7 +630,7 @@ onMounted(() => {
|
||||
>
|
||||
<a-select
|
||||
v-model:value="modalState.from.neType"
|
||||
:options="useNeInfoStore().getNeSelectOtions"
|
||||
:options="neCascaderOptions"
|
||||
@change="fnSelectPerformanceInit"
|
||||
:allow-clear="false"
|
||||
:placeholder="t('views.traceManage.task.neTypePlease')"
|
||||
@@ -628,24 +638,19 @@ onMounted(() => {
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.objectType')"
|
||||
name="kpiSobjectTypeet"
|
||||
v-show="modalState.from.neType"
|
||||
v-bind="modalStateFrom.validateInfos.objectType"
|
||||
>
|
||||
<a-select
|
||||
placeholder="Please select"
|
||||
v-model:value="modalState.from.objectType"
|
||||
:options="modalState.objectTypeArr"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.title')"
|
||||
name="title"
|
||||
v-bind="modalStateFrom.validateInfos.title"
|
||||
>
|
||||
<a-input v-model:value="modalState.from.title" allow-clear>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.kpiId')"
|
||||
@@ -656,64 +661,96 @@ onMounted(() => {
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.period')"
|
||||
name="period"
|
||||
v-bind="modalStateFrom.validateInfos.period"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="modalState.from.period"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
:options="[
|
||||
{ label: '5S', value: 5 },
|
||||
{ label: '1M', value: 60 },
|
||||
{ label: '5M', value: 300 },
|
||||
{ label: '15M', value: 900 },
|
||||
{ label: '30M', value: 1800 },
|
||||
{ label: '60M', value: 3600 },
|
||||
]"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.title')"
|
||||
name="title"
|
||||
v-bind="modalStateFrom.validateInfos.title"
|
||||
>
|
||||
<a-input v-model:value="modalState.from.title" allow-clear> </a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.expression')"
|
||||
name="expression"
|
||||
:label-col="{ span: 3 }"
|
||||
v-bind="modalStateFrom.validateInfos.expression"
|
||||
>
|
||||
<a-input v-model:value="modalState.from.expression" allow-clear>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="21" :md="21" :xs="24">
|
||||
<a-form-item name="perSelect">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
name="perSelect"
|
||||
:label="t('views.perfManage.customTarget.symbol')"
|
||||
>
|
||||
<a-select
|
||||
placeholder="Please select"
|
||||
:options="modalStateFromOption.symbolJson"
|
||||
@select="fnSelectSymbol"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
name="perSelect"
|
||||
:label="t('views.perfManage.customTarget.element')"
|
||||
>
|
||||
<a-select
|
||||
mode="multiple"
|
||||
style="margin-left: 80px"
|
||||
placeholder="Please select"
|
||||
allow-clear
|
||||
v-model:value="modalState.selectedPre"
|
||||
:options="modalState.neTypPerformance"
|
||||
@select="fnSelectPer"
|
||||
@deselect="fnDelPer"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.unit')"
|
||||
name="expression"
|
||||
v-bind="modalStateFrom.validateInfos.unit"
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="modalState.from.unit"
|
||||
:options="[
|
||||
{
|
||||
label: 'Mbps',
|
||||
value: 'Mbps',
|
||||
},
|
||||
{
|
||||
label: '%',
|
||||
value: '%',
|
||||
},
|
||||
]"
|
||||
></a-auto-complete>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.status')"
|
||||
name="status"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="modalState.from.status"
|
||||
default-value="0"
|
||||
:options="[
|
||||
{
|
||||
label: t('views.perfManage.customTarget.active'),
|
||||
value: 'Active',
|
||||
},
|
||||
{
|
||||
label: t('views.perfManage.customTarget.inactive'),
|
||||
value: 'Inactive',
|
||||
},
|
||||
]"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.description')"
|
||||
name="description"
|
||||
:label-col="{ span: 3 }"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.description"
|
||||
|
||||
834
src/views/perfManage/kpiCReport/index.vue
Normal file
834
src/views/perfManage/kpiCReport/index.vue
Normal file
@@ -0,0 +1,834 @@
|
||||
<script setup lang="ts">
|
||||
import * as echarts from 'echarts/core';
|
||||
import {
|
||||
TooltipComponent,
|
||||
TooltipComponentOption,
|
||||
GridComponent,
|
||||
GridComponentOption,
|
||||
LegendComponent,
|
||||
LegendComponentOption,
|
||||
DataZoomComponent,
|
||||
DataZoomComponentOption,
|
||||
} from 'echarts/components';
|
||||
import { LineChart, LineSeriesOption } from 'echarts/charts';
|
||||
import { UniversalTransition } from 'echarts/features';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
|
||||
import {
|
||||
reactive,
|
||||
ref,
|
||||
onMounted,
|
||||
toRaw,
|
||||
markRaw,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
} from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { message, Modal } from 'ant-design-vue/lib';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
||||
import TableColumnsDnd from '@/components/TableColumnsDnd/index.vue';
|
||||
import {
|
||||
RESULT_CODE_ERROR,
|
||||
RESULT_CODE_SUCCESS,
|
||||
} from '@/constants/result-constants';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { listCustom } from '@/api/perfManage/customTarget';
|
||||
import { listCustomData } from '@/api/perfManage/customData';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { writeSheet } from '@/utils/execl-utils';
|
||||
import saveAs from 'file-saver';
|
||||
import { generateColorRGBA } from '@/utils/generate-utils';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import { useRoute } from 'vue-router';
|
||||
const neInfoStore = useNeInfoStore();
|
||||
const route = useRoute();
|
||||
const { t, currentLocale } = useI18n();
|
||||
const ws = new WS();
|
||||
|
||||
echarts.use([
|
||||
TooltipComponent,
|
||||
GridComponent,
|
||||
LegendComponent,
|
||||
DataZoomComponent,
|
||||
LineChart,
|
||||
CanvasRenderer,
|
||||
UniversalTransition,
|
||||
]);
|
||||
|
||||
type EChartsOption = echarts.ComposeOption<
|
||||
| TooltipComponentOption
|
||||
| GridComponentOption
|
||||
| LegendComponentOption
|
||||
| DataZoomComponentOption
|
||||
| LineSeriesOption
|
||||
>;
|
||||
|
||||
/**图DOM节点实例对象 */
|
||||
const kpiChartDom = ref<HTMLElement | undefined>(undefined);
|
||||
|
||||
/**图实例对象 */
|
||||
const kpiChart = ref<any>(null);
|
||||
|
||||
/**网元参数 */
|
||||
let neCascaderOptions = ref<Record<string, any>[]>([]);
|
||||
|
||||
/**记录开始结束时间 */
|
||||
let queryRangePicker = ref<[string, string]>(['', '']);
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns = ref<ColumnsType>([]);
|
||||
|
||||
/**表格字段列排序 */
|
||||
let tableColumnsDnd = ref<ColumnsType>([]);
|
||||
|
||||
/**表格分页器参数 */
|
||||
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;
|
||||
},
|
||||
});
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: Record<string, any>[];
|
||||
/**显示表格 */
|
||||
showTable: boolean;
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
tableColumns: [],
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
seached: true,
|
||||
data: [],
|
||||
showTable: false,
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams: any = reactive({
|
||||
/**网元类型 */
|
||||
neType: '',
|
||||
/**网元标识 */
|
||||
neId: '',
|
||||
/**开始时间 */
|
||||
startTime: '',
|
||||
/**结束时间 */
|
||||
endTime: '',
|
||||
/**排序字段 */
|
||||
sortField: 'created_at',
|
||||
/**排序方式 */
|
||||
sortOrder: 'desc',
|
||||
});
|
||||
|
||||
/**表格分页、排序、筛选变化时触发操作, 排序方式,取值为 ascend descend */
|
||||
function fnTableChange(pagination: any, filters: any, sorter: any, extra: any) {
|
||||
const { columnKey, order } = sorter;
|
||||
if (!order) return;
|
||||
if (order.startsWith(queryParams.sortOrder)) return;
|
||||
|
||||
if (order) {
|
||||
queryParams.sortField = columnKey;
|
||||
queryParams.sortOrder = order.replace('end', '');
|
||||
} else {
|
||||
queryParams.sortOrder = 'asc';
|
||||
}
|
||||
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**对象信息状态类型 */
|
||||
type StateType = {
|
||||
/**网元类型 */
|
||||
neType: string[];
|
||||
/**图表实时统计 */
|
||||
chartRealTime: boolean;
|
||||
/**图表标签选择 */
|
||||
chartLegendSelectedFlag: boolean;
|
||||
};
|
||||
|
||||
/**对象信息状态 */
|
||||
let state: StateType = reactive({
|
||||
neType: [],
|
||||
chartRealTime: false,
|
||||
chartLegendSelectedFlag: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* 数据列表导出
|
||||
*/
|
||||
function fnRecordExport() {
|
||||
Modal.confirm({
|
||||
title: 'Tip',
|
||||
content: t('views.perfManage.goldTarget.exportSure'),
|
||||
onOk() {
|
||||
const key = 'exportKPI';
|
||||
message.loading({ content: t('common.loading'), key });
|
||||
|
||||
if (tableState.data.length <= 0) {
|
||||
message.error({
|
||||
content: t('views.perfManage.goldTarget.exportEmpty'),
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const tableColumnsTitleArr: string[] = [];
|
||||
const tableColumnsKeyArr: string[] = [];
|
||||
for (const columns of tableColumnsDnd.value) {
|
||||
tableColumnsTitleArr.push(`${columns.title}`);
|
||||
tableColumnsKeyArr.push(`${columns.key}`);
|
||||
}
|
||||
|
||||
const kpiDataArr = [];
|
||||
for (const item of tableState.data) {
|
||||
const kpiData: Record<string, any> = {};
|
||||
const keys = Object.keys(item);
|
||||
for (let i = 0; i <= tableColumnsKeyArr.length; i++) {
|
||||
for (const key of keys) {
|
||||
if (tableColumnsKeyArr[i] === key) {
|
||||
const title = tableColumnsTitleArr[i];
|
||||
kpiData[title] = item[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
kpiDataArr.push(kpiData);
|
||||
}
|
||||
|
||||
writeSheet(kpiDataArr, 'KPI', { header: tableColumnsTitleArr })
|
||||
.then(fileBlob => saveAs(fileBlob, `kpi_data_${Date.now()}.xlsx`))
|
||||
.finally(() => {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: t('common.export') }),
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**查询数据列表表头 */
|
||||
function fnGetListTitle() {
|
||||
// 当前语言
|
||||
var language = currentLocale.value.split('_')[0];
|
||||
if (language === 'zh') language = 'cn';
|
||||
|
||||
// 获取表头文字
|
||||
listCustom({ neType: state.neType[0], status: 'Active' })
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
if (res.data.length === 0) {
|
||||
message.error({
|
||||
content: t('views.perfManage.customTarget.kpiIdTip'),
|
||||
duration: 2,
|
||||
});
|
||||
tableState.data = [];
|
||||
tableColumns.value = [];
|
||||
tableColumnsDnd.value = [];
|
||||
fnRanderChartData();
|
||||
return false;
|
||||
}
|
||||
tableColumns.value = [];
|
||||
const columns: ColumnsType = [];
|
||||
for (const item of res.data) {
|
||||
const kpiDisplay = item[`unit`]? item[`title`]+ `(${item['unit']})`:item[`title`];
|
||||
const kpiValue = item[`kpiId`];
|
||||
columns.push({
|
||||
title: kpiDisplay,
|
||||
dataIndex: kpiValue,
|
||||
align: 'left',
|
||||
key: kpiValue,
|
||||
resizable: true,
|
||||
width: 100,
|
||||
minWidth: 150,
|
||||
maxWidth: 300,
|
||||
});
|
||||
}
|
||||
columns.push({
|
||||
title: t('views.perfManage.perfData.neName'),
|
||||
dataIndex: 'neName',
|
||||
key: 'neName',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
});
|
||||
columns.push({
|
||||
title: t('views.perfManage.goldTarget.time'),
|
||||
dataIndex: 'timeGroup',
|
||||
align: 'left',
|
||||
fixed: 'right',
|
||||
key: 'timeGroup',
|
||||
sorter: true,
|
||||
width: 100,
|
||||
});
|
||||
|
||||
nextTick(() => {
|
||||
tableColumns.value = columns;
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('common.getInfoFail'),
|
||||
duration: 2,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.then(result => {
|
||||
result && fnGetList();
|
||||
});
|
||||
}
|
||||
|
||||
/**查询数据列表 */
|
||||
function fnGetList() {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
queryParams.neType = state.neType[0];
|
||||
queryParams.neId = state.neType[1];
|
||||
queryParams.startTime = queryRangePicker.value[0];
|
||||
queryParams.endTime = queryRangePicker.value[1];
|
||||
listCustomData(toRaw(queryParams))
|
||||
.then(res => {
|
||||
tableState.loading = false;
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
tablePagination.total = res.data.length;
|
||||
tableState.data = res.data;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.then(result => {
|
||||
if (result) {
|
||||
fnRanderChartData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**切换显示类型 图或表格 */
|
||||
function fnChangShowType() {
|
||||
tableState.showTable = !tableState.showTable;
|
||||
}
|
||||
|
||||
/**绘制图表 */
|
||||
function fnRanderChart() {
|
||||
const container: HTMLElement | undefined = kpiChartDom.value;
|
||||
if (!container) return;
|
||||
kpiChart.value = markRaw(echarts.init(container, 'light'));
|
||||
|
||||
const option: EChartsOption = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
position: function (pt: any) {
|
||||
return [pt[0], '10%'];
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: [], // 数据x轴
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
boundaryGap: [0, '100%'],
|
||||
},
|
||||
legend: {
|
||||
type: 'scroll',
|
||||
orient: 'vertical',
|
||||
top: 40,
|
||||
right: 20,
|
||||
itemWidth: 20,
|
||||
itemGap: 25,
|
||||
textStyle: {
|
||||
color: '#646A73',
|
||||
},
|
||||
icon: 'circle',
|
||||
selected: {},
|
||||
},
|
||||
grid: {
|
||||
left: '10%',
|
||||
right: '30%',
|
||||
bottom: '20%',
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: 'inside',
|
||||
start: 90,
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
start: 90,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: [], // 数据y轴
|
||||
};
|
||||
kpiChart.value.setOption(option);
|
||||
|
||||
// 创建 ResizeObserver 实例
|
||||
var observer = new ResizeObserver(entries => {
|
||||
if (kpiChart.value) {
|
||||
kpiChart.value.resize();
|
||||
}
|
||||
});
|
||||
// 监听元素大小变化
|
||||
observer.observe(container);
|
||||
}
|
||||
|
||||
/**图表标签选择 */
|
||||
let chartLegendSelected: Record<string, boolean> = {};
|
||||
/**图表配置数据x轴 */
|
||||
let chartDataXAxisData: string[] = [];
|
||||
/**图表配置数据y轴 */
|
||||
let chartDataYSeriesData: Record<string, any>[] = [];
|
||||
|
||||
/**图表数据渲染 */
|
||||
function fnRanderChartData() {
|
||||
if (kpiChart.value == null && tableState.data.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 重置
|
||||
chartLegendSelected = {};
|
||||
chartDataXAxisData = [];
|
||||
chartDataYSeriesData = [];
|
||||
|
||||
for (var columns of tableColumns.value) {
|
||||
if (
|
||||
columns.key === 'neName' ||
|
||||
columns.key === 'startIndex' ||
|
||||
columns.key === 'timeGroup'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const color = generateColorRGBA();
|
||||
chartDataYSeriesData.push({
|
||||
name: `${columns.title}`,
|
||||
key: `${columns.key}`,
|
||||
type: 'line',
|
||||
symbol: 'none',
|
||||
sampling: 'lttb',
|
||||
itemStyle: {
|
||||
color: color,
|
||||
},
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: color.replace(')', ',0.8)'),
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: color.replace(')', ',0.3)'),
|
||||
},
|
||||
]),
|
||||
},
|
||||
data: [],
|
||||
});
|
||||
chartLegendSelected[`${columns.title}`] = state.chartLegendSelectedFlag;
|
||||
}
|
||||
|
||||
// 用降序就反转
|
||||
let orgData = tableState.data;
|
||||
if (queryParams.sortOrder === 'desc') {
|
||||
orgData = orgData.toReversed();
|
||||
}
|
||||
|
||||
for (const item of orgData) {
|
||||
chartDataXAxisData.push(item['timeGroup']);
|
||||
const keys = Object.keys(item);
|
||||
for (const y of chartDataYSeriesData) {
|
||||
for (const key of keys) {
|
||||
if (y.key === key) {
|
||||
y.data.push(+item[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// console.log(queryParams.sortOrder, chartLegendSelected);
|
||||
// console.log(chartDataXAxisData, chartDataYSeriesData);
|
||||
|
||||
// 绘制图数据
|
||||
kpiChart.value.setOption(
|
||||
{
|
||||
legend: {
|
||||
selected: chartLegendSelected,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: chartDataXAxisData,
|
||||
},
|
||||
series: chartDataYSeriesData,
|
||||
},
|
||||
{
|
||||
replaceMerge: ['xAxis', 'series'],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**图表折线显示全部 */
|
||||
function fnLegendSelected(bool: any) {
|
||||
for (const key of Object.keys(chartLegendSelected)) {
|
||||
chartLegendSelected[key] = bool;
|
||||
}
|
||||
kpiChart.value.setOption({
|
||||
legend: {
|
||||
selected: chartLegendSelected,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**图表实时统计 */
|
||||
function fnRealTimeSwitch(bool: any) {
|
||||
if (bool) {
|
||||
tableState.seached = false;
|
||||
// 建立链接
|
||||
const options: OptionsType = {
|
||||
url: '/ws',
|
||||
params: {
|
||||
/**订阅通道组
|
||||
*
|
||||
* 指标(GroupID:10_neType_neId)
|
||||
*/
|
||||
subGroupID: `20_${queryParams.neType}_${queryParams.neId}`,
|
||||
},
|
||||
onmessage: wsMessage,
|
||||
onerror: wsError,
|
||||
};
|
||||
ws.connect(options);
|
||||
} else {
|
||||
tableState.seached = true;
|
||||
ws.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**接收数据后回调 */
|
||||
function wsError(ev: any) {
|
||||
// 接收数据后回调
|
||||
console.error(ev);
|
||||
}
|
||||
|
||||
/**接收数据后回调 */
|
||||
function wsMessage(res: Record<string, any>) {
|
||||
const { code, requestId, data } = res;
|
||||
if (code === RESULT_CODE_ERROR) {
|
||||
console.warn(res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// 订阅组信息
|
||||
if (!data?.groupId) {
|
||||
return;
|
||||
}
|
||||
// kpiEvent 黄金指标指标事件
|
||||
const kpiEvent = data.data;
|
||||
tableState.data.unshift(kpiEvent);
|
||||
tablePagination.total++;
|
||||
|
||||
// 非对应网元类型
|
||||
if (kpiEvent.neType !== queryParams.neType) return;
|
||||
|
||||
for (const key of Object.keys(data.data)) {
|
||||
const v = kpiEvent[key];
|
||||
// x轴
|
||||
if (key === 'timeGroup') {
|
||||
// chartDataXAxisData.shift();
|
||||
chartDataXAxisData.push(v);
|
||||
continue;
|
||||
}
|
||||
// y轴
|
||||
const yItem = chartDataYSeriesData.find(item => item.key === key);
|
||||
if (yItem) {
|
||||
// yItem.data.shift();
|
||||
yItem.data.push(+v);
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制图数据
|
||||
kpiChart.value.setOption({
|
||||
xAxis: {
|
||||
data: chartDataXAxisData,
|
||||
},
|
||||
series: chartDataYSeriesData,
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 目前支持的 AMF AUSF MME MOCNGW NSSF SMF UDM UPF PCF
|
||||
// 获取网元网元列表
|
||||
neInfoStore.fnNelist().then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
if (res.data.length > 0) {
|
||||
// 过滤不可用的网元
|
||||
neCascaderOptions.value = neInfoStore.getNeCascaderOptions.filter(
|
||||
(item: any) => {
|
||||
return !['OMC', 'NSSF', 'NEF', 'NRF', 'LMF', 'N3IWF'].includes(
|
||||
item.value
|
||||
);
|
||||
}
|
||||
);
|
||||
if (neCascaderOptions.value.length === 0) {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 无查询参数neType时 默认选择UPF
|
||||
const queryNeType = (route.query.neType as string) || 'UPF';
|
||||
const item = neCascaderOptions.value.find(s => s.value === queryNeType);
|
||||
if (item && item.children) {
|
||||
const info = item.children[0];
|
||||
state.neType = [info.neType, info.neId];
|
||||
queryParams.neType = info.neType;
|
||||
queryParams.neId = info.neId;
|
||||
} else {
|
||||
const info = neCascaderOptions.value[0].children[0];
|
||||
state.neType = [info.neType, info.neId];
|
||||
queryParams.neType = info.neType;
|
||||
queryParams.neId = info.neId;
|
||||
}
|
||||
|
||||
// 查询当前小时
|
||||
|
||||
const now = new Date();
|
||||
now.setMinutes(0, 0, 0);
|
||||
queryRangePicker.value[0] = parseDateToStr(now.getTime());
|
||||
now.setMinutes(59, 59, 59);
|
||||
queryRangePicker.value[1] = parseDateToStr(now.getTime());
|
||||
fnGetListTitle();
|
||||
// 绘图
|
||||
fnRanderChart();
|
||||
}
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
ws.close();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
v-show="tableState.seached"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParamsFrom" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
name="neType"
|
||||
:label="t('views.traceManage.task.neType')"
|
||||
>
|
||||
<a-cascader
|
||||
v-model:value="state.neType"
|
||||
:options="neCascaderOptions"
|
||||
:allow-clear="false"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="10" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.goldTarget.timeFrame')"
|
||||
name="timeFrame"
|
||||
>
|
||||
<a-range-picker
|
||||
v-model:value="queryRangePicker"
|
||||
bordered
|
||||
:allow-clear="false"
|
||||
:show-time="{ format: 'HH:mm:ss' }"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%"
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :lg="2" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button
|
||||
type="primary"
|
||||
:loading="tableState.loading"
|
||||
@click.prevent="fnGetListTitle()"
|
||||
>
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</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"
|
||||
:loading="tableState.loading"
|
||||
@click.prevent="fnChangShowType()"
|
||||
>
|
||||
<template #icon> <AreaChartOutlined /> </template>
|
||||
{{
|
||||
tableState.showTable
|
||||
? t('views.perfManage.goldTarget.kpiChartTitle')
|
||||
: t('views.perfManage.goldTarget.kpiTableTitle')
|
||||
}}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
:loading="tableState.loading"
|
||||
@click.prevent="fnRecordExport()"
|
||||
v-show="tableState.showTable"
|
||||
>
|
||||
<template #icon>
|
||||
<ExportOutlined />
|
||||
</template>
|
||||
{{ t('common.export') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center" v-show="tableState.showTable">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<TableColumnsDnd
|
||||
v-if="tableColumns.length > 0"
|
||||
:cache-id="`kpiTarget_${state.neType[0]}`"
|
||||
:columns="tableColumns"
|
||||
v-model:columns-dnd="tableColumnsDnd"
|
||||
></TableColumnsDnd>
|
||||
<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>
|
||||
<a-form layout="inline" v-show="!tableState.showTable">
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.goldTarget.showChartSelected')"
|
||||
name="chartLegendSelectedFlag"
|
||||
>
|
||||
<a-switch
|
||||
:disabled="tableState.loading"
|
||||
v-model:checked="state.chartLegendSelectedFlag"
|
||||
:checked-children="t('common.switch.open')"
|
||||
:un-checked-children="t('common.switch.shut')"
|
||||
@change="fnLegendSelected"
|
||||
size="small"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.goldTarget.realTimeData')"
|
||||
name="chartRealTime"
|
||||
>
|
||||
<a-switch
|
||||
:disabled="tableState.loading"
|
||||
v-model:checked="state.chartRealTime"
|
||||
:checked-children="t('common.switch.open')"
|
||||
:un-checked-children="t('common.switch.shut')"
|
||||
@change="fnRealTimeSwitch"
|
||||
size="small"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
v-show="tableState.showTable"
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumnsDnd"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: tableColumnsDnd.length * 200, y: 'calc(100vh - 480px)' }"
|
||||
@resizeColumn="(w:number, col:any) => (col.width = w)"
|
||||
:show-expand-column="false"
|
||||
@change="fnTableChange"
|
||||
>
|
||||
</a-table>
|
||||
|
||||
<!-- 图表 -->
|
||||
<div style="padding: 24px" v-show="!tableState.showTable">
|
||||
<div ref="kpiChartDom" style="height: 450px; width: 100%"></div>
|
||||
</div>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -1,543 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw } from 'vue';
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { Modal } 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 { parseDateToStr } from '@/utils/date-utils';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { saveAs } from 'file-saver';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { getTraceRawInfo, listTraceData } from '@/api/traceManage/analysis';
|
||||
const { t } = useI18n();
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**移动号 */
|
||||
imsi: '',
|
||||
/**移动号 */
|
||||
msisdn: '',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
imsi: '',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
seached: true,
|
||||
data: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('views.traceManage.analysis.trackTaskId'),
|
||||
dataIndex: 'taskId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.imsi'),
|
||||
dataIndex: 'imsi',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.msisdn'),
|
||||
dataIndex: 'msisdn',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.srcIp'),
|
||||
dataIndex: 'srcAddr',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.dstIp'),
|
||||
dataIndex: 'dstAddr',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.signalType'),
|
||||
dataIndex: 'ifType',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.msgType'),
|
||||
dataIndex: 'msgType',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.msgDirect'),
|
||||
dataIndex: 'msgDirect',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.rowTime'),
|
||||
dataIndex: 'timestamp',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
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;
|
||||
}
|
||||
listTraceData(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||
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 = {
|
||||
/**抽屉框是否显示 */
|
||||
visible: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
};
|
||||
|
||||
/**抽屉对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
visible: false,
|
||||
title: '',
|
||||
from: {
|
||||
rawData: '',
|
||||
rawDataHTML: '',
|
||||
downBtn: false,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 对话框弹出显示
|
||||
* @param row 记录信息
|
||||
*/
|
||||
function fnModalVisible(row: Record<string, any>) {
|
||||
// 进制转数据
|
||||
const hexString = parseBase64Data(row.rawMsg);
|
||||
const rawData = convertToReadableFormat(hexString);
|
||||
modalState.from.rawData = rawData;
|
||||
// RAW解析HTML
|
||||
getTraceRawInfo(row.id).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
const htmlString = rawDataHTMLScript(res.msg);
|
||||
modalState.from.rawDataHTML = htmlString;
|
||||
modalState.from.downBtn = true;
|
||||
} else {
|
||||
modalState.from.rawDataHTML = t('views.traceManage.analysis.noData');
|
||||
}
|
||||
});
|
||||
modalState.title = t('views.traceManage.analysis.taskTitle', {
|
||||
num: row.imsi,
|
||||
});
|
||||
modalState.visible = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭
|
||||
*/
|
||||
function fnModalVisibleClose() {
|
||||
modalState.visible = false;
|
||||
modalState.from.downBtn = false;
|
||||
modalState.from.rawDataHTML = '';
|
||||
modalState.from.rawData = '';
|
||||
}
|
||||
|
||||
// 将Base64编码解码为字节数组
|
||||
function parseBase64Data(hexData: string) {
|
||||
// 将Base64编码解码为字节数组
|
||||
const byteString = atob(hexData);
|
||||
const byteArray = new Uint8Array(byteString.length);
|
||||
for (let i = 0; i < byteString.length; i++) {
|
||||
byteArray[i] = byteString.charCodeAt(i);
|
||||
}
|
||||
|
||||
// 将每一个字节转换为2位16进制数表示,并拼接起来
|
||||
let hexString = '';
|
||||
for (let i = 0; i < byteArray.length; i++) {
|
||||
const hex = byteArray[i].toString(16);
|
||||
hexString += hex.length === 1 ? '0' + hex : hex;
|
||||
}
|
||||
return hexString;
|
||||
}
|
||||
|
||||
// 转换十六进制字节流为可读格式和ASCII码表示
|
||||
function convertToReadableFormat(hexString: string) {
|
||||
let result = '';
|
||||
let asciiResult = '';
|
||||
let arr = [];
|
||||
let row = 100;
|
||||
for (let i = 0; i < hexString.length; i += 2) {
|
||||
const hexChars = hexString.substring(i, i + 2);
|
||||
const decimal = parseInt(hexChars, 16);
|
||||
const asciiChar =
|
||||
decimal >= 32 && decimal <= 126 ? String.fromCharCode(decimal) : '.';
|
||||
|
||||
result += hexChars + ' ';
|
||||
asciiResult += asciiChar;
|
||||
|
||||
if ((i + 2) % 32 === 0) {
|
||||
arr.push({
|
||||
row: row,
|
||||
code: result,
|
||||
asciiText: asciiResult,
|
||||
});
|
||||
result = '';
|
||||
asciiResult = '';
|
||||
row += 10;
|
||||
}
|
||||
if (2 + i == hexString.length) {
|
||||
arr.push({
|
||||
row: row,
|
||||
code: result,
|
||||
asciiText: asciiResult,
|
||||
});
|
||||
result = '';
|
||||
asciiResult = '';
|
||||
row += 10;
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
// 信息详情HTMl内容处理
|
||||
function rawDataHTMLScript(htmlString: string) {
|
||||
// 删除所有 <a> 标签
|
||||
// const withoutATags = htmlString.replace(/<a\b[^>]*>(.*?)<\/a>/gi, '');
|
||||
// 删除所有 <script> 标签
|
||||
let withoutScriptTags = htmlString.replace(
|
||||
/<script\b[^>]*>([\s\S]*?)<\/script>/gi,
|
||||
''
|
||||
);
|
||||
// 默认全展开
|
||||
// const withoutHiddenElements = withoutScriptTags.replace(
|
||||
// /style="display:none"/gi,
|
||||
// 'style="background:#ffffff"'
|
||||
// );
|
||||
|
||||
function set_node(node: any, str: string) {
|
||||
if (!node) return;
|
||||
node.style.display = str;
|
||||
node.style.background = '#ffffff';
|
||||
}
|
||||
Reflect.set(window, 'set_node', set_node);
|
||||
function toggle_node(node: any) {
|
||||
node = document.getElementById(node);
|
||||
if (!node) return;
|
||||
set_node(node, node.style.display != 'none' ? 'none' : 'block');
|
||||
}
|
||||
Reflect.set(window, 'toggle_node', toggle_node);
|
||||
function hide_node(node: any) {
|
||||
node = document.getElementById(node);
|
||||
if (!node) return;
|
||||
set_node(node, 'none');
|
||||
}
|
||||
Reflect.set(window, 'hide_node', hide_node);
|
||||
|
||||
// 展开第一个
|
||||
withoutScriptTags = withoutScriptTags.replace(
|
||||
'id="f1c" style="display:none"',
|
||||
'id="f1c" style="display:block"'
|
||||
);
|
||||
return withoutScriptTags;
|
||||
}
|
||||
|
||||
/**信息文件下载 */
|
||||
function fnDownloadFile() {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.traceManage.analysis.taskDownTip'),
|
||||
onOk() {
|
||||
const blob = new Blob([modalState.from.rawDataHTML], {
|
||||
type: 'text/plain',
|
||||
});
|
||||
saveAs(blob, `${modalState.title}_${Date.now()}.html`);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
onMounted(() => {});
|
||||
</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.traceManage.analysis.imsi')"
|
||||
name="imsi"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="queryParams.imsi"
|
||||
:allow-clear="true"
|
||||
:placeholder="t('views.traceManage.analysis.imsiPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.analysis.msisdn')"
|
||||
name="imsi"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="queryParams.msisdn"
|
||||
:allow-clear="true"
|
||||
:placeholder="t('views.traceManage.analysis.msisdnPlease')"
|
||||
></a-input>
|
||||
</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> </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">
|
||||
<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: true }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>查看详情</template>
|
||||
<a-button type="link" @click.prevent="fnModalVisible(record)">
|
||||
<template #icon><ProfileOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<h1>Perf Report</h1>
|
||||
</a-card>
|
||||
|
||||
<!-- 详情框 -->
|
||||
<ProModal
|
||||
:drag="true"
|
||||
:width="800"
|
||||
:title="modalState.title"
|
||||
:visible="modalState.visible"
|
||||
@cancel="fnModalVisibleClose"
|
||||
>
|
||||
<div class="raw-title">
|
||||
{{ t('views.traceManage.analysis.signalData') }}
|
||||
</div>
|
||||
<a-row
|
||||
class="raw"
|
||||
:gutter="16"
|
||||
v-for="v in modalState.from.rawData"
|
||||
:key="v.row"
|
||||
>
|
||||
<a-col class="num" :span="2">{{ v.row }}</a-col>
|
||||
<a-col class="code" :span="12">{{ v.code }}</a-col>
|
||||
<a-col class="txt" :span="10">{{ v.asciiText }}</a-col>
|
||||
</a-row>
|
||||
<a-divider />
|
||||
<div class="raw-title">
|
||||
{{ t('views.traceManage.analysis.signalDetail') }}
|
||||
<a-button
|
||||
type="dashed"
|
||||
size="small"
|
||||
@click.prevent="fnDownloadFile"
|
||||
v-if="modalState.from.downBtn"
|
||||
>
|
||||
<template #icon>
|
||||
<DownloadOutlined />
|
||||
</template>
|
||||
{{ t('views.traceManage.analysis.taskDownText') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="raw-html" v-html="modalState.from.rawDataHTML"></div>
|
||||
</ProModal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.raw {
|
||||
&-title {
|
||||
color: #000000d9;
|
||||
font-size: 24px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.num {
|
||||
background-color: #e5e5e5;
|
||||
}
|
||||
.code {
|
||||
background-color: #e7e6ff;
|
||||
}
|
||||
.txt {
|
||||
background-color: #ffe3e5;
|
||||
}
|
||||
|
||||
&-html {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less" scoped></style>
|
||||
|
||||
@@ -1,543 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw } from 'vue';
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { Modal } 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 { parseDateToStr } from '@/utils/date-utils';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { saveAs } from 'file-saver';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { getTraceRawInfo, listTraceData } from '@/api/traceManage/analysis';
|
||||
const { t } = useI18n();
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**移动号 */
|
||||
imsi: '',
|
||||
/**移动号 */
|
||||
msisdn: '',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
imsi: '',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
seached: true,
|
||||
data: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('views.traceManage.analysis.trackTaskId'),
|
||||
dataIndex: 'taskId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.imsi'),
|
||||
dataIndex: 'imsi',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.msisdn'),
|
||||
dataIndex: 'msisdn',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.srcIp'),
|
||||
dataIndex: 'srcAddr',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.dstIp'),
|
||||
dataIndex: 'dstAddr',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.signalType'),
|
||||
dataIndex: 'ifType',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.msgType'),
|
||||
dataIndex: 'msgType',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.msgDirect'),
|
||||
dataIndex: 'msgDirect',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.rowTime'),
|
||||
dataIndex: 'timestamp',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
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;
|
||||
}
|
||||
listTraceData(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||
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 = {
|
||||
/**抽屉框是否显示 */
|
||||
visible: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
};
|
||||
|
||||
/**抽屉对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
visible: false,
|
||||
title: '',
|
||||
from: {
|
||||
rawData: '',
|
||||
rawDataHTML: '',
|
||||
downBtn: false,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 对话框弹出显示
|
||||
* @param row 记录信息
|
||||
*/
|
||||
function fnModalVisible(row: Record<string, any>) {
|
||||
// 进制转数据
|
||||
const hexString = parseBase64Data(row.rawMsg);
|
||||
const rawData = convertToReadableFormat(hexString);
|
||||
modalState.from.rawData = rawData;
|
||||
// RAW解析HTML
|
||||
getTraceRawInfo(row.id).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
const htmlString = rawDataHTMLScript(res.msg);
|
||||
modalState.from.rawDataHTML = htmlString;
|
||||
modalState.from.downBtn = true;
|
||||
} else {
|
||||
modalState.from.rawDataHTML = t('views.traceManage.analysis.noData');
|
||||
}
|
||||
});
|
||||
modalState.title = t('views.traceManage.analysis.taskTitle', {
|
||||
num: row.imsi,
|
||||
});
|
||||
modalState.visible = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭
|
||||
*/
|
||||
function fnModalVisibleClose() {
|
||||
modalState.visible = false;
|
||||
modalState.from.downBtn = false;
|
||||
modalState.from.rawDataHTML = '';
|
||||
modalState.from.rawData = '';
|
||||
}
|
||||
|
||||
// 将Base64编码解码为字节数组
|
||||
function parseBase64Data(hexData: string) {
|
||||
// 将Base64编码解码为字节数组
|
||||
const byteString = atob(hexData);
|
||||
const byteArray = new Uint8Array(byteString.length);
|
||||
for (let i = 0; i < byteString.length; i++) {
|
||||
byteArray[i] = byteString.charCodeAt(i);
|
||||
}
|
||||
|
||||
// 将每一个字节转换为2位16进制数表示,并拼接起来
|
||||
let hexString = '';
|
||||
for (let i = 0; i < byteArray.length; i++) {
|
||||
const hex = byteArray[i].toString(16);
|
||||
hexString += hex.length === 1 ? '0' + hex : hex;
|
||||
}
|
||||
return hexString;
|
||||
}
|
||||
|
||||
// 转换十六进制字节流为可读格式和ASCII码表示
|
||||
function convertToReadableFormat(hexString: string) {
|
||||
let result = '';
|
||||
let asciiResult = '';
|
||||
let arr = [];
|
||||
let row = 100;
|
||||
for (let i = 0; i < hexString.length; i += 2) {
|
||||
const hexChars = hexString.substring(i, i + 2);
|
||||
const decimal = parseInt(hexChars, 16);
|
||||
const asciiChar =
|
||||
decimal >= 32 && decimal <= 126 ? String.fromCharCode(decimal) : '.';
|
||||
|
||||
result += hexChars + ' ';
|
||||
asciiResult += asciiChar;
|
||||
|
||||
if ((i + 2) % 32 === 0) {
|
||||
arr.push({
|
||||
row: row,
|
||||
code: result,
|
||||
asciiText: asciiResult,
|
||||
});
|
||||
result = '';
|
||||
asciiResult = '';
|
||||
row += 10;
|
||||
}
|
||||
if (2 + i == hexString.length) {
|
||||
arr.push({
|
||||
row: row,
|
||||
code: result,
|
||||
asciiText: asciiResult,
|
||||
});
|
||||
result = '';
|
||||
asciiResult = '';
|
||||
row += 10;
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
// 信息详情HTMl内容处理
|
||||
function rawDataHTMLScript(htmlString: string) {
|
||||
// 删除所有 <a> 标签
|
||||
// const withoutATags = htmlString.replace(/<a\b[^>]*>(.*?)<\/a>/gi, '');
|
||||
// 删除所有 <script> 标签
|
||||
let withoutScriptTags = htmlString.replace(
|
||||
/<script\b[^>]*>([\s\S]*?)<\/script>/gi,
|
||||
''
|
||||
);
|
||||
// 默认全展开
|
||||
// const withoutHiddenElements = withoutScriptTags.replace(
|
||||
// /style="display:none"/gi,
|
||||
// 'style="background:#ffffff"'
|
||||
// );
|
||||
|
||||
function set_node(node: any, str: string) {
|
||||
if (!node) return;
|
||||
node.style.display = str;
|
||||
node.style.background = '#ffffff';
|
||||
}
|
||||
Reflect.set(window, 'set_node', set_node);
|
||||
function toggle_node(node: any) {
|
||||
node = document.getElementById(node);
|
||||
if (!node) return;
|
||||
set_node(node, node.style.display != 'none' ? 'none' : 'block');
|
||||
}
|
||||
Reflect.set(window, 'toggle_node', toggle_node);
|
||||
function hide_node(node: any) {
|
||||
node = document.getElementById(node);
|
||||
if (!node) return;
|
||||
set_node(node, 'none');
|
||||
}
|
||||
Reflect.set(window, 'hide_node', hide_node);
|
||||
|
||||
// 展开第一个
|
||||
withoutScriptTags = withoutScriptTags.replace(
|
||||
'id="f1c" style="display:none"',
|
||||
'id="f1c" style="display:block"'
|
||||
);
|
||||
return withoutScriptTags;
|
||||
}
|
||||
|
||||
/**信息文件下载 */
|
||||
function fnDownloadFile() {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.traceManage.analysis.taskDownTip'),
|
||||
onOk() {
|
||||
const blob = new Blob([modalState.from.rawDataHTML], {
|
||||
type: 'text/plain',
|
||||
});
|
||||
saveAs(blob, `${modalState.title}_${Date.now()}.html`);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
onMounted(() => {});
|
||||
</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.traceManage.analysis.imsi')"
|
||||
name="imsi"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="queryParams.imsi"
|
||||
:allow-clear="true"
|
||||
:placeholder="t('views.traceManage.analysis.imsiPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.analysis.msisdn')"
|
||||
name="imsi"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="queryParams.msisdn"
|
||||
:allow-clear="true"
|
||||
:placeholder="t('views.traceManage.analysis.msisdnPlease')"
|
||||
></a-input>
|
||||
</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> </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">
|
||||
<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: true }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>查看详情</template>
|
||||
<a-button type="link" @click.prevent="fnModalVisible(record)">
|
||||
<template #icon><ProfileOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<h1>Perf Set</h1>
|
||||
</a-card>
|
||||
|
||||
<!-- 详情框 -->
|
||||
<ProModal
|
||||
:drag="true"
|
||||
:width="800"
|
||||
:title="modalState.title"
|
||||
:visible="modalState.visible"
|
||||
@cancel="fnModalVisibleClose"
|
||||
>
|
||||
<div class="raw-title">
|
||||
{{ t('views.traceManage.analysis.signalData') }}
|
||||
</div>
|
||||
<a-row
|
||||
class="raw"
|
||||
:gutter="16"
|
||||
v-for="v in modalState.from.rawData"
|
||||
:key="v.row"
|
||||
>
|
||||
<a-col class="num" :span="2">{{ v.row }}</a-col>
|
||||
<a-col class="code" :span="12">{{ v.code }}</a-col>
|
||||
<a-col class="txt" :span="10">{{ v.asciiText }}</a-col>
|
||||
</a-row>
|
||||
<a-divider />
|
||||
<div class="raw-title">
|
||||
{{ t('views.traceManage.analysis.signalDetail') }}
|
||||
<a-button
|
||||
type="dashed"
|
||||
size="small"
|
||||
@click.prevent="fnDownloadFile"
|
||||
v-if="modalState.from.downBtn"
|
||||
>
|
||||
<template #icon>
|
||||
<DownloadOutlined />
|
||||
</template>
|
||||
{{ t('views.traceManage.analysis.taskDownText') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="raw-html" v-html="modalState.from.rawDataHTML"></div>
|
||||
</ProModal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.raw {
|
||||
&-title {
|
||||
color: #000000d9;
|
||||
font-size: 24px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.num {
|
||||
background-color: #e5e5e5;
|
||||
}
|
||||
.code {
|
||||
background-color: #e7e6ff;
|
||||
}
|
||||
.txt {
|
||||
background-color: #ffe3e5;
|
||||
}
|
||||
|
||||
&-html {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less" scoped></style>
|
||||
|
||||
@@ -1128,7 +1128,7 @@ onMounted(() => {
|
||||
</a-row>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.comment')"
|
||||
:label="t('views.traceManage.task.remark')"
|
||||
name="comment"
|
||||
>
|
||||
<a-textarea
|
||||
@@ -1136,7 +1136,7 @@ onMounted(() => {
|
||||
:auto-size="{ minRows: 2, maxRows: 6 }"
|
||||
:maxlength="250"
|
||||
:show-count="true"
|
||||
:placeholder="t('views.traceManage.task.commentPlease')"
|
||||
:placeholder="t('views.traceManage.task.remarkPlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
274
src/views/tool/net/index.vue
Normal file
274
src/views/tool/net/index.vue
Normal file
@@ -0,0 +1,274 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive ,toRaw, onMounted } 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 useI18n from '@/hooks/useI18n';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
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,
|
||||
});
|
||||
|
||||
//临时缓存
|
||||
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)//设置定时器
|
||||
});
|
||||
|
||||
/**查询按钮**/
|
||||
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();//建立新的实时数据连接
|
||||
}
|
||||
s = setInterval(()=>{//设置新的定时器s
|
||||
extracted();
|
||||
},queryParams.changeTime)
|
||||
}
|
||||
|
||||
/**刷新频率改变**/
|
||||
function fnRealTime2() {//时间粒度改变时触发
|
||||
queryReset2(true)//改变定时器
|
||||
}
|
||||
/**
|
||||
* 实时数据
|
||||
*/
|
||||
function fnRealTime() {
|
||||
const options: OptionsType = {
|
||||
url: '/ws',
|
||||
onmessage: wsMessage,
|
||||
onerror: wsError,
|
||||
};
|
||||
//建立连接
|
||||
ws.connect(options);
|
||||
}
|
||||
|
||||
/**接收数据后回调(失败) */
|
||||
function wsError(ev: any) {
|
||||
// 接收数据后回调
|
||||
console.error(ev);
|
||||
}
|
||||
|
||||
/**接收数据后回调(成功) */
|
||||
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;
|
||||
|
||||
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 };
|
||||
});
|
||||
|
||||
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: [],
|
||||
});
|
||||
|
||||
/**表格状态类型 */
|
||||
type TableStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
};
|
||||
|
||||
/**表格字段列 */
|
||||
const tableColumns: ColumnsType<any> = [
|
||||
{
|
||||
title: t('views.tool.net.PID'),
|
||||
dataIndex: 'pid',
|
||||
align: 'center',
|
||||
width: 50,
|
||||
sorter:{//PID排序
|
||||
compare:(a:any, b:any)=>a.pid-b.pid,
|
||||
multiple:1,
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t('views.tool.net.name'),
|
||||
dataIndex: 'name',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.tool.net.localAddr'),
|
||||
dataIndex: 'localAddr',
|
||||
align: 'center',
|
||||
width: 70,
|
||||
},
|
||||
{
|
||||
title: t('views.tool.net.remoteAddr'),
|
||||
dataIndex:'remoteAddr',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.tool.net.status'),
|
||||
dataIndex: 'status',
|
||||
align: 'center',
|
||||
width: 70,
|
||||
},
|
||||
{
|
||||
title: t('views.tool.net.type'),
|
||||
dataIndex: 'type',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<a-form :model="queryParams" name="formParams" 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-input-number
|
||||
v-model:value="queryParams.pid"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
style='width: 100%'
|
||||
></a-input-number>
|
||||
</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-input
|
||||
v-model:value="queryParams.name"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</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
|
||||
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>
|
||||
</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>
|
||||
</PageContainer>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
367
src/views/tool/ps/index.vue
Normal file
367
src/views/tool/ps/index.vue
Normal file
@@ -0,0 +1,367 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
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 { diffValue, parseDuration } from '@/utils/date-utils';
|
||||
import { parseSizeFromFile } from '@/utils/parse-utils';
|
||||
|
||||
const { t } = useI18n();
|
||||
const ws = new WS();
|
||||
|
||||
/**表单查询参数 */
|
||||
let queryParams = reactive({
|
||||
pid: undefined,
|
||||
name: '',
|
||||
username: '',
|
||||
});
|
||||
|
||||
/**状态对象 */
|
||||
let state = reactive({
|
||||
/**调度器 */
|
||||
interval: null as any,
|
||||
/**刷新周期 */
|
||||
intervalTime: 5_000,
|
||||
/**查询参数 */
|
||||
query: {
|
||||
pid: undefined,
|
||||
name: '',
|
||||
username: '',
|
||||
},
|
||||
});
|
||||
|
||||
/**接收数据后回调(成功) */
|
||||
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('ps_')) {
|
||||
// 将数据填入表格
|
||||
if (Array.isArray(data)) {
|
||||
tableState.data = data;
|
||||
} else {
|
||||
tableState.data = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**实时数据*/
|
||||
function fnRealTime(reLink: boolean) {
|
||||
if (reLink) {
|
||||
ws.close();
|
||||
}
|
||||
const options: OptionsType = {
|
||||
url: '/ws',
|
||||
onmessage: wsMessage,
|
||||
onerror: (ev: any) => {
|
||||
// 接收数据后回调
|
||||
console.error(ev);
|
||||
},
|
||||
};
|
||||
//建立连接
|
||||
ws.connect(options);
|
||||
}
|
||||
|
||||
/**调度器周期变更*/
|
||||
function fnIntervalChange(v: any) {
|
||||
clearInterval(state.interval);
|
||||
const timer = parseInt(v);
|
||||
if (timer > 1_000) {
|
||||
state.intervalTime = v;
|
||||
fnGetList();
|
||||
}
|
||||
}
|
||||
|
||||
/**查询列表 */
|
||||
function fnGetList() {
|
||||
if (tableState.loading || ws.state() === -1) return;
|
||||
tableState.loading = true;
|
||||
const msg = {
|
||||
requestId: `ps_${state.interval}`,
|
||||
type: 'ps',
|
||||
data: state.query,
|
||||
};
|
||||
// 首发
|
||||
ws.send(msg);
|
||||
// 定时刷新数据
|
||||
state.interval = setInterval(() => {
|
||||
msg.data = state.query;
|
||||
ws.send(msg);
|
||||
}, state.intervalTime);
|
||||
tableState.loading = false;
|
||||
}
|
||||
|
||||
/**查询参数传入 */
|
||||
function fnQuery() {
|
||||
state.query = JSON.parse(JSON.stringify(queryParams));
|
||||
nextTick(() => {
|
||||
ws.send({
|
||||
requestId: `ps_${state.interval}`,
|
||||
type: 'ps',
|
||||
data: state.query,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
Object.assign(queryParams, {
|
||||
pid: undefined,
|
||||
name: '',
|
||||
username: '',
|
||||
});
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
// 重置查询条件
|
||||
Object.assign(state.query, {
|
||||
pid: undefined,
|
||||
name: '',
|
||||
username: '',
|
||||
});
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TableStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TableStateType = reactive({
|
||||
loading: false,
|
||||
data: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
const tableColumns: ColumnsType<any> = [
|
||||
{
|
||||
title: t('views.tool.ps.pid'),
|
||||
dataIndex: 'pid',
|
||||
align: 'right',
|
||||
width: 100,
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => a.pid - b.pid,
|
||||
multiple: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.tool.ps.cpuPercent'),
|
||||
dataIndex: 'cpuPercent',
|
||||
align: 'left',
|
||||
width: 120,
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => a.cpuPercent - b.cpuPercent,
|
||||
multiple: 3,
|
||||
},
|
||||
customRender(opt) {
|
||||
return `${opt.value} %`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.tool.ps.diskRead'),
|
||||
dataIndex: 'diskRead',
|
||||
align: 'right',
|
||||
width: 100,
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => a.diskRead - b.diskRead,
|
||||
multiple: 3,
|
||||
},
|
||||
customRender(opt) {
|
||||
return parseSizeFromFile(+opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.tool.ps.diskWrite'),
|
||||
dataIndex: 'diskWrite',
|
||||
align: 'right',
|
||||
width: 100,
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => a.diskWrite - b.diskWrite,
|
||||
multiple: 3,
|
||||
},
|
||||
customRender(opt) {
|
||||
return parseSizeFromFile(+opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.tool.ps.numThreads'),
|
||||
dataIndex: 'numThreads',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
sorter: {
|
||||
//线程数比较大小
|
||||
compare: (a: any, b: any) => a.numThreads - b.numThreads,
|
||||
multiple: 4, //优先级4
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.tool.ps.runTime'),
|
||||
dataIndex: 'startTime',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
customRender(opt) {
|
||||
const second = diffValue(Date.now(), +opt.value, 'second');
|
||||
return parseDuration(second);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.tool.ps.username'),
|
||||
dataIndex: 'username',
|
||||
align: 'left',
|
||||
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(() => {
|
||||
ws.close();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<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%"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<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-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.tool.ps.username')" name="username">
|
||||
<a-input
|
||||
v-model:value="queryParams.username"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</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>
|
||||
<div>{{ state.query }}</div>
|
||||
<div>{{ queryParams }}</div>
|
||||
</a-card>
|
||||
|
||||
<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="pid"
|
||||
: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;
|
||||
}
|
||||
</style>
|
||||
@@ -9,7 +9,7 @@ import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { saveAs } from 'file-saver';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { getTraceRawInfo, listTraceData } from '@/api/traceManage/analysis';
|
||||
import { getTraceRawInfo, listTraceData } from '@/api/trace/analysis';
|
||||
const { t } = useI18n();
|
||||
|
||||
/**查询参数 */
|
||||
|
||||
404
src/views/traceManage/pcap/file.vue
Normal file
404
src/views/traceManage/pcap/file.vue
Normal file
@@ -0,0 +1,404 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, toRaw } 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 { 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 useNeInfoStore from '@/store/modules/neinfo';
|
||||
import useTabsStore from '@/store/modules/tabs';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import saveAs from 'file-saver';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
const tabsStore = useTabsStore();
|
||||
const neInfoStore = useNeInfoStore();
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
// 获取地址栏参数
|
||||
const routeParams = route.query as Record<string, any>;
|
||||
|
||||
/**网元参数 */
|
||||
let neTypeSelect = ref<string[]>([]);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**网元类型 */
|
||||
neType: '',
|
||||
neId: '',
|
||||
/**读取路径 */
|
||||
path: '',
|
||||
/**前缀过滤 */
|
||||
search: '',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'small',
|
||||
data: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = reactive([
|
||||
{
|
||||
title: t('views.logManage.neFile.fileMode'),
|
||||
dataIndex: 'fileMode',
|
||||
align: 'center',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.logManage.neFile.size'),
|
||||
dataIndex: 'size',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.logManage.neFile.modifiedTime'),
|
||||
dataIndex: 'modifiedTime',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value * 1000);
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.logManage.neFile.fileName'),
|
||||
dataIndex: 'fileName',
|
||||
align: 'left',
|
||||
resizable: true,
|
||||
width: 200,
|
||||
minWidth: 100,
|
||||
maxWidth: 350,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'fileName',
|
||||
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;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**下载触发等待 */
|
||||
let downLoading = ref<boolean>(false);
|
||||
|
||||
/**信息文件下载 */
|
||||
function fnDownloadFile(row: Record<string, any>) {
|
||||
if (downLoading.value) return;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.logManage.neFile.downTip', { fileName: row.fileName }),
|
||||
onOk() {
|
||||
downLoading.value = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
getNeFile({
|
||||
neType: queryParams.neType,
|
||||
neId: queryParams.neId,
|
||||
path: queryParams.path,
|
||||
fileName: row.fileName,
|
||||
delTemp: true,
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', {
|
||||
msg: t('common.downloadText'),
|
||||
}),
|
||||
duration: 2,
|
||||
});
|
||||
saveAs(res.data, `${row.fileName}`);
|
||||
} else {
|
||||
message.error({
|
||||
content: t('views.logManage.neFile.downTipErr'),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
downLoading.value = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**tmp目录下,UPF标准版内部输出目录 */
|
||||
let tmp = ref<boolean>(false);
|
||||
|
||||
/**UPF标准版内部抓包的输出目录 */
|
||||
function fnUPFTmp() {
|
||||
fnDirCD('/tmp', 0);
|
||||
}
|
||||
|
||||
/**关闭跳转 */
|
||||
function fnClose() {
|
||||
const to = tabsStore.tabClose(route.path);
|
||||
if (to) {
|
||||
router.push(to);
|
||||
} else {
|
||||
router.back();
|
||||
}
|
||||
}
|
||||
|
||||
/**访问路径 */
|
||||
let nePathArr = ref<string[]>([]);
|
||||
|
||||
/**进入目录 */
|
||||
function fnDirCD(dir: string, index?: number) {
|
||||
if (index === undefined) {
|
||||
nePathArr.value.push(dir);
|
||||
queryParams.search = '';
|
||||
fnGetList(1);
|
||||
return;
|
||||
}
|
||||
if (index === 0) {
|
||||
const neType = queryParams.neType;
|
||||
if (neType === 'UPF' && tmp.value) {
|
||||
nePathArr.value = ['/tmp'];
|
||||
queryParams.search = `${neType}_${queryParams.neId}`;
|
||||
} else {
|
||||
nePathArr.value = [
|
||||
`/tmp/omc/tcpdump/${neType.toLowerCase()}/${queryParams.neId}`,
|
||||
];
|
||||
queryParams.search = '';
|
||||
}
|
||||
|
||||
fnGetList(1);
|
||||
} else {
|
||||
nePathArr.value = nePathArr.value.slice(0, index + 1);
|
||||
queryParams.search = '';
|
||||
fnGetList(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**网元类型选择对应修改 */
|
||||
function fnNeChange(keys: any, _: any) {
|
||||
// 不是同类型时需要重新加载
|
||||
if (Array.isArray(keys) && queryParams.neType !== keys[0]) {
|
||||
const neType = keys[0];
|
||||
queryParams.neType = neType;
|
||||
queryParams.neId = keys[1];
|
||||
if (neType === 'UPF' && tmp.value) {
|
||||
nePathArr.value = ['/tmp'];
|
||||
queryParams.search = `${neType}_${keys[1]}`;
|
||||
} else {
|
||||
nePathArr.value = [`/tmp/omc/tcpdump/${neType.toLowerCase()}/${keys[1]}`];
|
||||
queryParams.search = '';
|
||||
}
|
||||
fnGetList(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**查询列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (queryParams.neId === '') {
|
||||
message.warning({
|
||||
content: t('views.logManage.neFile.neTypePlease'),
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
queryParams.path = nePathArr.value.join('/');
|
||||
listNeFiles(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
message.error(res.msg, 3);
|
||||
tablePagination.total = 0;
|
||||
tableState.data = [];
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取网元网元列表
|
||||
neInfoStore.fnNelist().then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
if (res.data.length === 0) {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
} else if (routeParams.neType && routeParams.neId) {
|
||||
neTypeSelect.value = [routeParams.neType, routeParams.neId];
|
||||
fnNeChange(neTypeSelect.value, undefined);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16" :wrap="true">
|
||||
<a-col>
|
||||
<a-form-item style="margin-bottom: 0">
|
||||
<a-button type="default" @click.prevent="fnClose()">
|
||||
<template #icon><CloseOutlined /></template>
|
||||
{{ t('common.close') }}
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col>
|
||||
<a-form-item
|
||||
:label="t('views.logManage.neFile.neType')"
|
||||
name="neType"
|
||||
style="margin-bottom: 0"
|
||||
>
|
||||
<a-cascader
|
||||
v-model:value="neTypeSelect"
|
||||
:options="neInfoStore.getNeCascaderOptions"
|
||||
@change="fnNeChange"
|
||||
:allow-clear="false"
|
||||
:placeholder="t('views.logManage.neFile.neTypePlease')"
|
||||
:disabled="downLoading || tableState.loading"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col v-if="nePathArr.length > 0">
|
||||
<a-form-item
|
||||
:label="t('views.logManage.neFile.nePath')"
|
||||
name="configName"
|
||||
style="margin-bottom: 0"
|
||||
>
|
||||
<a-breadcrumb>
|
||||
<a-breadcrumb-item
|
||||
v-for="(path, index) in nePathArr"
|
||||
:key="path"
|
||||
@click="fnDirCD(path, index)"
|
||||
>
|
||||
{{ path }}
|
||||
</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip placement="topRight" v-if="neTypeSelect[0] === 'UPF'">
|
||||
<template #title>
|
||||
{{ t('views.traceManage.pcap.fileUPFTip') }}
|
||||
</template>
|
||||
<a-checkbox v-model:checked="tmp" @change="fnUPFTmp()">
|
||||
{{ t('views.traceManage.pcap.fileUPF') }}
|
||||
</a-checkbox>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="fileName"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: 800 }"
|
||||
@resizeColumn="(w:number, col:any) => (col.width = w)"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'fileName'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-button
|
||||
type="link"
|
||||
:loading="downLoading"
|
||||
@click.prevent="fnDownloadFile(record)"
|
||||
v-if="record.fileType === 'file'"
|
||||
>
|
||||
<template #icon><DownloadOutlined /></template>
|
||||
{{ t('common.downloadText') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
:loading="downLoading"
|
||||
@click.prevent="fnDirCD(record.fileName)"
|
||||
v-if="record.fileType === 'dir'"
|
||||
>
|
||||
<template #icon><FolderOutlined /></template>
|
||||
{{ t('views.logManage.neFile.dirCd') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -1,24 +1,67 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive } from 'vue';
|
||||
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, traceUPF } from '@/api/traceManage/pcap';
|
||||
import { dumpStart, dumpStop, dumpDownload, traceUPF } from '@/api/trace/pcap';
|
||||
import { listAllNeInfo } from '@/api/ne/neInfo';
|
||||
import { getNeFile } from '@/api/tool/neFile';
|
||||
import saveAs from 'file-saver';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { MENU_PATH_INLINE } from '@/constants/menu-constants';
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
from: Record<
|
||||
string,
|
||||
{
|
||||
loading: boolean;
|
||||
/**网元名 */
|
||||
title: string;
|
||||
/**命令 */
|
||||
cmdStart: string;
|
||||
/**upf标准版需要停止命令,一般空字符 */
|
||||
cmdStop: string;
|
||||
/**任务编号 */
|
||||
taskCode: string;
|
||||
/**任务日志,upf标准版为空字符串 */
|
||||
logMsg: string;
|
||||
/**提交表单参数 */
|
||||
data: {
|
||||
neType: string;
|
||||
neId: string;
|
||||
cmd?: string;
|
||||
};
|
||||
}
|
||||
>;
|
||||
/**tcpdump命令组 */
|
||||
cmdOptions: Record<string, any>[];
|
||||
cmdOptions: {
|
||||
/**命令名称 */
|
||||
label: string;
|
||||
/**命令选中值 */
|
||||
value: string;
|
||||
/**开始命令 */
|
||||
start: string;
|
||||
/**停止命令 */
|
||||
stop: string;
|
||||
}[];
|
||||
/**UPF命令组 */
|
||||
cmdOptionsUPF: Record<string, any>[];
|
||||
cmdOptionsUPF: {
|
||||
/**命令名称 */
|
||||
label: string;
|
||||
/**命令选中值 */
|
||||
value: string;
|
||||
/**开始命令 */
|
||||
start: string;
|
||||
/**停止命令 */
|
||||
stop: string;
|
||||
}[];
|
||||
/**详情框是否显示 */
|
||||
visibleByView: boolean;
|
||||
/**详情框内容 */
|
||||
@@ -31,29 +74,35 @@ let modalState: ModalStateType = reactive({
|
||||
cmdOptions: [
|
||||
{
|
||||
label: t('views.traceManage.pcap.execCmd'),
|
||||
start: '-n -s 0 -v -w',
|
||||
stop: '',
|
||||
value: 'any',
|
||||
start: '-n -v -s 0',
|
||||
stop: '',
|
||||
},
|
||||
{
|
||||
label: t('views.traceManage.pcap.execCmdsSctp'),
|
||||
label: t('views.traceManage.pcap.execCmd2'),
|
||||
value: 'any2',
|
||||
start: 'sctp or tcp port 3030 or 8088',
|
||||
stop: '',
|
||||
value: 'any2',
|
||||
},
|
||||
{
|
||||
label: t('views.traceManage.pcap.execCmd3'),
|
||||
value: 'any3',
|
||||
start: '-n -s 0 -v -G 10 -W 7',
|
||||
stop: '',
|
||||
},
|
||||
],
|
||||
cmdOptionsUPF: [
|
||||
{
|
||||
label: t('views.traceManage.pcap.execUPFCmdA'),
|
||||
value: 'pcap trace',
|
||||
start: 'pcap trace rx tx max 100000 intfc any',
|
||||
stop: 'pcap trace rx tx off',
|
||||
value: 'pcap trace',
|
||||
},
|
||||
{
|
||||
label: t('views.traceManage.pcap.execUPFCmdB'),
|
||||
value: 'pcap dispatch',
|
||||
start: 'pcap dispatch trace on max 100000',
|
||||
stop: 'pcap dispatch trace off',
|
||||
value: 'pcap dispatch',
|
||||
},
|
||||
],
|
||||
visibleByView: false,
|
||||
@@ -80,25 +129,25 @@ let tableState: TabeStateType = reactive({
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('views.configManage.neManage.neType'),
|
||||
title: t('views.ne.common.neType'),
|
||||
dataIndex: 'neType',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.configManage.neManage.neId'),
|
||||
title: t('views.ne.common.neId'),
|
||||
dataIndex: 'neId',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.configManage.neManage.neName'),
|
||||
title: t('views.ne.common.neName'),
|
||||
dataIndex: 'neName',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.configManage.neManage.ip'),
|
||||
title: t('views.ne.common.ipAddr'),
|
||||
dataIndex: 'ip',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
@@ -108,13 +157,12 @@ let tableColumns: ColumnsType = [
|
||||
key: 'cmd',
|
||||
dataIndex: 'serverState',
|
||||
align: 'left',
|
||||
width: 300,
|
||||
width: 350,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
align: 'left',
|
||||
fixed: 'right',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -137,20 +185,22 @@ function fnGetList() {
|
||||
) {
|
||||
tableState.data = res.data;
|
||||
// 初始网元参数表单
|
||||
const { start, stop } = modalState.cmdOptions[0];
|
||||
for (const item of res.data) {
|
||||
modalState.from[item.id] = {
|
||||
loading: false,
|
||||
title: item.neName, // 网元名
|
||||
cmdStart: start,
|
||||
cmdStop: stop, // upf需要停止命令
|
||||
out: '',
|
||||
log: '',
|
||||
data: {
|
||||
neType: item.neType,
|
||||
neId: item.neId,
|
||||
},
|
||||
};
|
||||
if (tableState.data.length > 0) {
|
||||
const { start, stop } = modalState.cmdOptions[0];
|
||||
for (const item of res.data) {
|
||||
modalState.from[item.id] = {
|
||||
loading: false,
|
||||
title: item.neName,
|
||||
cmdStart: start,
|
||||
cmdStop: stop,
|
||||
taskCode: '',
|
||||
logMsg: '',
|
||||
data: {
|
||||
neType: item.neType,
|
||||
neId: item.neId,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
message.warning({
|
||||
@@ -166,6 +216,9 @@ function fnGetList() {
|
||||
function fnSelectCmd(id: any, option: any) {
|
||||
modalState.from[id].cmdStart = option.start;
|
||||
modalState.from[id].cmdStop = option.stop;
|
||||
// 重置任务
|
||||
modalState.from[id].taskCode = '';
|
||||
modalState.from[id].logMsg = '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -203,8 +256,9 @@ function fnRecordStart(row?: Record<string, any>) {
|
||||
if (res.status === 'fulfilled') {
|
||||
const resV = res.value;
|
||||
if (resV.code === RESULT_CODE_SUCCESS) {
|
||||
fromArr[idx].out = resV.data.out;
|
||||
fromArr[idx].log = resV.data.log;
|
||||
if (!fromArr[idx].cmdStop) {
|
||||
fromArr[idx].taskCode = resV.data;
|
||||
}
|
||||
fromArr[idx].loading = true;
|
||||
message.success({
|
||||
content: t('views.traceManage.pcap.startOk', { title }),
|
||||
@@ -250,24 +304,51 @@ function fnRecordStop(row?: Record<string, any>) {
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.traceManage.pcap.stopTip', { title: row.neName }),
|
||||
onOk() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
const fromArr = neIDs.map(id => modalState.from[id]);
|
||||
const reqArr = fromArr.map(from => {
|
||||
const reqArr: any = [];
|
||||
for (const from of fromArr) {
|
||||
if (from.data.neType === 'UPF' && from.cmdStart.startsWith('pcap')) {
|
||||
return traceUPF(Object.assign({ cmd: from.cmdStop }, from.data));
|
||||
reqArr.push(
|
||||
traceUPF(Object.assign({ cmd: from.cmdStop }, from.data))
|
||||
);
|
||||
} else {
|
||||
const taskCode = from.taskCode;
|
||||
if (!taskCode) {
|
||||
message.warning({
|
||||
content: t('views.traceManage.pcap.stopNotRun', {
|
||||
title: from.title,
|
||||
}),
|
||||
duration: 3,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
reqArr.push(
|
||||
dumpStop(Object.assign({ taskCode: from.taskCode }, from.data))
|
||||
);
|
||||
}
|
||||
return dumpStop(Object.assign({ fileName: from.out }, from.data));
|
||||
});
|
||||
}
|
||||
if (reqArr.length === 0) return;
|
||||
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
Promise.allSettled(reqArr)
|
||||
.then(resArr => {
|
||||
resArr.forEach((res, idx) => {
|
||||
const title = fromArr[idx].title;
|
||||
|
||||
if (res.status === 'fulfilled') {
|
||||
const resV = res.value;
|
||||
fromArr[idx].loading = false;
|
||||
fromArr[idx].logMsg = '';
|
||||
if (fromArr[idx].cmdStop) {
|
||||
fromArr[idx].taskCode = '';
|
||||
}
|
||||
|
||||
if (resV.code === RESULT_CODE_SUCCESS) {
|
||||
fromArr[idx].out = resV.data.out;
|
||||
fromArr[idx].log = resV.data.log;
|
||||
fromArr[idx].loading = false;
|
||||
if (fromArr[idx].cmdStop) {
|
||||
fromArr[idx].taskCode = resV.data;
|
||||
} else {
|
||||
fromArr[idx].logMsg = resV.msg;
|
||||
}
|
||||
message.success({
|
||||
content: t('views.traceManage.pcap.stopOk', { title }),
|
||||
duration: 3,
|
||||
@@ -314,11 +395,11 @@ function fnDownPCAP(row?: Record<string, any>) {
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.traceManage.pcap.downTip', { title: row.neName }),
|
||||
onOk() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
const fromArr = neIDs.map(id => modalState.from[id]);
|
||||
const reqArr = [];
|
||||
const reqArr: any = [];
|
||||
for (const from of fromArr) {
|
||||
if (!from.out) {
|
||||
const taskCode = from.taskCode;
|
||||
if (!taskCode) {
|
||||
message.warning({
|
||||
content: t('views.traceManage.pcap.stopNotRun', {
|
||||
title: from.title,
|
||||
@@ -327,24 +408,32 @@ function fnDownPCAP(row?: Record<string, any>) {
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
reqArr.push(
|
||||
getNeFile(
|
||||
Object.assign(
|
||||
{
|
||||
path: '/tmp',
|
||||
fileName: `${from.out}.pcap`,
|
||||
},
|
||||
from.data
|
||||
if (from.data.neType === 'UPF' && taskCode.startsWith('/tmp')) {
|
||||
const fileName = taskCode.substring(taskCode.lastIndexOf('/') + 1);
|
||||
reqArr.push(
|
||||
getNeFile(
|
||||
Object.assign(
|
||||
{ path: '/tmp', fileName, delTemp: true },
|
||||
from.data
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
);
|
||||
} else {
|
||||
reqArr.push(
|
||||
dumpDownload(
|
||||
Object.assign({ taskCode: taskCode, delTemp: true }, from.data)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (reqArr.length === 0) return;
|
||||
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
Promise.allSettled(reqArr)
|
||||
.then(resArr => {
|
||||
resArr.forEach((res, idx) => {
|
||||
const title = fromArr[idx].title;
|
||||
const taskCode = fromArr[idx].taskCode;
|
||||
|
||||
if (res.status === 'fulfilled') {
|
||||
const resV = res.value;
|
||||
@@ -354,9 +443,10 @@ function fnDownPCAP(row?: Record<string, any>) {
|
||||
duration: 3,
|
||||
});
|
||||
// 文件名
|
||||
const fileName = `${fromArr[idx].out}.pcap`;
|
||||
if (fileName.length > 6) {
|
||||
saveAs(resV.data, fileName);
|
||||
if (taskCode.startsWith('/tmp')) {
|
||||
saveAs(resV.data, `${title}_${Date.now()}.pcap`);
|
||||
} else {
|
||||
saveAs(resV.data, `${title}_${Date.now()}.zip`);
|
||||
}
|
||||
} else {
|
||||
message.warning({
|
||||
@@ -379,15 +469,33 @@ function fnDownPCAP(row?: Record<string, any>) {
|
||||
});
|
||||
}
|
||||
|
||||
/**批量操作 */
|
||||
function fnBatchOper(key: string) {
|
||||
switch (key) {
|
||||
case 'start':
|
||||
fnRecordStart();
|
||||
break;
|
||||
case 'stop':
|
||||
fnRecordStop();
|
||||
break;
|
||||
case 'down':
|
||||
fnDownPCAP();
|
||||
break;
|
||||
default:
|
||||
console.warn('undefined batch oper', key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 查看
|
||||
* @param dictId 字典编号id
|
||||
* @param dictId 编号id
|
||||
*/
|
||||
function fnModalVisibleByVive(id: string | number) {
|
||||
const from = modalState.from[id];
|
||||
if (!from) return;
|
||||
modalState.visibleByView = true;
|
||||
modalState.logMsg = from.log;
|
||||
modalState.logMsg = from.logMsg;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -399,6 +507,22 @@ function fnModalCancel() {
|
||||
modalState.logMsg = '';
|
||||
}
|
||||
|
||||
/**跳转文件数据页面 */
|
||||
function fnFileView(row?: Record<string, any>) {
|
||||
let query = undefined;
|
||||
if (row) {
|
||||
const from = modalState.from[row.id];
|
||||
query = {
|
||||
neId: from.data.neId,
|
||||
neType: from.data.neType,
|
||||
};
|
||||
}
|
||||
router.push({
|
||||
path: `${route.path}${MENU_PATH_INLINE}/file`,
|
||||
query: query,
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取网元列表
|
||||
fnGetList();
|
||||
@@ -411,30 +535,33 @@ onMounted(() => {
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
@click.prevent="fnRecordStart()"
|
||||
>
|
||||
<template #icon><PlayCircleOutlined /> </template>
|
||||
{{ t('views.traceManage.pcap.textStartBatch') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
danger
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
@click.prevent="fnRecordStop()"
|
||||
>
|
||||
<template #icon><CloseSquareOutlined /> </template>
|
||||
{{ t('views.traceManage.pcap.textStopBatch') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
@click.prevent="fnDownPCAP()"
|
||||
>
|
||||
<template #icon><DownloadOutlined /></template>
|
||||
{{ t('views.traceManage.pcap.textDownBatch') }}
|
||||
<a-button @click="fnFileView()">
|
||||
<FileSearchOutlined />
|
||||
{{ t('views.traceManage.pcap.fileView') }}
|
||||
</a-button>
|
||||
|
||||
<a-dropdown trigger="click">
|
||||
<a-button :disabled="tableState.selectedRowKeys.length <= 0">
|
||||
{{ t('views.traceManage.pcap.batchOper') }}
|
||||
<DownOutlined />
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu @click="({ key }:any) => fnBatchOper(key)">
|
||||
<a-menu-item key="start">
|
||||
<PlayCircleOutlined />
|
||||
{{ t('views.traceManage.pcap.batchStartText') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="stop">
|
||||
<StopOutlined />
|
||||
{{ t('views.traceManage.pcap.batchStopText') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="down">
|
||||
<DownloadOutlined />
|
||||
{{ t('views.traceManage.pcap.batchDownText') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
@@ -459,7 +586,7 @@ onMounted(() => {
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:pagination="false"
|
||||
:scroll="{ x: tableColumns.length * 120 }"
|
||||
:scroll="{ x: tableColumns.length * 170 }"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
selectedRowKeys: tableState.selectedRowKeys,
|
||||
@@ -484,57 +611,84 @@ onMounted(() => {
|
||||
</template>
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-space :size="8" align="start" direction="horizontal">
|
||||
<a-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="modalState.from[record.id].loading"
|
||||
@click.prevent="fnRecordStart(record)"
|
||||
>
|
||||
<template #icon><PlayCircleOutlined /> </template>
|
||||
{{ t('views.traceManage.pcap.textStart') }}
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
size="small"
|
||||
@click.prevent="fnRecordStop(record)"
|
||||
>
|
||||
<template #icon><CloseSquareOutlined /> </template>
|
||||
{{ t('views.traceManage.pcap.textStop') }}
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
size="small"
|
||||
@click.prevent="fnModalVisibleByVive(record.id)"
|
||||
v-if="modalState.from[record.id].log"
|
||||
>
|
||||
<template #icon><FileTextOutlined /> </template>
|
||||
{{ t('views.traceManage.pcap.textLog') }}
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
size="small"
|
||||
@click.prevent="fnDownPCAP(record)"
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>
|
||||
<div>{{ t('views.traceManage.pcap.textStart') }}</div>
|
||||
</template>
|
||||
<a-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:disabled="modalState.from[record.id].loading"
|
||||
@click.prevent="fnRecordStart(record)"
|
||||
>
|
||||
<template #icon><PlayCircleOutlined /> </template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
placement="topRight"
|
||||
v-if="
|
||||
!modalState.from[record.id].loading &&
|
||||
modalState.from[record.id].out
|
||||
modalState.from[record.id].loading ||
|
||||
modalState.from[record.id].cmdStop
|
||||
"
|
||||
>
|
||||
<template #icon><DownloadOutlined /></template>
|
||||
{{ t('views.traceManage.pcap.textDown') }}
|
||||
</a-button>
|
||||
<template #title>
|
||||
<div>{{ t('views.traceManage.pcap.textStop') }}</div>
|
||||
</template>
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
size="small"
|
||||
@click.prevent="fnRecordStop(record)"
|
||||
>
|
||||
<template #icon><StopOutlined /> </template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip
|
||||
placement="topRight"
|
||||
v-if="
|
||||
!modalState.from[record.id].loading &&
|
||||
!!modalState.from[record.id].logMsg
|
||||
"
|
||||
>
|
||||
<template #title>
|
||||
<div>{{ t('views.traceManage.pcap.textLog') }}</div>
|
||||
</template>
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
size="small"
|
||||
@click.prevent="fnModalVisibleByVive(record.id)"
|
||||
>
|
||||
<template #icon><FileTextOutlined /> </template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
placement="topRight"
|
||||
v-if="
|
||||
!modalState.from[record.id].loading &&
|
||||
!!modalState.from[record.id].taskCode
|
||||
"
|
||||
>
|
||||
<template #title>
|
||||
<div>{{ t('views.traceManage.pcap.textDown') }}</div>
|
||||
</template>
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
size="small"
|
||||
@click.prevent="fnDownPCAP(record)"
|
||||
>
|
||||
<template #icon><DownloadOutlined /> </template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 详情框 -->
|
||||
<!-- 日志信息框 -->
|
||||
<ProModal
|
||||
:drag="true"
|
||||
:width="800"
|
||||
@@ -542,12 +696,13 @@ onMounted(() => {
|
||||
:footer="false"
|
||||
:maskClosable="false"
|
||||
:keyboard="false"
|
||||
:body-style="{ padding: '12px' }"
|
||||
:title="t('views.traceManage.pcap.textLogMsg')"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="modalState.logMsg"
|
||||
:auto-size="{ minRows: 2, maxRows: 24 }"
|
||||
:auto-size="{ minRows: 2, maxRows: 18 }"
|
||||
:disabled="true"
|
||||
style="color: rgba(0, 0, 0, 0.85)"
|
||||
/>
|
||||
|
||||
801
src/views/traceManage/task-hlr/index.vue
Normal file
801
src/views/traceManage/task-hlr/index.vue
Normal file
@@ -0,0 +1,801 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw, ref } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { Form, message, Modal } from 'ant-design-vue/lib';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import {
|
||||
delTaskHLR,
|
||||
listTaskHLR,
|
||||
startTaskHLR,
|
||||
stopTaskHLR,
|
||||
fileTaskHLR,
|
||||
filePullTaskHLR,
|
||||
} from '@/api/trace/taskHLR';
|
||||
import { getNeFile } from '@/api/tool/neFile';
|
||||
import saveAs from 'file-saver';
|
||||
const { t } = useI18n();
|
||||
|
||||
/**开始结束时间 */
|
||||
let queryRangePicker = ref<[string, string]>(['', '']);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
imsi: '',
|
||||
msisdn: '',
|
||||
/**记录时间 */
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
/**排序字段 */
|
||||
sortField: '',
|
||||
/**排序方式 */
|
||||
sortOrder: 'asc',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
imsi: '',
|
||||
msisdn: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
sortField: '',
|
||||
sortOrder: 'asc',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
queryRangePicker.value = ['', ''];
|
||||
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 = reactive([
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'id',
|
||||
align: 'right',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.task.imsi'),
|
||||
dataIndex: 'imsi',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.task.msisdn'),
|
||||
dataIndex: 'msisdn',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
// {
|
||||
// title: t('views.traceManage.task.status'),
|
||||
// dataIndex: 'status',
|
||||
// align: 'left',
|
||||
// width: 100,
|
||||
// },
|
||||
{
|
||||
title: t('views.traceManage.task.startTime'),
|
||||
dataIndex: 'startTime',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value);
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.task.endTime'),
|
||||
dataIndex: 'endTime',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value);
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
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;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* 信息删除
|
||||
* @param row 记录编号ID
|
||||
*/
|
||||
function fnRecordDelete(row: Record<string, any>) {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.traceManage.task.delTaskTip', { id: row.id }),
|
||||
onOk() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
delTaskHLR(row.id)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('views.traceManage.task.delTask', { id: row.id }),
|
||||
duration: 3,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**查询信息列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
if (!queryRangePicker.value) {
|
||||
queryRangePicker.value = ['', ''];
|
||||
}
|
||||
queryParams.startTime = queryRangePicker.value[0];
|
||||
queryParams.endTime = queryRangePicker.value[1];
|
||||
listTaskHLR(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;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 信息停止
|
||||
* @param row 记录编号ID
|
||||
*/
|
||||
function fnRecordStop(id: string) {
|
||||
if (!id || modalState.confirmLoading) return;
|
||||
if (id === '0') {
|
||||
id = tableState.selectedRowKeys.join(',');
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.traceManage.task.stopTaskTip', { id }),
|
||||
onOk() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
stopTaskHLR({ id })
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('views.traceManage.task.stopTask', { id }),
|
||||
duration: 3,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**详情框是否显示 */
|
||||
visibleByView: boolean;
|
||||
/**新增框或修改框是否显示 */
|
||||
visibleByEdit: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**任务开始结束时间 */
|
||||
timeRangePicker: [string, string];
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
/**文件列表数据 */
|
||||
fileList: any[];
|
||||
/**错误信息 */
|
||||
fileErrMsg: string;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
visibleByView: false,
|
||||
visibleByEdit: false,
|
||||
title: '',
|
||||
timeRangePicker: ['', ''],
|
||||
from: {
|
||||
id: undefined,
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
remark: '',
|
||||
// 跟踪类型用户
|
||||
imsi: '',
|
||||
msisdn: '',
|
||||
},
|
||||
confirmLoading: false,
|
||||
fileList: [],
|
||||
fileErrMsg: '',
|
||||
});
|
||||
|
||||
/**对话框内表单属性和校验规则 */
|
||||
const modalStateFrom = Form.useForm(
|
||||
modalState.from,
|
||||
reactive({
|
||||
endTime: [
|
||||
{
|
||||
required: false,
|
||||
message: t('views.traceManage.task.rangePickerPlease'),
|
||||
},
|
||||
],
|
||||
// 跟踪用户
|
||||
imsi: [
|
||||
{
|
||||
required: false,
|
||||
message: t('views.traceManage.task.imsiPlease'),
|
||||
},
|
||||
],
|
||||
msisdn: [
|
||||
{
|
||||
required: false,
|
||||
message: t('views.traceManage.task.msisdnPlease'),
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**开始结束时间选择对应修改 */
|
||||
function fnRangePickerChange(item: any, _: any) {
|
||||
modalState.from.startTime = +item[0];
|
||||
modalState.from.endTime = +item[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出显示
|
||||
*/
|
||||
function fnModalVisibleByVive(id: Record<string, any>) {
|
||||
if (modalState.confirmLoading) return;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
modalState.confirmLoading = true;
|
||||
fileTaskHLR({ id, dir: '/usr/local/log' }).then(res => {
|
||||
modalState.fileErrMsg = '';
|
||||
modalState.fileList = [];
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
for (const item of res.data) {
|
||||
if (item.err != '') {
|
||||
modalState.fileErrMsg += `${item.neName}: ${item.err} \n`;
|
||||
continue;
|
||||
}
|
||||
modalState.fileList.push(item);
|
||||
}
|
||||
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
modalState.title = t('views.traceManage.task.viewTask');
|
||||
modalState.visibleByView = true;
|
||||
} else {
|
||||
message.error(t('views.traceManage.task.errorTaskInfo'), 3);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param id 不传为新增
|
||||
*/
|
||||
function fnModalVisibleByEdit(id?: string) {
|
||||
if (!id) {
|
||||
fnModalCancel();
|
||||
modalState.title = t('views.traceManage.task.addTask');
|
||||
modalState.visibleByEdit = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalOk() {
|
||||
const from = toRaw(modalState.from);
|
||||
if (from.imsi === '' && from.msisdn === '') {
|
||||
message.warning({
|
||||
content: t('views.traceManage.task.imsiORmsisdn'),
|
||||
duration: 3,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
modalStateFrom
|
||||
.validate()
|
||||
.then(e => {
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
|
||||
startTaskHLR(from)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: modalState.title }),
|
||||
duration: 3,
|
||||
});
|
||||
fnModalCancel();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
modalState.confirmLoading = false;
|
||||
fnGetList(1);
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.visibleByView = false;
|
||||
modalState.visibleByEdit = false;
|
||||
modalState.confirmLoading = false;
|
||||
modalStateFrom.resetFields();
|
||||
modalState.timeRangePicker = ['', ''];
|
||||
}
|
||||
|
||||
/**下载触发等待 */
|
||||
let downLoading = ref<boolean>(false);
|
||||
|
||||
/**信息文件下载 */
|
||||
function fnDownloadFile(row: Record<string, any>) {
|
||||
if (downLoading.value) return;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.logManage.neFile.downTip', { fileName: row.fileName }),
|
||||
onOk() {
|
||||
downLoading.value = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
const path = row.filePath.substring(0, row.filePath.lastIndexOf('/'));
|
||||
filePullTaskHLR({
|
||||
neType: row.neType,
|
||||
neId: row.neId,
|
||||
path: path,
|
||||
fileName: row.fileName,
|
||||
delTemp: true,
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', {
|
||||
msg: t('common.downloadText'),
|
||||
}),
|
||||
duration: 2,
|
||||
});
|
||||
saveAs(res.data, `${row.fileName}`);
|
||||
} else {
|
||||
message.error({
|
||||
content: t('views.logManage.neFile.downTipErr'),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
downLoading.value = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
</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="IMSI" name="imsi ">
|
||||
<a-input
|
||||
v-model:value="queryParams.imsi"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="MSISDN" name="msisdn ">
|
||||
<a-input
|
||||
v-model:value="queryParams.msisdn"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.time')"
|
||||
name="queryRangePicker"
|
||||
>
|
||||
<a-range-picker
|
||||
v-model:value="queryRangePicker"
|
||||
allow-clear
|
||||
bordered
|
||||
:show-time="{ format: 'HH:mm:ss' }"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="x"
|
||||
:placeholder="[
|
||||
t('views.traceManage.task.startTime'),
|
||||
t('views.traceManage.task.endTime'),
|
||||
]"
|
||||
style="width: 100%"
|
||||
></a-range-picker>
|
||||
</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><PlusOutlined /></template>
|
||||
{{ t('common.addText') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
:loading="modalState.confirmLoading"
|
||||
@click.prevent="fnRecordStop('0')"
|
||||
>
|
||||
<template #icon><StopOutlined /></template>
|
||||
{{ t('views.traceManage.task.textStop') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</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: true }"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
columnWidth: '48px',
|
||||
selectedRowKeys: tableState.selectedRowKeys,
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip v-if="record.status === '1'">
|
||||
<template #title>
|
||||
{{ t('views.traceManage.task.textStop') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="link"
|
||||
danger
|
||||
@click.prevent="fnRecordStop(record.id)"
|
||||
>
|
||||
<template #icon><StopOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.viewText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByVive(record.id)"
|
||||
>
|
||||
<template #icon><ProfileOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.deleteText') }}</template>
|
||||
<a-button type="link" @click.prevent="fnRecordDelete(record)">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 详情框 -->
|
||||
<ProModal
|
||||
:drag="true"
|
||||
:visible="modalState.visibleByView"
|
||||
:title="modalState.title"
|
||||
@cancel="fnModalCancel"
|
||||
:footer="null"
|
||||
>
|
||||
<a-form
|
||||
name="fileList"
|
||||
layout="horizontal"
|
||||
autocomplete="off"
|
||||
:label-col="{ span: 5 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.traceFile')"
|
||||
name="fileTree"
|
||||
v-show="modalState.fileList.length > 0"
|
||||
>
|
||||
<a-tree
|
||||
:height="250"
|
||||
:tree-data="modalState.fileList"
|
||||
:field-names="{ title: 'fileName', key: 'filePath' }"
|
||||
>
|
||||
<template #title="item">
|
||||
<span>{{ item.fileName }}</span>
|
||||
<span
|
||||
class="fileTree-download"
|
||||
@click.prevent="fnDownloadFile(item)"
|
||||
>
|
||||
{{ t('common.downloadText') }}
|
||||
</span>
|
||||
</template>
|
||||
</a-tree>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.errMsg')"
|
||||
name="fileErrMsg"
|
||||
v-show="modalState.fileErrMsg.length > 0"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="modalState.fileErrMsg"
|
||||
:auto-size="{ minRows: 2, maxRows: 6 }"
|
||||
:disabled="true"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ProModal>
|
||||
|
||||
<!-- 新增框或修改框 -->
|
||||
<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"
|
||||
:label-col="{ span: 4 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<!-- 用户跟踪 -->
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.imsi')"
|
||||
name="imsi"
|
||||
v-bind="modalStateFrom.validateInfos.imsi"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.imsi"
|
||||
allow-clear
|
||||
:placeholder="t('views.traceManage.task.imsiPlease')"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
<div>{{ t('views.traceManage.task.imsiTip') }}</div>
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.msisdn')"
|
||||
name="msisdn"
|
||||
v-bind="modalStateFrom.validateInfos.msisdn"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.msisdn"
|
||||
allow-clear
|
||||
:placeholder="t('views.traceManage.task.msisdnPlease')"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
<div>{{ t('views.traceManage.task.msisdnTip') }}</div>
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.rangePicker')"
|
||||
name="endTime"
|
||||
v-bind="modalStateFrom.validateInfos.endTime"
|
||||
>
|
||||
<a-range-picker
|
||||
v-model:value="modalState.timeRangePicker"
|
||||
allow-clear
|
||||
bordered
|
||||
:show-time="{ format: 'HH:mm:ss' }"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="x"
|
||||
:placeholder="[
|
||||
t('views.traceManage.task.startTime'),
|
||||
t('views.traceManage.task.endTime'),
|
||||
]"
|
||||
style="width: 100%"
|
||||
@change="fnRangePickerChange"
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('views.traceManage.task.remark')" name="remark">
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.remark"
|
||||
:auto-size="{ minRows: 2, maxRows: 6 }"
|
||||
:maxlength="250"
|
||||
:show-count="true"
|
||||
:placeholder="t('views.traceManage.task.remarkPlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ProModal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.fileTree-download {
|
||||
margin-left: 12px;
|
||||
color: #eb2f96;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw } from 'vue';
|
||||
import { reactive, onMounted, toRaw, ref } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { Form, message, Modal } from 'ant-design-vue/lib';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
@@ -8,7 +8,6 @@ import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import {
|
||||
addTraceTask,
|
||||
@@ -16,9 +15,11 @@ import {
|
||||
getTraceTask,
|
||||
listTraceTask,
|
||||
updateTraceTask,
|
||||
} from '@/api/traceManage/task';
|
||||
} from '@/api/trace/task';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import { regExpIPv4, regExpPort } from '@/utils/regular-utils';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { parseObjHumpToLine } from '@/utils/parse-utils';
|
||||
const neInfoStore = useNeInfoStore();
|
||||
const { getDict } = useDictStore();
|
||||
const { t } = useI18n();
|
||||
@@ -31,15 +32,18 @@ let dict: {
|
||||
traceType: [],
|
||||
});
|
||||
|
||||
/**网元类型_多neId */
|
||||
let neCascaderOptions = ref<Record<string, any>[]>([]);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**网元类型 */
|
||||
neType: '',
|
||||
/**记录时间 */
|
||||
beginTime: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
/**排序字段 */
|
||||
sortField: 'startTime',
|
||||
sortField: 'start_time',
|
||||
/**排序方式 */
|
||||
sortOrder: 'asc',
|
||||
/**当前页数 */
|
||||
@@ -52,9 +56,9 @@ let queryParams = reactive({
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
neType: '',
|
||||
beginTime: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
sortField: 'startTime',
|
||||
sortField: 'start_time',
|
||||
sortOrder: 'asc',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
@@ -114,11 +118,6 @@ let tableColumns: ColumnsType = [
|
||||
key: 'traceType',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.task.creater'),
|
||||
dataIndex: 'accountId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.task.startTime'),
|
||||
dataIndex: 'startTime',
|
||||
@@ -178,11 +177,16 @@ function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
}
|
||||
|
||||
/**表格分页、排序、筛选变化时触发操作, 排序方式,取值为 ascend descend */
|
||||
function fnTableChange(pagination: any, filters: any, sorter: any, extra: any) {
|
||||
const { field, order } = sorter;
|
||||
if (order) {
|
||||
queryParams.sortField = field;
|
||||
queryParams.sortField = parseObjHumpToLine(field);
|
||||
queryParams.sortOrder = order.replace('end', '');
|
||||
} else {
|
||||
queryParams.sortOrder = 'asc';
|
||||
@@ -191,32 +195,40 @@ function fnTableChange(pagination: any, filters: any, sorter: any, extra: any) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份信息删除
|
||||
* @param row 记录编号ID
|
||||
* 记录删除
|
||||
* @param id 编号
|
||||
*/
|
||||
function fnRecordDelete(id: string) {
|
||||
if (!id || modalState.confirmLoading) return;
|
||||
let msg = t('views.traceManage.task.delTaskTip', { id: id });
|
||||
if (id === '0') {
|
||||
const mun = `...${tableState.selectedRowKeys.length}`;
|
||||
msg = `${t('views.traceManage.task.delTaskTip', { id: mun })} `;
|
||||
id = tableState.selectedRowKeys.join(',');
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.traceManage.task.delTaskTip', { num: id }),
|
||||
content: msg,
|
||||
onOk() {
|
||||
const key = 'delTraceTask';
|
||||
message.loading({ content: t('common.loading'), key });
|
||||
delTraceTask(id).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('views.traceManage.task.delTask', { num: id }),
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key: key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
delTraceTask(id)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(t('common.operateOk'), 3);
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -251,8 +263,6 @@ function fnGetList(pageNum?: number) {
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**详情框是否显示 */
|
||||
visibleByView: boolean;
|
||||
/**新增框或修改框是否显示 */
|
||||
visibleByEdit: boolean;
|
||||
/**标题 */
|
||||
@@ -266,7 +276,25 @@ type ModalStateType = {
|
||||
/**任务开始结束时间 */
|
||||
timeRangePicker: [string, string];
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
from: {
|
||||
id?: string;
|
||||
neType: string;
|
||||
neId: string;
|
||||
/**1-Interface,2-Device,3-User */
|
||||
traceType: string;
|
||||
startTime?: number;
|
||||
endTime?: number;
|
||||
remark?: string;
|
||||
/**1接口跟踪 */
|
||||
interfaces?: string;
|
||||
/**2设备跟踪 */
|
||||
srcIp?: string;
|
||||
dstIp?: string;
|
||||
signalPort?: number;
|
||||
/**3用户跟踪 */
|
||||
imsi?: string;
|
||||
msisdn?: string;
|
||||
};
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
@@ -284,18 +312,19 @@ let modalState: ModalStateType = reactive({
|
||||
id: '',
|
||||
neType: '',
|
||||
neId: '',
|
||||
traceType: 'Device',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
comment: '',
|
||||
// 跟踪类型用户
|
||||
imsi: '',
|
||||
msisdn: '',
|
||||
// 跟踪类型接口
|
||||
traceType: '3',
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
remark: '',
|
||||
/**1接口跟踪 */
|
||||
interfaces: '',
|
||||
/**2设备跟踪 */
|
||||
srcIp: '',
|
||||
dstIp: '',
|
||||
interfaces: '',
|
||||
signalPort: '',
|
||||
signalPort: undefined,
|
||||
/**3用户跟踪 */
|
||||
imsi: '',
|
||||
msisdn: '',
|
||||
},
|
||||
confirmLoading: false,
|
||||
});
|
||||
@@ -322,7 +351,7 @@ const modalStateFrom = Form.useForm(
|
||||
message: t('views.traceManage.task.rangePickerPlease'),
|
||||
},
|
||||
],
|
||||
// 跟踪用户
|
||||
// 用户跟踪
|
||||
imsi: [
|
||||
{
|
||||
required: true,
|
||||
@@ -331,11 +360,18 @@ const modalStateFrom = Form.useForm(
|
||||
],
|
||||
msisdn: [
|
||||
{
|
||||
required: true,
|
||||
required: false,
|
||||
message: t('views.traceManage.task.msisdnPlease'),
|
||||
},
|
||||
],
|
||||
// 跟踪接口
|
||||
// 接口跟踪
|
||||
interfaces: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.traceManage.task.interfacesPlease'),
|
||||
},
|
||||
],
|
||||
// 数据跟踪
|
||||
srcIp: [
|
||||
{
|
||||
required: true,
|
||||
@@ -350,15 +386,9 @@ const modalStateFrom = Form.useForm(
|
||||
message: t('views.traceManage.task.dstIpPlease'),
|
||||
},
|
||||
],
|
||||
interfaces: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.traceManage.task.interfacesPlease'),
|
||||
},
|
||||
],
|
||||
signalPort: [
|
||||
{
|
||||
required: true,
|
||||
required: false,
|
||||
pattern: regExpPort,
|
||||
message: t('views.traceManage.task.signalPortPlease'),
|
||||
},
|
||||
@@ -370,18 +400,29 @@ const modalStateFrom = Form.useForm(
|
||||
function fnNeChange(_: any, item: any) {
|
||||
modalState.from.neType = item[1].neType;
|
||||
modalState.from.neId = item[1].neId;
|
||||
|
||||
// 网元信令接口可选列表
|
||||
modalState.from.interfaces = '';
|
||||
modalState.neTypeInterfaceSelect = [];
|
||||
if (modalState.from.traceType !== 'Interface') return;
|
||||
// 网元信令接口
|
||||
fnSelectInterfaceInit(item[1].neType);
|
||||
}
|
||||
|
||||
/**跟踪类型选择对应修改 */
|
||||
function fnTraceTypeChange(v: any, _: any) {
|
||||
// 网元信令接口可选列表
|
||||
if (v === '1' && modalState.from.neType) {
|
||||
modalState.from.interfaces = '';
|
||||
modalState.neTypeInterfaceSelect = [];
|
||||
fnSelectInterfaceInit(modalState.from.neType);
|
||||
}
|
||||
}
|
||||
|
||||
/**开始结束时间选择对应修改 */
|
||||
function fnRangePickerChange(_: any, item: any) {
|
||||
modalState.from.startTime = item[0];
|
||||
modalState.from.endTime = item[1];
|
||||
function fnRangePickerChange(item: any, _: any) {
|
||||
modalState.from.startTime = +item[0];
|
||||
modalState.from.endTime = +item[1];
|
||||
}
|
||||
function fnRangePickerDisabledDate(current: Dayjs) {
|
||||
return current && current < dayjs().subtract(1, 'day').endOf('day');
|
||||
}
|
||||
|
||||
/**信令接口选择对应修改 */
|
||||
@@ -402,39 +443,6 @@ function fnSelectInterfaceInit(neType: string) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param noticeId 网元id, 不传为新增
|
||||
*/
|
||||
function fnModalVisibleByVive(id: string) {
|
||||
if (modalState.confirmLoading) return;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
modalState.confirmLoading = true;
|
||||
getTraceTask(id).then(res => {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
modalState.neType = [res.data.neType, res.data.neId];
|
||||
modalState.timeRangePicker = [res.data.startTime, res.data.endTime];
|
||||
modalState.from = Object.assign(modalState.from, res.data);
|
||||
// 接口
|
||||
if (res.data.traceType === 'Interface') {
|
||||
if (
|
||||
res.data.interfaces.length > 4 &&
|
||||
res.data.interfaces.includes('[')
|
||||
) {
|
||||
modalState.neTypeInterfaceSelect = JSON.parse(res.data.interfaces);
|
||||
}
|
||||
fnSelectInterfaceInit(res.data.neType);
|
||||
}
|
||||
modalState.title = t('views.traceManage.task.viewTask');
|
||||
modalState.visibleByView = true;
|
||||
} else {
|
||||
message.error(t('views.traceManage.task.errorTaskInfo'), 3);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param noticeId 网元id, 不传为新增
|
||||
@@ -481,13 +489,16 @@ function fnModalVisibleByEdit(id?: string) {
|
||||
function fnModalOk() {
|
||||
const from = toRaw(modalState.from);
|
||||
let valids = ['traceType', 'neId', 'endTime'];
|
||||
if (from.traceType === 'UE') {
|
||||
if (from.traceType === '1') {
|
||||
valids = valids.concat(['interfaces']);
|
||||
}
|
||||
if (from.traceType === '2') {
|
||||
valids = valids.concat(['srcIp', 'dstIp', 'signalPort']);
|
||||
}
|
||||
if (from.traceType === '3') {
|
||||
valids = valids.concat(['imsi', 'msisdn']);
|
||||
}
|
||||
if (from.traceType === 'Interface') {
|
||||
valids = valids.concat(['srcIp', 'dstIp', 'interfaces', 'signalPort']);
|
||||
}
|
||||
from.accountId = useUserStore().userName;
|
||||
|
||||
modalStateFrom
|
||||
.validate(valids)
|
||||
.then(e => {
|
||||
@@ -525,7 +536,6 @@ function fnModalOk() {
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.visibleByView = false;
|
||||
modalState.visibleByEdit = false;
|
||||
modalState.confirmLoading = false;
|
||||
modalStateFrom.resetFields();
|
||||
@@ -542,15 +552,40 @@ onMounted(() => {
|
||||
dict.traceType = resArr[0].value;
|
||||
}
|
||||
});
|
||||
Promise.allSettled([
|
||||
// 获取网元网元列表
|
||||
neInfoStore.fnNelist(),
|
||||
// 获取跟踪接口列表
|
||||
neInfoStore.fnNeTraceInterface(),
|
||||
]).finally(() => {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
|
||||
// 获取网元网元列表
|
||||
useNeInfoStore()
|
||||
.fnNelist()
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
if (res.data.length > 0) {
|
||||
// 过滤不可用的网元
|
||||
neCascaderOptions.value = neInfoStore.getNeSelectOtions.filter(
|
||||
(item: any) => {
|
||||
return ['UDM'].includes(item.value);
|
||||
}
|
||||
);
|
||||
if (neCascaderOptions.value.length === 0) {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
// 获取跟踪接口列表
|
||||
neInfoStore.fnNeTraceInterface();
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -571,7 +606,7 @@ onMounted(() => {
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="queryParams.neType"
|
||||
:options="neInfoStore.getNeSelectOtions"
|
||||
:options="neCascaderOptions"
|
||||
allow-clear
|
||||
:placeholder="t('views.traceManage.task.neTypePlease')"
|
||||
/>
|
||||
@@ -580,13 +615,13 @@ onMounted(() => {
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.startTime')"
|
||||
name="beginTime"
|
||||
name="startTime"
|
||||
>
|
||||
<a-date-picker
|
||||
show-time
|
||||
v-model:value="queryParams.beginTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
v-model:value="queryParams.startTime"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="x"
|
||||
:placeholder="t('views.traceManage.task.startTime')"
|
||||
style="width: 100%"
|
||||
/>
|
||||
@@ -600,8 +635,8 @@ onMounted(() => {
|
||||
<a-date-picker
|
||||
show-time
|
||||
v-model:value="queryParams.endTime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="x"
|
||||
:placeholder="t('views.traceManage.task.endTime')"
|
||||
style="width: 100%"
|
||||
/>
|
||||
@@ -628,10 +663,23 @@ onMounted(() => {
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-button type="primary" @click.prevent="fnModalVisibleByEdit()">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
{{ t('common.addText') }}
|
||||
</a-button>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button type="primary" @click.prevent="fnModalVisibleByEdit()">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
{{ t('common.addText') }}
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
:loading="modalState.confirmLoading"
|
||||
@click.prevent="fnRecordDelete('0')"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
{{ t('common.deleteText') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
@@ -690,6 +738,12 @@ onMounted(() => {
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: true }"
|
||||
@change="fnTableChange"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
columnWidth: '48px',
|
||||
selectedRowKeys: tableState.selectedRowKeys,
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'traceType'">
|
||||
@@ -697,15 +751,6 @@ onMounted(() => {
|
||||
</template>
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.viewText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByVive(record.id)"
|
||||
>
|
||||
<template #icon><ProfileOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.editText') }}</template>
|
||||
<a-button
|
||||
@@ -719,6 +764,7 @@ onMounted(() => {
|
||||
<template #title>{{ t('common.deleteText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
danger
|
||||
@click.prevent="fnRecordDelete(record.id)"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
@@ -730,117 +776,6 @@ onMounted(() => {
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 详情框 -->
|
||||
<ProModal
|
||||
:drag="true"
|
||||
:width="800"
|
||||
:visible="modalState.visibleByView"
|
||||
:title="modalState.title"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.trackType')"
|
||||
name="traceType"
|
||||
>
|
||||
<DictTag
|
||||
:options="dict.traceType"
|
||||
:value="modalState.from.traceType"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.neType')"
|
||||
name="neType"
|
||||
>
|
||||
<a-cascader
|
||||
:value="modalState.neType"
|
||||
:options="neInfoStore.getNeCascaderOptions"
|
||||
disabled
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 用户跟踪 -->
|
||||
<template v-if="modalState.from.traceType === 'UE'">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.msisdn')"
|
||||
name="msisdn"
|
||||
>
|
||||
{{ modalState.from.msisdn }}
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('views.traceManage.task.imsi')" name="imsi">
|
||||
{{ modalState.from.imsi }}
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 接口跟踪 -->
|
||||
<template v-if="modalState.from.traceType === 'Interface'">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.srcIp')"
|
||||
name="srcIp"
|
||||
>
|
||||
{{ modalState.from.srcIp }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.dstIp')"
|
||||
name="dstIp"
|
||||
>
|
||||
{{ modalState.from.dstIp }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.interfaces')"
|
||||
name="endTime"
|
||||
>
|
||||
{{ modalState.neTypeInterfaceSelect }}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.signalPort')"
|
||||
name="endTime"
|
||||
>
|
||||
{{ modalState.from.signalPort }}
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.rangePicker')"
|
||||
name="endTime"
|
||||
>
|
||||
<a-range-picker
|
||||
disabled
|
||||
:value="modalState.timeRangePicker"
|
||||
allow-clear
|
||||
bordered
|
||||
:show-time="{ format: 'HH:mm:ss' }"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%"
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.comment')"
|
||||
name="comment"
|
||||
>
|
||||
{{ modalState.from.comment }}
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-button key="cancel" @click="fnModalCancel">{{
|
||||
t('common.close')
|
||||
}}</a-button>
|
||||
</template>
|
||||
</ProModal>
|
||||
|
||||
<!-- 新增框或修改框 -->
|
||||
<ProModal
|
||||
:drag="true"
|
||||
@@ -854,89 +789,124 @@ onMounted(() => {
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form name="modalStateFrom" layout="horizontal">
|
||||
<a-form
|
||||
name="modalStateFrom"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 4 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.trackType')"
|
||||
name="traceType"
|
||||
v-bind="modalStateFrom.validateInfos.traceType"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="modalState.from.traceType"
|
||||
:placeholder="t('views.traceManage.task.trackTypePlease')"
|
||||
:options="dict.traceType"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.neType')"
|
||||
:label-col="{ span: 8 }"
|
||||
name="neType"
|
||||
v-bind="modalStateFrom.validateInfos.neId"
|
||||
>
|
||||
<a-cascader
|
||||
v-model:value="modalState.neType"
|
||||
:options="neInfoStore.getNeCascaderOptions"
|
||||
:options="neCascaderOptions"
|
||||
@change="fnNeChange"
|
||||
:allow-clear="false"
|
||||
:placeholder="t('views.traceManage.task.neTypePlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.trackType')"
|
||||
:label-col="{ span: 8 }"
|
||||
name="traceType"
|
||||
v-bind="modalStateFrom.validateInfos.traceType"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="modalState.from.traceType"
|
||||
:options="dict.traceType"
|
||||
@change="fnTraceTypeChange"
|
||||
:allow-clear="false"
|
||||
:placeholder="t('views.traceManage.task.trackTypePlease')"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 用户跟踪 -->
|
||||
<template v-if="modalState.from.traceType === 'UE'">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.rangePicker')"
|
||||
name="endTime"
|
||||
v-bind="modalStateFrom.validateInfos.endTime"
|
||||
>
|
||||
<a-range-picker
|
||||
v-model:value="modalState.timeRangePicker"
|
||||
@change="fnRangePickerChange"
|
||||
allow-clear
|
||||
bordered
|
||||
:show-time="{ format: 'HH:mm:ss' }"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="x"
|
||||
style="width: 100%"
|
||||
:disabled-date="fnRangePickerDisabledDate"
|
||||
:placeholder="[
|
||||
t('views.traceManage.task.startTime'),
|
||||
t('views.traceManage.task.endTime'),
|
||||
]"
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('views.traceManage.task.remark')" name="remark">
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.remark"
|
||||
:auto-size="{ minRows: 1, maxRows: 6 }"
|
||||
:maxlength="450"
|
||||
:show-count="true"
|
||||
:placeholder="t('views.traceManage.task.remarkPlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 接口跟踪 -->
|
||||
<template v-if="modalState.from.traceType === '1'">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.msisdn')"
|
||||
name="msisdn"
|
||||
v-bind="modalStateFrom.validateInfos.msisdn"
|
||||
:label="t('views.traceManage.task.interfaces')"
|
||||
name="interfaces"
|
||||
v-bind="modalStateFrom.validateInfos.interfaces"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.msisdn"
|
||||
allow-clear
|
||||
:placeholder="t('views.traceManage.task.msisdnPlease')"
|
||||
<a-select
|
||||
mode="multiple"
|
||||
:placeholder="t('views.traceManage.task.interfacesPlease')"
|
||||
v-model:value="modalState.neTypeInterfaceSelect"
|
||||
:options="modalState.neTypeInterface"
|
||||
@change="fnSelectInterface"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
<div>{{ t('views.traceManage.task.msisdnTip') }}</div>
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.imsi')"
|
||||
name="imsi"
|
||||
v-bind="modalStateFrom.validateInfos.imsi"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.imsi"
|
||||
allow-clear
|
||||
:placeholder="t('views.traceManage.task.imsiPlease')"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
<div>{{ t('views.traceManage.task.imsiTip') }}</div>
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 接口跟踪 -->
|
||||
<template v-if="modalState.from.traceType === 'Interface'">
|
||||
<!-- 设备跟踪 -->
|
||||
<template v-if="modalState.from.traceType === '2'">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.signalPort')"
|
||||
name="signalPort"
|
||||
v-bind="modalStateFrom.validateInfos.signalPort"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="modalState.from.signalPort"
|
||||
style="width: 100%"
|
||||
:placeholder="t('views.traceManage.task.signalPortPlease')"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
<div>{{ t('views.traceManage.task.signalPortTip') }}</div>
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.srcIp')"
|
||||
:label-col="{ span: 8 }"
|
||||
name="srcIp"
|
||||
v-bind="modalStateFrom.validateInfos.srcIp"
|
||||
>
|
||||
@@ -959,6 +929,7 @@ onMounted(() => {
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.dstIp')"
|
||||
:label-col="{ span: 8 }"
|
||||
name="dstIp"
|
||||
v-bind="modalStateFrom.validateInfos.dstIp"
|
||||
>
|
||||
@@ -979,34 +950,44 @@ onMounted(() => {
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<!-- 用户跟踪 -->
|
||||
<template v-if="modalState.from.traceType === '3'">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.interfaces')"
|
||||
name="interfaces"
|
||||
v-bind="modalStateFrom.validateInfos.interfaces"
|
||||
>
|
||||
<a-select
|
||||
mode="multiple"
|
||||
:placeholder="t('views.traceManage.task.interfacesPlease')"
|
||||
v-model:value="modalState.neTypeInterfaceSelect"
|
||||
:options="modalState.neTypeInterface"
|
||||
@change="fnSelectInterface"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.signalPort')"
|
||||
name="signalPort"
|
||||
v-bind="modalStateFrom.validateInfos.signalPort"
|
||||
:label="t('views.traceManage.task.imsi')"
|
||||
name="imsi"
|
||||
v-bind="modalStateFrom.validateInfos.imsi"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.signalPort"
|
||||
v-model:value="modalState.from.imsi"
|
||||
allow-clear
|
||||
:placeholder="t('views.traceManage.task.signalPortPlease')"
|
||||
:placeholder="t('views.traceManage.task.imsiPlease')"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
<div>{{ t('views.traceManage.task.signalPortTip') }}</div>
|
||||
<div>{{ t('views.traceManage.task.imsiTip') }}</div>
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.msisdn')"
|
||||
name="msisdn"
|
||||
v-bind="modalStateFrom.validateInfos.msisdn"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.msisdn"
|
||||
allow-clear
|
||||
:placeholder="t('views.traceManage.task.msisdnPlease')"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
<div>{{ t('views.traceManage.task.msisdnTip') }}</div>
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
@@ -1014,39 +995,6 @@ onMounted(() => {
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.rangePicker')"
|
||||
name="endTime"
|
||||
v-bind="modalStateFrom.validateInfos.endTime"
|
||||
>
|
||||
<a-range-picker
|
||||
v-model:value="modalState.timeRangePicker"
|
||||
@change="fnRangePickerChange"
|
||||
allow-clear
|
||||
bordered
|
||||
:show-time="{ format: 'HH:mm:ss' }"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
:placeholder="[
|
||||
t('views.traceManage.task.startTime'),
|
||||
t('views.traceManage.task.endTime'),
|
||||
]"
|
||||
style="width: 100%"
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.comment')"
|
||||
name="comment"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.comment"
|
||||
:auto-size="{ minRows: 2, maxRows: 6 }"
|
||||
:maxlength="250"
|
||||
:show-count="true"
|
||||
:placeholder="t('views.traceManage.task.commentPlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ProModal>
|
||||
</PageContainer>
|
||||
|
||||
141
src/views/traceManage/tshark/components/DissectionDump.vue
Normal file
141
src/views/traceManage/tshark/components/DissectionDump.vue
Normal file
@@ -0,0 +1,141 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import DissectionDumpHigh from './DissectionDumpHigh.vue';
|
||||
|
||||
const props = defineProps({
|
||||
base64: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
select: {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
selected: {
|
||||
type: Object,
|
||||
default: { id: '', idx: 0, start: 0, length: 0 },
|
||||
},
|
||||
});
|
||||
|
||||
const addrLines = ref<string[]>([]);
|
||||
const hexLines = ref<string[]>([]);
|
||||
const asciiLines = ref<string[]>([]);
|
||||
|
||||
const asciiHighlight = ref([0, 0]);
|
||||
const hexHighlight = ref([0, 0]);
|
||||
|
||||
watch(
|
||||
() => props.selected,
|
||||
newSelected => {
|
||||
const { start, length: size } = newSelected;
|
||||
|
||||
const hexSize = size * 2 + size - 1;
|
||||
const hexPos = start * 2 + start;
|
||||
const asciiPos = start + Math.floor(start / 16);
|
||||
const asciiSize = start + size + Math.floor((start + size) / 16) - asciiPos;
|
||||
|
||||
asciiHighlight.value = [asciiPos, size > 0 ? asciiSize : 0];
|
||||
hexHighlight.value = [hexPos, size > 0 ? hexSize : 0];
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.base64,
|
||||
base64Str => {
|
||||
// Decode base64 to a string
|
||||
const binaryString = atob(base64Str);
|
||||
// Convert binary string to Uint8Array
|
||||
const newBuffer = new Uint8Array(binaryString.length);
|
||||
for (let i = 0; i < binaryString.length; i++) {
|
||||
newBuffer[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
|
||||
let addrLinesTemp: string[] = [];
|
||||
let hexLinesTemp: string[] = [];
|
||||
let asciiLinesTemp: string[] = [];
|
||||
|
||||
for (let i = 0; i < newBuffer.length; i += 16) {
|
||||
let address = i.toString(16).padStart(8, '0');
|
||||
let block = newBuffer.slice(i, i + 16);
|
||||
let hexArray = [];
|
||||
let asciiArray = [];
|
||||
|
||||
for (let value of block) {
|
||||
hexArray.push(value.toString(16).padStart(2, '0'));
|
||||
asciiArray.push(
|
||||
value >= 0x20 && value < 0x7f ? String.fromCharCode(value) : '.'
|
||||
);
|
||||
}
|
||||
|
||||
let hexString =
|
||||
hexArray.length > 8
|
||||
? hexArray.slice(0, 8).join(' ') + ' ' + hexArray.slice(8).join(' ')
|
||||
: hexArray.join(' ');
|
||||
|
||||
let asciiString = asciiArray.join('');
|
||||
|
||||
addrLinesTemp.push(address);
|
||||
hexLinesTemp.push(hexString);
|
||||
asciiLinesTemp.push(asciiString);
|
||||
}
|
||||
|
||||
addrLines.value = addrLinesTemp;
|
||||
hexLines.value = hexLinesTemp;
|
||||
asciiLines.value = asciiLinesTemp;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const onHexClick = (offset: number) => {
|
||||
if (typeof props.select !== 'function') return;
|
||||
props.select(Math.floor(offset / 3));
|
||||
};
|
||||
|
||||
const onAsciiClick = (offset: number) => {
|
||||
if (typeof props.select !== 'function') return;
|
||||
props.select(offset - Math.floor(offset / 17));
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tbd">
|
||||
<div class="tbd-offset">
|
||||
{{ addrLines.join('\n') }}
|
||||
</div>
|
||||
<div class="tbd-box">
|
||||
<DissectionDumpHigh
|
||||
:text="hexLines.join('\n')"
|
||||
:start="hexHighlight[0]"
|
||||
:size="hexHighlight[1]"
|
||||
:onOffsetClicked="onHexClick"
|
||||
/>
|
||||
</div>
|
||||
<div class="tbd-box">
|
||||
<DissectionDumpHigh
|
||||
:text="asciiLines.join('\n')"
|
||||
:start="asciiHighlight[0]"
|
||||
:size="asciiHighlight[1]"
|
||||
:onOffsetClicked="onAsciiClick"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.tbd {
|
||||
display: flex;
|
||||
white-space: pre;
|
||||
word-break: break-all;
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
.tbd-offset {
|
||||
color: #6b7280;
|
||||
user-select: none;
|
||||
}
|
||||
.tbd-box {
|
||||
margin-left: 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,52 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
const props = defineProps({
|
||||
text: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
start: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
onOffsetClicked: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const before = computed(() => props.text.substring(0, props.start));
|
||||
const hl = computed(() =>
|
||||
props.text.substring(props.start, props.start + props.size)
|
||||
);
|
||||
const end = computed(() => props.text.substring(props.start + props.size));
|
||||
|
||||
const handleClick = (offset: number) => {
|
||||
const selection = window.getSelection();
|
||||
if (!selection) return;
|
||||
props.onOffsetClicked(selection.anchorOffset + offset);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<span @click="handleClick(0)">{{ before }}</span>
|
||||
<span @click="handleClick(before.length)" class="hl">
|
||||
{{ hl }}
|
||||
</span>
|
||||
<span @click="handleClick(before.length + hl.length)">
|
||||
{{ end }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.hl {
|
||||
color: #ffffff;
|
||||
background-color: #4b5563;
|
||||
}
|
||||
</style>
|
||||
60
src/views/traceManage/tshark/components/DissectionTree.vue
Normal file
60
src/views/traceManage/tshark/components/DissectionTree.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue';
|
||||
import DissectionTreeSub from './DissectionTreeSub.vue';
|
||||
|
||||
defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
tree: {
|
||||
type: Array as PropType<Record<string, any>[]>,
|
||||
required: true,
|
||||
},
|
||||
sub: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
select: {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
selected: {
|
||||
type: Object,
|
||||
default: { id: '', idx: 0, start: 0, length: 0 },
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul :class="{ tree: true, 'tree-issub': sub }">
|
||||
<li v-for="(n, i) in tree" :key="`${id}-${i}`" class="tree-li">
|
||||
<DissectionTreeSub
|
||||
:id="`${id}-${i}`"
|
||||
:node="n"
|
||||
:select="select"
|
||||
:selected="selected"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.tree {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0 solid #e5e7eb;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.tree-issub {
|
||||
padding-left: 0.5rem;
|
||||
border-left-width: 1px;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
.tree-li {
|
||||
display: list-item;
|
||||
text-align: -webkit-match-parent;
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
116
src/views/traceManage/tshark/components/DissectionTreeSub.vue
Normal file
116
src/views/traceManage/tshark/components/DissectionTreeSub.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import DissectionTree from './DissectionTree.vue';
|
||||
import {
|
||||
CaretDownOutlined,
|
||||
CaretRightOutlined,
|
||||
MinusOutlined,
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
const props = defineProps({
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
node: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
select: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
selected: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['update:selected']);
|
||||
|
||||
const open = ref(false);
|
||||
|
||||
watch(
|
||||
() => props.selected,
|
||||
() => {
|
||||
if (!open.value) {
|
||||
open.value = props.selected.id.startsWith(props.id + '-');
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const toggle = () => {
|
||||
if (open.value && props.selected.id.startsWith(props.id + '-')) {
|
||||
const NO_SELECTION = { id: '', idx: 0, start: 0, length: 0 };
|
||||
emit('update:selected', NO_SELECTION);
|
||||
if (typeof props.select === 'function') {
|
||||
props.select(NO_SELECTION);
|
||||
}
|
||||
}
|
||||
open.value = !open.value;
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
if (props.node.length > 0) {
|
||||
const select = {
|
||||
id: props.id,
|
||||
idx: props.node.data_source_idx,
|
||||
start: props.node.start,
|
||||
length: props.node.length,
|
||||
};
|
||||
emit('update:selected', select);
|
||||
if (typeof props.select === 'function') {
|
||||
props.select(select);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="{ 'tree-sub': true, 'tree-sub_hl': id === selected.id }">
|
||||
<component
|
||||
:is="
|
||||
node.tree && node.tree.length > 0
|
||||
? open
|
||||
? CaretDownOutlined
|
||||
: CaretRightOutlined
|
||||
: MinusOutlined
|
||||
"
|
||||
class="tree-sub_icon"
|
||||
@click="toggle"
|
||||
/>
|
||||
<span @click="handleClick" @dblclick="toggle" class="tree-sub_text">
|
||||
{{ node.label }}
|
||||
</span>
|
||||
</div>
|
||||
<DissectionTree
|
||||
v-if="node.tree && node.tree.length > 0 && open"
|
||||
:id="id"
|
||||
:tree="node.tree"
|
||||
:select="select"
|
||||
:selected="selected"
|
||||
sub
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.tree-sub {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tree-sub_hl {
|
||||
color: #ffffff;
|
||||
background-color: #4b5563;
|
||||
}
|
||||
.tree-sub_icon {
|
||||
color: #6b7280;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
.tree-sub_text {
|
||||
width: 100%;
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
293
src/views/traceManage/tshark/components/PacketTable.vue
Normal file
293
src/views/traceManage/tshark/components/PacketTable.vue
Normal file
@@ -0,0 +1,293 @@
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, computed, unref, onUpdated, watchEffect } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
/**列表高度 */
|
||||
height: {
|
||||
type: Number,
|
||||
default: 300,
|
||||
},
|
||||
/**列表项高度 */
|
||||
itemHeight: {
|
||||
type: Number,
|
||||
default: 30,
|
||||
},
|
||||
/**数据 */
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
/**预先兜底缓存数量 */
|
||||
cache: {
|
||||
type: Number,
|
||||
default: 2,
|
||||
},
|
||||
|
||||
/**列 */
|
||||
columns: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
selectedFrame: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
onSelectedFrame: {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
onScrollBottom: {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
});
|
||||
|
||||
const state = reactive<any>({
|
||||
start: 0,
|
||||
end: 10,
|
||||
scrollOffset: 0,
|
||||
cacheData: [],
|
||||
});
|
||||
const virtualListRef = ref();
|
||||
|
||||
const getWrapperStyle = computed(() => {
|
||||
const { height } = props;
|
||||
return {
|
||||
height: `${height}px`,
|
||||
};
|
||||
});
|
||||
|
||||
const getInnerStyle = computed(() => {
|
||||
return {
|
||||
height: `${unref(getTotalHeight)}px`,
|
||||
width: '100%',
|
||||
};
|
||||
});
|
||||
|
||||
const getListStyle = computed(() => {
|
||||
return {
|
||||
willChange: 'transform',
|
||||
transform: `translateY(${state.scrollOffset}px)`,
|
||||
};
|
||||
});
|
||||
|
||||
// 数据数量
|
||||
const total = computed(() => {
|
||||
return props.data.length;
|
||||
});
|
||||
|
||||
// 总体高度
|
||||
const getTotalHeight = computed(() => {
|
||||
return unref(total) * props.itemHeight;
|
||||
});
|
||||
|
||||
// 当前屏幕显示的数量
|
||||
const clientCount = computed(() => {
|
||||
return Math.ceil(props.height / props.itemHeight);
|
||||
});
|
||||
|
||||
// 当前屏幕显示的数据
|
||||
const clientData = computed<any[]>(() => {
|
||||
return props.data.slice(state.start, state.end);
|
||||
});
|
||||
|
||||
const onScroll = (e: any) => {
|
||||
const { scrollHeight, scrollTop, clientHeight } = e.target;
|
||||
if (state.scrollOffset === scrollTop) return;
|
||||
const { cache, height, itemHeight } = props;
|
||||
const cacheCount = Math.max(1, cache);
|
||||
|
||||
let startIndex = Math.floor(scrollTop / itemHeight);
|
||||
|
||||
const endIndex = Math.max(
|
||||
0,
|
||||
Math.min(unref(total), startIndex + unref(clientCount) + cacheCount)
|
||||
);
|
||||
|
||||
if (startIndex > cacheCount) {
|
||||
startIndex = startIndex - cacheCount;
|
||||
}
|
||||
|
||||
// 偏移量
|
||||
const offset = scrollTop - (scrollTop % itemHeight);
|
||||
Object.assign(state, {
|
||||
start: startIndex,
|
||||
end: endIndex,
|
||||
scrollOffset: offset,
|
||||
});
|
||||
|
||||
// 底部小于高度时触发
|
||||
if (scrollHeight - scrollTop - clientHeight < height) {
|
||||
props.onScrollBottom(endIndex);
|
||||
}
|
||||
};
|
||||
|
||||
onUpdated(() => {});
|
||||
|
||||
watchEffect(() => {
|
||||
clientData.value.forEach((_, index) => {
|
||||
const currentIndex = state.start + index;
|
||||
if (Object.hasOwn(state.cacheData, currentIndex)) return;
|
||||
state.cacheData[currentIndex] = {
|
||||
top: currentIndex * props.itemHeight,
|
||||
height: props.itemHeight,
|
||||
bottom: (currentIndex + 1) * props.itemHeight,
|
||||
index: currentIndex,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const tableState = reactive({
|
||||
selected: false,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="table">
|
||||
<div class="thead">
|
||||
<div class="thead-item" v-for="v in columns">
|
||||
{{ v }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="virtual-list-wrapper"
|
||||
ref="wrapperRef"
|
||||
:style="getWrapperStyle"
|
||||
@scroll="onScroll"
|
||||
>
|
||||
<div class="virtual-list-inner" ref="innerRef" :style="getInnerStyle">
|
||||
<div class="virtual-list" :style="getListStyle" ref="virtualListRef">
|
||||
<div
|
||||
class="tbody"
|
||||
v-for="(item, index) in clientData"
|
||||
:key="index + state.start"
|
||||
:style="{
|
||||
height: itemHeight + 'px',
|
||||
backgroundColor:
|
||||
item.number === props.selectedFrame
|
||||
? 'blue'
|
||||
: item.bg
|
||||
? `#${item.bg.toString(16).padStart(6, '0')}`
|
||||
: '',
|
||||
color:
|
||||
item.number === props.selectedFrame
|
||||
? 'white'
|
||||
: item.fg
|
||||
? `#${item.fg.toString(16).padStart(6, '0')}`
|
||||
: '',
|
||||
}"
|
||||
@click="onSelectedFrame(item.number)"
|
||||
>
|
||||
<div class="tbody-item" v-for="col in item.columns">
|
||||
{{ col }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.virtual-list-wrapper {
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
.thead {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.thead-item {
|
||||
white-space: nowrap;
|
||||
padding-bottom: 0.25rem;
|
||||
padding-top: 0.25rem;
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
text-align: left;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5rem;
|
||||
font-weight: 600;
|
||||
/* flex-basis: 100%; */
|
||||
}
|
||||
.tbody {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-top: 1px #f0f0f0 solid;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tbody-item {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5rem;
|
||||
/* flex-basis: 100%; */
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.thead-item:nth-child(1),
|
||||
.tbody-item:nth-child(1) {
|
||||
flex-basis: 5rem;
|
||||
width: 5rem;
|
||||
}
|
||||
.tbody-item:nth-child(1) {
|
||||
text-align: right;
|
||||
}
|
||||
.thead-item:nth-child(2),
|
||||
.tbody-item:nth-child(2) {
|
||||
flex-basis: 8rem;
|
||||
width: 8rem;
|
||||
}
|
||||
.thead-item:nth-child(3),
|
||||
.tbody-item:nth-child(3) {
|
||||
flex-basis: 8rem;
|
||||
width: 8rem;
|
||||
}
|
||||
.thead-item:nth-child(4),
|
||||
.tbody-item:nth-child(4) {
|
||||
flex-basis: 8rem;
|
||||
width: 8rem;
|
||||
}
|
||||
.thead-item:nth-child(5),
|
||||
.tbody-item:nth-child(5) {
|
||||
flex-basis: 6rem;
|
||||
width: 6rem;
|
||||
}
|
||||
.thead-item:nth-child(6),
|
||||
.tbody-item:nth-child(6) {
|
||||
flex-basis: 6rem;
|
||||
width: 6rem;
|
||||
}
|
||||
.tbody-item:nth-child(6) {
|
||||
text-align: right;
|
||||
}
|
||||
.thead-item:nth-child(7),
|
||||
.tbody-item:nth-child(7) {
|
||||
text-align: left;
|
||||
text-wrap: nowrap;
|
||||
flex: 1;
|
||||
width: 5rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 修改滚动条的样式 */
|
||||
.tbody-item:nth-child(7)::-webkit-scrollbar {
|
||||
width: 4px; /* 设置滚动条宽度 */
|
||||
height: 4px;
|
||||
}
|
||||
.tbody-item:nth-child(7)::-webkit-scrollbar-track {
|
||||
background-color: #f0f0f0; /* 设置滚动条轨道背景颜色 */
|
||||
}
|
||||
.tbody-item:nth-child(7)::-webkit-scrollbar-thumb {
|
||||
background-color: #bfbfbf; /* 设置滚动条滑块颜色 */
|
||||
}
|
||||
.tbody-item:nth-child(7)::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #1890ff; /* 设置鼠标悬停时滚动条滑块颜色 */
|
||||
}
|
||||
</style>
|
||||
513
src/views/traceManage/tshark/index.vue
Normal file
513
src/views/traceManage/tshark/index.vue
Normal file
@@ -0,0 +1,513 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted } from 'vue';
|
||||
import { message, Upload } from 'ant-design-vue/lib';
|
||||
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
|
||||
import { FileType } from 'ant-design-vue/lib/upload/interface';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import DissectionTree from './components/DissectionTree.vue';
|
||||
import DissectionDump from './components/DissectionDump.vue';
|
||||
import PacketTable from './components/PacketTable.vue';
|
||||
import { scriptUrl } from '@/assets/js/wiregasm_worker';
|
||||
import { parseSizeFromFile } from '@/utils/parse-utils';
|
||||
import { WK, OptionsType } from '@/plugins/wk-worker';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
const { t } = useI18n();
|
||||
const wk = new WK();
|
||||
|
||||
const NO_SELECTION = { id: '', idx: 0, start: 0, length: 0 };
|
||||
|
||||
type StateType = {
|
||||
/**初始化 */
|
||||
initialized: boolean;
|
||||
/**pcap信息 */
|
||||
summary: {
|
||||
filename: string;
|
||||
file_type: string;
|
||||
file_length: number;
|
||||
file_encap_type: string;
|
||||
packet_count: number;
|
||||
start_time: number;
|
||||
stop_time: number;
|
||||
elapsed_time: number;
|
||||
};
|
||||
/**字段 */
|
||||
columns: string[];
|
||||
/**过滤条件 */
|
||||
filter: string;
|
||||
/**过滤条件错误信息 */
|
||||
filterError: string | null;
|
||||
/**当前过滤条件 */
|
||||
currentFilter: string;
|
||||
/**当前选中的帧编号 */
|
||||
selectedFrame: number;
|
||||
/**当前选中的帧数据 */
|
||||
selectedPacket: { tree: any[]; data_sources: any[] };
|
||||
/**pcap包帧数据 */
|
||||
packetFrameData: Map<string, any> | null;
|
||||
/**当前选中的帧数据-空占位 */
|
||||
selectedTreeEntry: typeof NO_SELECTION;
|
||||
/**选择帧的Dump数据标签 */
|
||||
selectedDataSourceIndex: number;
|
||||
/**处理完成状态 */
|
||||
finishedProcessing: boolean;
|
||||
/**pcap包帧数,匹配帧数 */
|
||||
totalFrames: number;
|
||||
/**pcap包帧数据 */
|
||||
packetFrames: any[];
|
||||
/**加载帧数 */
|
||||
nextPageSize: number;
|
||||
/**加载页数 */
|
||||
nextPageNum: number;
|
||||
/**加载下一页 */
|
||||
nextPageLoad: boolean;
|
||||
};
|
||||
|
||||
const state = reactive<StateType>({
|
||||
initialized: false,
|
||||
summary: {
|
||||
filename: '',
|
||||
file_type: 'Wireshark/tcpdump/... - pcap',
|
||||
file_length: 0,
|
||||
file_encap_type: 'Ethernet',
|
||||
packet_count: 0,
|
||||
start_time: 0,
|
||||
stop_time: 0,
|
||||
elapsed_time: 0,
|
||||
},
|
||||
columns: [],
|
||||
filter: '',
|
||||
filterError: null,
|
||||
currentFilter: '',
|
||||
selectedFrame: 1,
|
||||
/**当前选中的帧数据 */
|
||||
selectedPacket: { tree: [], data_sources: [] },
|
||||
packetFrameData: null, // 注意:Map 需要额外处理
|
||||
selectedTreeEntry: NO_SELECTION, // NO_SELECTION 需要定义
|
||||
/**选择帧的Dump数据标签 */
|
||||
selectedDataSourceIndex: 0,
|
||||
/**处理完成状态 */
|
||||
finishedProcessing: false,
|
||||
totalFrames: 0,
|
||||
packetFrames: [],
|
||||
nextPageNum: 1,
|
||||
nextPageSize: 40,
|
||||
nextPageLoad: false,
|
||||
});
|
||||
|
||||
// 清除帧数据和报文信息状态
|
||||
function fnStateReset() {
|
||||
// 加载pcap包的数据
|
||||
state.nextPageNum = 1;
|
||||
// 选择帧的数据
|
||||
state.selectedFrame = 0;
|
||||
state.selectedPacket = { tree: [], data_sources: [] };
|
||||
state.packetFrameData = null;
|
||||
state.selectedTreeEntry = NO_SELECTION;
|
||||
state.selectedDataSourceIndex = 0;
|
||||
}
|
||||
|
||||
/**解析帧数据为简单结构 */
|
||||
function parseFrameData(id: string, node: Record<string, any>) {
|
||||
let map = new Map();
|
||||
|
||||
if (node.tree && node.tree.length > 0) {
|
||||
for (let i = 0; i < node.tree.length; i++) {
|
||||
const subMap = parseFrameData(`${id}-${i}`, node.tree[i]);
|
||||
subMap.forEach((value, key) => {
|
||||
map.set(key, value);
|
||||
});
|
||||
}
|
||||
} else if (node.length > 0) {
|
||||
map.set(id, {
|
||||
id: id,
|
||||
idx: node.data_source_idx,
|
||||
start: node.start,
|
||||
length: node.length,
|
||||
});
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**帧数据点击选中 */
|
||||
function fnSelectedTreeEntry(e: any) {
|
||||
console.log('fnSelectedTreeEntry', e);
|
||||
state.selectedTreeEntry = e;
|
||||
}
|
||||
/**报文数据点击选中 */
|
||||
function fnSelectedFindSelection(src_idx: number, pos: number) {
|
||||
console.log('fnSelectedFindSelection', pos);
|
||||
if (state.packetFrameData == null) return;
|
||||
// find the smallest one
|
||||
let current = null;
|
||||
for (let [k, pp] of state.packetFrameData) {
|
||||
if (pp.idx !== src_idx) continue;
|
||||
|
||||
if (pos >= pp.start && pos <= pp.start + pp.length) {
|
||||
if (
|
||||
current != null &&
|
||||
state.packetFrameData.get(current).length > pp.length
|
||||
) {
|
||||
current = k;
|
||||
} else {
|
||||
current = k;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (current != null) {
|
||||
state.selectedTreeEntry = state.packetFrameData.get(current);
|
||||
}
|
||||
}
|
||||
/**包数据表点击选中 */
|
||||
function fnSelectedFrame(no: number) {
|
||||
console.log('fnSelectedFrame', no, state.totalFrames);
|
||||
state.selectedFrame = no;
|
||||
wk.send({ type: 'select', number: state.selectedFrame });
|
||||
}
|
||||
/**包数据表滚动底部加载 */
|
||||
function fnScrollBottom() {
|
||||
const totalFetched = state.packetFrames.length;
|
||||
console.log('fnScrollBottom', totalFetched);
|
||||
if (!state.nextPageLoad && totalFetched < state.totalFrames) {
|
||||
state.nextPageLoad = true;
|
||||
state.nextPageNum++;
|
||||
fnLoaldFrames(state.filter, state.nextPageNum);
|
||||
}
|
||||
}
|
||||
/**包数据表过滤 */
|
||||
function fnFilterFrames() {
|
||||
console.log('fnFilterFinish', state.filter);
|
||||
wk.send({ type: 'check-filter', filter: state.filter });
|
||||
}
|
||||
/**包数据表记载 */
|
||||
function fnLoaldFrames(filter: string, page: number = 1) {
|
||||
if (!(state.initialized && state.finishedProcessing)) return;
|
||||
const limit = state.nextPageSize;
|
||||
wk.send({
|
||||
type: 'frames',
|
||||
filter: filter,
|
||||
skip: (page - 1) * limit,
|
||||
limit: limit,
|
||||
});
|
||||
}
|
||||
|
||||
/**本地示例文件 */
|
||||
async function fnLoadExample() {
|
||||
const name = 'test_ethernet.pcap';
|
||||
const res = await fetch('/wiregasm/test_ethernet.pcap');
|
||||
const body = await res.arrayBuffer();
|
||||
|
||||
state.summary = {
|
||||
filename: '',
|
||||
file_type: 'Wireshark/tcpdump/... - pcap',
|
||||
file_length: 0,
|
||||
file_encap_type: 'Ethernet',
|
||||
packet_count: 0,
|
||||
start_time: 0,
|
||||
stop_time: 0,
|
||||
elapsed_time: 0,
|
||||
};
|
||||
state.finishedProcessing = false;
|
||||
|
||||
wk.send({ type: 'process-data', name: name, data: body });
|
||||
}
|
||||
|
||||
/**上传前检查或转换压缩 */
|
||||
function fnBeforeUpload(file: FileType) {
|
||||
const fileName = file.name;
|
||||
const suff = fileName.substring(fileName.lastIndexOf('.'));
|
||||
const allowList = ['.pcap', '.cap', '.pcapng', '.pcap0'];
|
||||
if (!allowList.includes(suff)) {
|
||||
const msg = `${t('components.UploadModal.onlyAllow')} ${allowList.join(
|
||||
','
|
||||
)}`;
|
||||
message.error(msg, 3);
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**表单上传文件 */
|
||||
function fnUpload(up: UploadRequestOption) {
|
||||
state.summary = {
|
||||
filename: '',
|
||||
file_type: 'Wireshark/tcpdump/... - pcap',
|
||||
file_length: 0,
|
||||
file_encap_type: 'Ethernet',
|
||||
packet_count: 0,
|
||||
start_time: 0,
|
||||
stop_time: 0,
|
||||
elapsed_time: 0,
|
||||
};
|
||||
state.finishedProcessing = false;
|
||||
|
||||
wk.send({ type: 'process', file: up.file });
|
||||
}
|
||||
|
||||
/**接收数据后回调 */
|
||||
function wkMessage(res: Record<string, any>) {
|
||||
switch (res.type) {
|
||||
case 'status':
|
||||
console.info(res.status);
|
||||
break;
|
||||
case 'error':
|
||||
console.warn(res.error);
|
||||
break;
|
||||
case 'init':
|
||||
wk.send({ type: 'columns' });
|
||||
state.initialized = true;
|
||||
break;
|
||||
case 'columns':
|
||||
state.columns = res.data;
|
||||
break;
|
||||
case 'frames':
|
||||
// console.log(res.data);
|
||||
const { matched, frames } = res.data;
|
||||
state.totalFrames = matched;
|
||||
|
||||
if (state.nextPageNum == 1) {
|
||||
state.packetFrames = frames;
|
||||
// 有匹配的选择第一个
|
||||
if (frames.length > 0) {
|
||||
state.selectedFrame = frames[0].number;
|
||||
fnSelectedFrame(state.selectedFrame);
|
||||
}
|
||||
} else {
|
||||
state.packetFrames = state.packetFrames.concat(frames);
|
||||
state.nextPageLoad = false;
|
||||
}
|
||||
break;
|
||||
case 'selected':
|
||||
state.selectedPacket = res.data;
|
||||
state.packetFrameData = parseFrameData('root', res.data);
|
||||
state.selectedTreeEntry = NO_SELECTION;
|
||||
state.selectedDataSourceIndex = 0;
|
||||
break;
|
||||
case 'processed':
|
||||
// setStatus(`Error: non-zero return code (${e.data.code})`);
|
||||
state.finishedProcessing = true;
|
||||
if (res.data.code === 0) {
|
||||
state.summary = res.data.summary;
|
||||
}
|
||||
|
||||
// 加载数据
|
||||
fnStateReset();
|
||||
fnLoaldFrames(state.filter);
|
||||
break;
|
||||
case 'filter':
|
||||
const filterRes = res.data;
|
||||
if (filterRes.ok) {
|
||||
state.currentFilter = state.filter;
|
||||
state.filterError = null;
|
||||
// 加载数据
|
||||
fnStateReset();
|
||||
fnLoaldFrames(state.filter);
|
||||
} else {
|
||||
state.filterError = filterRes.error;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.log(res);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 建立链接
|
||||
const options: OptionsType = {
|
||||
url: scriptUrl,
|
||||
onmessage: wkMessage,
|
||||
onerror: (ev: any) => {
|
||||
console.error(ev);
|
||||
},
|
||||
};
|
||||
wk.connect(options);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:loading="!state.initialized"
|
||||
:body-style="{ padding: '12px' }"
|
||||
>
|
||||
<div class="toolbar">
|
||||
<a-space :size="8" class="toolbar-oper">
|
||||
<a-upload
|
||||
name="file"
|
||||
list-type="picture"
|
||||
:max-count="1"
|
||||
accept=".pcap,.cap,.pcapng,.pcap0"
|
||||
:show-upload-list="false"
|
||||
:before-upload="fnBeforeUpload"
|
||||
:custom-request="fnUpload"
|
||||
>
|
||||
<a-button type="primary"> Upload </a-button>
|
||||
</a-upload>
|
||||
<a-button @click="fnLoadExample">Example</a-button>
|
||||
</a-space>
|
||||
|
||||
<div class="toolbar-info">
|
||||
<a-tag color="green" v-show="!!state.currentFilter">
|
||||
{{ state.currentFilter }}
|
||||
</a-tag>
|
||||
<span> Matched Frame: {{ state.totalFrames }} </span>
|
||||
</div>
|
||||
|
||||
<!-- 包信息 -->
|
||||
<a-popover
|
||||
trigger="click"
|
||||
placement="bottomLeft"
|
||||
v-if="state.summary.filename"
|
||||
>
|
||||
<template #content>
|
||||
<div class="summary">
|
||||
<div class="summary-item">
|
||||
<span>Type:</span>
|
||||
<span>{{ state.summary.file_type }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span>Size:</span>
|
||||
<span>{{ parseSizeFromFile(state.summary.file_length) }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span>Encapsulation:</span>
|
||||
<span>{{ state.summary.file_encap_type }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span>Packets:</span>
|
||||
<span>{{ state.summary.packet_count }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span>Duration:</span>
|
||||
<span>{{ Math.round(state.summary.elapsed_time) }}s</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<InfoCircleOutlined />
|
||||
</a-popover>
|
||||
</div>
|
||||
|
||||
<!-- 包数据表过滤 -->
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
v-model:value="state.filter"
|
||||
placeholder="display filter, example: tcp"
|
||||
:allow-clear="true"
|
||||
style="width: calc(100% - 100px)"
|
||||
@pressEnter="fnFilterFrames"
|
||||
>
|
||||
<template #prefix>
|
||||
<FilterOutlined />
|
||||
</template>
|
||||
</a-input>
|
||||
<a-button
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
style="width: 100px"
|
||||
@click="fnFilterFrames"
|
||||
>
|
||||
Filter
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<a-alert
|
||||
:message="state.filterError"
|
||||
type="error"
|
||||
v-if="state.filterError != null"
|
||||
/>
|
||||
|
||||
<!-- 包数据表 -->
|
||||
<PacketTable
|
||||
:columns="state.columns"
|
||||
:data="state.packetFrames"
|
||||
:selectedFrame="state.selectedFrame"
|
||||
:onSelectedFrame="fnSelectedFrame"
|
||||
:onScrollBottom="fnScrollBottom"
|
||||
></PacketTable>
|
||||
|
||||
<a-row :gutter="20">
|
||||
<a-col :lg="12" :md="12" :xs="24" class="tree">
|
||||
<!-- 帧数据 -->
|
||||
<DissectionTree
|
||||
id="root"
|
||||
:select="fnSelectedTreeEntry"
|
||||
:selected="state.selectedTreeEntry"
|
||||
:tree="state.selectedPacket.tree"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24" class="dump">
|
||||
<!-- 报文数据 -->
|
||||
<a-tabs
|
||||
v-model:activeKey="state.selectedDataSourceIndex"
|
||||
:tab-bar-gutter="16"
|
||||
:tab-bar-style="{ marginBottom: '8px' }"
|
||||
>
|
||||
<a-tab-pane
|
||||
:key="idx"
|
||||
:tab="v.name"
|
||||
v-for="(v, idx) in state.selectedPacket.data_sources"
|
||||
style="overflow: auto"
|
||||
>
|
||||
<DissectionDump
|
||||
:base64="v.data"
|
||||
:select="(pos:number)=>fnSelectedFindSelection(idx, pos)"
|
||||
:selected="
|
||||
idx === state.selectedTreeEntry.idx
|
||||
? state.selectedTreeEntry
|
||||
: NO_SELECTION
|
||||
"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.toolbar-info {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.summary-item > span:first-child {
|
||||
font-weight: 600;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.tree {
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.5rem;
|
||||
padding-bottom: 0.75rem;
|
||||
padding-top: 0.75rem;
|
||||
white-space: nowrap;
|
||||
overflow-y: auto;
|
||||
user-select: none;
|
||||
height: 100%;
|
||||
}
|
||||
.tree > ul.tree {
|
||||
min-height: 15rem;
|
||||
}
|
||||
|
||||
.dump {
|
||||
padding-bottom: 0.75rem;
|
||||
padding-top: 0.75rem;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
}
|
||||
.dump .ant-tabs-tabpane {
|
||||
min-height: calc(15rem - 56px);
|
||||
}
|
||||
</style>
|
||||
16
src/views/traceManage/wireshark/index.vue
Normal file
16
src/views/traceManage/wireshark/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>JS</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
Reference in New Issue
Block a user