Merge branch 'lichang' into lite
This commit is contained in:
18
package.json
18
package.json
@@ -12,19 +12,19 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons-vue": "^7.0.1",
|
||||
"@antv/g6": "4.8.24",
|
||||
"@ant-design/icons-vue": "7.0.1",
|
||||
"@antv/g6": "4.8.25",
|
||||
"@codemirror/lang-javascript": "6.2.3",
|
||||
"@codemirror/lang-yaml": "6.1.2",
|
||||
"@codemirror/merge": "6.10.0",
|
||||
"@codemirror/theme-one-dark": "6.1.2",
|
||||
"@tato30/vue-pdf": "1.11.3",
|
||||
"@vueuse/core": "12.8.2",
|
||||
"@vueuse/core": "13.0.0",
|
||||
"@xterm/addon-fit": "0.10.0",
|
||||
"@xterm/xterm": "5.5.0",
|
||||
"ant-design-vue": "4.2.6",
|
||||
"antdv-pro-layout": "4.2.0",
|
||||
"antdv-pro-modal": "4.0.6",
|
||||
"antdv-pro-modal": "4.0.8",
|
||||
"codemirror": "6.0.1",
|
||||
"crypto-js": "4.2.0",
|
||||
"dayjs": "1.11.13",
|
||||
@@ -50,12 +50,12 @@
|
||||
"@types/js-cookie": "3.0.6",
|
||||
"@types/node": "^18.0.0",
|
||||
"@types/nprogress": "0.2.3",
|
||||
"@vitejs/plugin-vue": "5.2.1",
|
||||
"@vitejs/plugin-vue": "5.2.3",
|
||||
"less": "4.2.2",
|
||||
"typescript": "5.6.3",
|
||||
"typescript": "5.8.2",
|
||||
"unplugin-vue-components": "0.28.0",
|
||||
"vite": "6.2.0",
|
||||
"vite-plugin-compression": "~0.5.1",
|
||||
"vue-tsc": "2.2.0"
|
||||
"vite": "6.3.3",
|
||||
"vite-plugin-compression": "0.5.1",
|
||||
"vue-tsc": "2.2.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
imsi,msisdn,sess_rules,pcc_rules,hdr_enrich,rfsp,sar,qos_audio,qos_video
|
||||
001012082101039,1234,internet|ims_sig,internet|ims_sig,321321,255,321312,32131,32131
|
||||
001012082101039,1234,internet|ims_sig,internet|ims_sig,321321,255,321312,32131,32131
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
001011100001157,1234567890ABCDEF1234567890ABCDEF,0,8000,11111111111111111111111111111111
|
||||
001011100001158,1234567890ABCDEF1234567890ABCDEF,0,8000,11111111111111111111111111111111
|
||||
001011100001158,1234567890ABCDEF1234567890ABCDEF,0,8000,11111111111111111111111111111111
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
001011100001157,62357000583,def_ambr,def_nssai,def_arfb,def_sar,0,3,def_snssai,1-000001&internet&ims,1,64,24,65,def_eps,1,010200000000,-
|
||||
001011100001158,62357000585,def_ambr,def_nssai,def_arfb,def_sar,0,3,def_snssai,1-000001&internet&ims,1,64,24,65,def_eps,1,010200000000,-
|
||||
001011100001158,62357000585,def_ambr,def_nssai,def_arfb,def_sar,0,3,def_snssai,1-000001&internet&ims,1,64,24,65,def_eps,1,010200000000,-
|
||||
|
||||
3
public/neDataImput/udm_voip_template.txt
Normal file
3
public/neDataImput/udm_voip_template.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
#username,password
|
||||
62357000580,123456
|
||||
62357000581,123456
|
||||
4
public/neDataImput/udm_volte_template.txt
Normal file
4
public/neDataImput/udm_volte_template.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
#vlote=0 MSISDN and IMSI need to be filled in the same way.
|
||||
#imsi,msisdn,vlote,vni
|
||||
460996650000580,62357000580,1,ims.mnc000.mcc460.3gppnetwork.org
|
||||
62357000581,62357000581,0,ims.mnc000.mcc460.3gppnetwork.org
|
||||
@@ -1,15 +1,28 @@
|
||||
import { CACHE_SESSION_CRYPTO_API } from '@/constants/cache-keys-constants';
|
||||
import { sessionGet } from '@/utils/cache-session-utils';
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
// 登录方法
|
||||
/**
|
||||
* 登录方法
|
||||
* @param data 数据
|
||||
* @returns 结果
|
||||
*/
|
||||
export function login(data: Record<string, string>) {
|
||||
return request({
|
||||
url: '/login',
|
||||
url: '/auth/login',
|
||||
method: 'POST',
|
||||
data: data,
|
||||
whithToken: false,
|
||||
crypto: sessionGet(CACHE_SESSION_CRYPTO_API) !== 'false',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出方法
|
||||
* @returns object
|
||||
*/
|
||||
export function logout() {
|
||||
return request({
|
||||
url: '/auth/logout',
|
||||
method: 'POST',
|
||||
repeatSubmit: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,11 +33,24 @@ export function login(data: Record<string, string>) {
|
||||
*/
|
||||
export function register(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/register',
|
||||
url: '/auth/register',
|
||||
method: 'POST',
|
||||
data: data,
|
||||
whithToken: false,
|
||||
crypto: sessionGet(CACHE_SESSION_CRYPTO_API) !== 'false',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新登录令牌
|
||||
* @param data 数据
|
||||
* @returns 结果
|
||||
*/
|
||||
export function refreshToken(refreshToken: string) {
|
||||
return request({
|
||||
url: '/auth/refresh-token',
|
||||
method: 'POST',
|
||||
data: { refreshToken },
|
||||
whithToken: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -40,16 +66,15 @@ export function getInfo() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出方法
|
||||
* 获取路由
|
||||
* @returns object
|
||||
*/
|
||||
export function logout() {
|
||||
export const getRouter = () => {
|
||||
return request({
|
||||
url: '/logout',
|
||||
method: 'POST',
|
||||
repeatSubmit: false,
|
||||
url: '/router',
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取验证码
|
||||
@@ -116,6 +116,21 @@ export function clearAlarm(ids: number[]) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 告警信息导出
|
||||
* @param params 查询列表条件
|
||||
* @returns object
|
||||
*/
|
||||
export function exportAlarm(params: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/alarm/export',
|
||||
method: 'GET',
|
||||
params: params,
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 手工同步
|
||||
* @param data 鉴权对象
|
||||
|
||||
@@ -4,19 +4,6 @@ import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export async function listAct(query: Record<string, any>) {
|
||||
return await request({
|
||||
url: `/neData/alarm/list`,
|
||||
method: 'GET',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认告警信息
|
||||
* @param data 鉴权对象
|
||||
|
||||
@@ -1,65 +1,5 @@
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { NE_TYPE_LIST } from '@/constants/ne-constants';
|
||||
|
||||
/**
|
||||
* 查询公告列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export async function listMain() {
|
||||
const result = await request({
|
||||
url: '/api/rest/systemManagement/v1/elementType/all/objectType/systemState',
|
||||
method: 'GET',
|
||||
timeout: 60_000,
|
||||
});
|
||||
// console.log(result);
|
||||
let realData = result.data.data;
|
||||
const mergedData = realData.map((obj: any) => {
|
||||
// console.log(obj);
|
||||
const [key, value] = Object.entries(obj)[0];
|
||||
const ipAddress = (value as any).ipAddress;
|
||||
const systemState = (value as any).systemState;
|
||||
const serialNum = (value as any).serialNum;
|
||||
const version = (value as any).version;
|
||||
|
||||
const errCode = systemState && systemState['errorCode'];
|
||||
var time = new Date();
|
||||
// console.log(key, value);
|
||||
let mergedObj;
|
||||
if (errCode === undefined && systemState) {
|
||||
mergedObj = {
|
||||
...systemState,
|
||||
refresh: parseDateToStr(time),
|
||||
ipAddress: ipAddress,
|
||||
name: key.split('/').join('_'),
|
||||
status: 'Normal',
|
||||
};
|
||||
} else {
|
||||
mergedObj = {
|
||||
version,
|
||||
refresh: parseDateToStr(time),
|
||||
ipAddress,
|
||||
serialNum,
|
||||
name: key.split('/').join('_'),
|
||||
expiryDate: '-',
|
||||
status: 'Abnormal',
|
||||
};
|
||||
}
|
||||
return mergedObj;
|
||||
});
|
||||
//通过sort进行冒泡排序
|
||||
mergedData.sort((a: any, b: any) => {
|
||||
const typeA = NE_TYPE_LIST.indexOf(a.name.split('_')[0]);
|
||||
const typeB = NE_TYPE_LIST.indexOf(b.name.split('_')[0]);
|
||||
if (typeA === -1) return 1; // 如果不在特定顺序中,排到后面
|
||||
if (typeB === -1) return -1; // 如果不在特定顺序中,排到后面
|
||||
return typeA - typeB;
|
||||
});
|
||||
|
||||
return mergedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务器时间
|
||||
|
||||
@@ -53,43 +53,3 @@ export function delFile(query: Record<string, any>) {
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新FTP信息
|
||||
* @param data 数据
|
||||
* @returns object
|
||||
*/
|
||||
export function updateFTPInfo(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/lm/table/ftp`,
|
||||
method: 'POST',
|
||||
data: data,
|
||||
crypto: sessionGet(CACHE_SESSION_CRYPTO_API) !== 'false',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取FTP信息
|
||||
* @param data 数据
|
||||
* @returns object
|
||||
*/
|
||||
export function getFTPInfo() {
|
||||
return request({
|
||||
url: `/lm/table/ftp`,
|
||||
method: 'GET',
|
||||
crypto: sessionGet(CACHE_SESSION_CRYPTO_API) !== 'false',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送FTP文件
|
||||
* @param data 数据
|
||||
* @returns object
|
||||
*/
|
||||
export function putFTPInfo(filePath: string, fileName: string) {
|
||||
return request({
|
||||
url: `/lm/table/ftp`,
|
||||
method: 'PUT',
|
||||
data: { filePath, fileName },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -83,43 +83,3 @@ export function importNeConfigBackup(data: Record<string, any>) {
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新FTP信息
|
||||
* @param data 数据
|
||||
* @returns object
|
||||
*/
|
||||
export function updateFTPInfo(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ne/config/backup/ftp`,
|
||||
method: 'POST',
|
||||
data: data,
|
||||
crypto: sessionGet(CACHE_SESSION_CRYPTO_API) !== 'false',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取FTP信息
|
||||
* @param data 数据
|
||||
* @returns object
|
||||
*/
|
||||
export function getFTPInfo() {
|
||||
return request({
|
||||
url: `/ne/config/backup/ftp`,
|
||||
method: 'GET',
|
||||
crypto: sessionGet(CACHE_SESSION_CRYPTO_API) !== 'false',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送FTP文件
|
||||
* @param data 数据
|
||||
* @returns object
|
||||
*/
|
||||
export function putFTPInfo(path: string) {
|
||||
return request({
|
||||
url: `/ne/config/backup/ftp`,
|
||||
method: 'PUT',
|
||||
data: { path },
|
||||
});
|
||||
}
|
||||
|
||||
38
src/api/neData/backup.ts
Normal file
38
src/api/neData/backup.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 备份文件-获取FTP配置
|
||||
* @returns object
|
||||
*/
|
||||
export function getBackupFTP() {
|
||||
return request({
|
||||
url: '/neData/backup/ftp',
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份文件-文件FTP发送
|
||||
* @param data 对象
|
||||
* @returns object
|
||||
*/
|
||||
export function pushBackupFTP(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/backup/ftp',
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份文件-更新FTP配置
|
||||
* @param data 对象
|
||||
* @returns object
|
||||
*/
|
||||
export function updateBackupFTP(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/backup/ftp',
|
||||
method: 'PUT',
|
||||
data,
|
||||
});
|
||||
}
|
||||
134
src/api/neData/udm_voip.ts
Normal file
134
src/api/neData/udm_voip.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* UDMVOIP用户重载数据
|
||||
* @param neId 网元ID
|
||||
* @returns object
|
||||
*/
|
||||
export function resetUDMVOIP(neId: string) {
|
||||
return request({
|
||||
url: `/neData/udm/voip/resetData/${neId}`,
|
||||
method: 'PUT',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDMVOIP用户列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listUDMVOIP(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/udm/voip/list',
|
||||
method: 'GET',
|
||||
params: query,
|
||||
timeout: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDMVOIP用户信息
|
||||
* @param neId 网元ID
|
||||
* @param username username
|
||||
* @returns object
|
||||
*/
|
||||
export function getUDMVOIP(neId: string, username: string) {
|
||||
return request({
|
||||
url: `/neData/udm/voip/${neId}/${username}`,
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDMVOIP用户新增
|
||||
* @param data VOIP对象
|
||||
* @returns object
|
||||
*/
|
||||
export function addUDMVOIP(
|
||||
neId: string,
|
||||
data: { username: string; password: string }
|
||||
) {
|
||||
return request({
|
||||
url: `/neData/udm/voip/${neId}`,
|
||||
method: 'POST',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDMVOIP用户批量新增
|
||||
* @param data VOIP对象
|
||||
* @param num 数量
|
||||
* @returns object
|
||||
*/
|
||||
export function batchAddUDMVOIP(
|
||||
neId: string,
|
||||
data: { username: string; password: string },
|
||||
num: number
|
||||
) {
|
||||
return request({
|
||||
url: `/neData/udm/voip/${neId}/${num}`,
|
||||
method: 'POST',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDMVOIP用户删除
|
||||
* @param data VOIP对象
|
||||
* @returns object
|
||||
*/
|
||||
export function delUDMVOIP(neId: string, username: string) {
|
||||
return request({
|
||||
url: `/neData/udm/voip/${neId}/${username}`,
|
||||
method: 'DELETE',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDMVOIP用户批量删除
|
||||
* @param neId 网元ID
|
||||
* @param username username
|
||||
* @param num 数量
|
||||
* @returns object
|
||||
*/
|
||||
export function batchDelUDMVOIP(neId: string, username: string, num: number) {
|
||||
return request({
|
||||
url: `/neData/udm/voip/${neId}/${username}/${num}`,
|
||||
method: 'DELETE',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDMVOIP用户导出
|
||||
* @param data 数据参数
|
||||
* @returns bolb
|
||||
*/
|
||||
export function exportUDMVOIP(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/udm/voip/export',
|
||||
method: 'GET',
|
||||
params: data,
|
||||
responseType: 'blob',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDMVOIP用户导入
|
||||
* @param data 表单数据对象
|
||||
* @returns object
|
||||
*/
|
||||
export function importUDMVOIP(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/neData/udm/voip/import`,
|
||||
method: 'POST',
|
||||
data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
127
src/api/neData/udm_volte_ims.ts
Normal file
127
src/api/neData/udm_volte_ims.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* UDMVolteIMS用户重载数据
|
||||
* @param neId 网元ID
|
||||
* @returns object
|
||||
*/
|
||||
export function resetUDMVolteIMS(neId: string) {
|
||||
return request({
|
||||
url: `/neData/udm/volte-ims/resetData/${neId}`,
|
||||
method: 'PUT',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDMVolteIMS用户列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listUDMVolteIMS(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/udm/volte-ims/list',
|
||||
method: 'GET',
|
||||
params: query,
|
||||
timeout: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDMVolteIMS用户信息
|
||||
* @param neId 网元ID
|
||||
* @param imsi IMSI
|
||||
* @returns object
|
||||
*/
|
||||
export function getUDMVolteIMS(neId: string, imsi: string) {
|
||||
return request({
|
||||
url: `/neData/udm/volte-ims/${neId}/${imsi}`,
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDMVolteIMS用户新增
|
||||
* @param data 签约对象
|
||||
* @returns object
|
||||
*/
|
||||
export function addUDMVolteIMS(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/neData/udm/volte-ims/${data.neId}`,
|
||||
method: 'POST',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDMVolteIMS用户批量新增
|
||||
* @param data 签约对象
|
||||
* @param num 数量
|
||||
* @returns object
|
||||
*/
|
||||
export function batchAddUDMVolteIMS(data: Record<string, any>, num: number) {
|
||||
return request({
|
||||
url: `/neData/udm/volte-ims/${data.neId}/${num}`,
|
||||
method: 'POST',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDMVolteIMS用户删除
|
||||
* @param data 签约对象
|
||||
* @returns object
|
||||
*/
|
||||
export function delUDMVolteIMS(neId: string, imsi: string) {
|
||||
return request({
|
||||
url: `/neData/udm/volte-ims/${neId}/${imsi}`,
|
||||
method: 'DELETE',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDMVolteIMS用户批量删除
|
||||
* @param neId 网元ID
|
||||
* @param imsi IMSI
|
||||
* @param num 数量
|
||||
* @returns object
|
||||
*/
|
||||
export function batchDelUDMVolteIMS(neId: string, imsi: string, num: number) {
|
||||
return request({
|
||||
url: `/neData/udm/volte-ims/${neId}/${imsi}/${num}`,
|
||||
method: 'DELETE',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDMVolteIMS用户导出
|
||||
* @param data 数据参数
|
||||
* @returns bolb
|
||||
*/
|
||||
export function exportUDMVolteIMS(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/udm/volte-ims/export',
|
||||
method: 'GET',
|
||||
params: data,
|
||||
responseType: 'blob',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDMVolteIMS用户导入
|
||||
* @param data 表单数据对象
|
||||
* @returns object
|
||||
*/
|
||||
export function importUDMVolteIMS(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/neData/udm/volte-ims/import`,
|
||||
method: 'POST',
|
||||
data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
@@ -220,6 +220,6 @@ export function taskRun(data: Record<string, any>) {
|
||||
export function taskStop(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/api/rest/performanceManagement/v1/elementType/${data.neType.toLowerCase()}/objectType/measureTask?id=${data.id}`,
|
||||
method: 'PATCH',
|
||||
method: 'PUT',
|
||||
});
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 获取路由
|
||||
* @returns object
|
||||
*/
|
||||
export const getRouters = () => {
|
||||
return request({
|
||||
url: '/router',
|
||||
method: 'GET',
|
||||
});
|
||||
};
|
||||
@@ -136,3 +136,16 @@ export function changeUserStatus(
|
||||
data: { userId, statusFlag },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户强制重置密码
|
||||
* @param password 密码
|
||||
* @returns object
|
||||
*/
|
||||
export function updateUserPasswordForce(password: string) {
|
||||
return request({
|
||||
url: '/system/user/profile/password-force',
|
||||
method: 'PUT',
|
||||
data: { password },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -210,6 +210,50 @@ export function chunkUpload(data: FormData) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地文件列表
|
||||
* @param path 文件路径
|
||||
* @param search search prefix
|
||||
* @returns object
|
||||
*/
|
||||
export async function listFile(query: Record<string, any>) {
|
||||
return request({
|
||||
url: `/file/list`,
|
||||
method: 'GET',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地文件获取下载
|
||||
* @param path 文件路径
|
||||
* @param fileName 文件名
|
||||
* @returns object
|
||||
*/
|
||||
export async function getFile(path: string, fileName: string) {
|
||||
return request({
|
||||
url: `/file`,
|
||||
method: 'GET',
|
||||
params: { path, fileName },
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地文件删除
|
||||
* @param path 文件路径
|
||||
* @param fileName 文件名
|
||||
* @returns object
|
||||
*/
|
||||
export async function delFile(path: string, fileName: string) {
|
||||
return request({
|
||||
url: `/file`,
|
||||
method: 'DELETE',
|
||||
params: { path, fileName },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 转存上传文件到静态资源
|
||||
* @returns object
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 跟踪任务数据列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export async function listTraceData(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/trace/task/list',
|
||||
method: 'GET',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 信令数据解析HTML
|
||||
* @param id 任务ID
|
||||
* @returns
|
||||
*/
|
||||
export function getTraceRawInfo(id: Record<string, string>) {
|
||||
return request({
|
||||
url: `/api/rest/traceManagement/v1/decMessage/${id}`,
|
||||
method: 'GET',
|
||||
responseType: 'text',
|
||||
});
|
||||
}
|
||||
@@ -62,3 +62,18 @@ export function packetKeep(taskNo: string, duration: number = 120) {
|
||||
data: { taskNo, duration },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 信令跟踪文件
|
||||
* @param taskNo 对象
|
||||
* @returns object
|
||||
*/
|
||||
export function packetPCAPFile(taskNo: string) {
|
||||
return request({
|
||||
url: '/trace/packet/filePull',
|
||||
method: 'GET',
|
||||
params: { taskNo },
|
||||
responseType: 'blob',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,29 +76,31 @@ export function filePullTask(traceId: string) {
|
||||
method: 'GET',
|
||||
params: { traceId },
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取网元跟踪接口列表
|
||||
* 跟踪任务数据列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export async function getNeTraceInterfaceAll() {
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/elementType/omc_db/objectType/ne_info`,
|
||||
export async function listTraceData(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/trace/data/list',
|
||||
method: 'GET',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询跟踪任务数据信息
|
||||
* @param id ID
|
||||
* @returns object
|
||||
*/
|
||||
export async function getTraceData(id: string | number) {
|
||||
return request({
|
||||
url: `/trace/data/${id}`,
|
||||
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;
|
||||
}
|
||||
|
||||
218
src/components/ForcePasswdChange/index.vue
Normal file
218
src/components/ForcePasswdChange/index.vue
Normal file
@@ -0,0 +1,218 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||
import { regExpPasswd, regExpUserName } from '@/utils/regular-utils';
|
||||
import { Form, message, Modal } from 'ant-design-vue';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import { updateUserPasswordForce } from '@/api/system/user';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { getConfigKey } from '@/api/system/config';
|
||||
const userStore = useUserStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**重置密码框是否显示 */
|
||||
open: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
/**密码策略 */
|
||||
passwordPolicy: Record<string, any>;
|
||||
/**密码有效期 */
|
||||
passwdExpireEnable: boolean;
|
||||
passwdExpire: Record<string, any>;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
open: userStore.forcePasswdChange,
|
||||
title: t('components.ForcePasswdChange.title'),
|
||||
from: {
|
||||
userId: userStore.userId,
|
||||
userName: userStore.userName,
|
||||
password: '',
|
||||
},
|
||||
confirmLoading: false,
|
||||
passwordPolicy: { minLength: 8, specialChars: 2, uppercase: 1, lowercase: 1 },
|
||||
passwdExpireEnable: false,
|
||||
passwdExpire: { expHours: 2, alertHours: 1 },
|
||||
});
|
||||
|
||||
/**对话框内表单属性和校验规则 */
|
||||
const modalStateFrom = Form.useForm(
|
||||
modalState.from,
|
||||
reactive({
|
||||
userName: [
|
||||
{
|
||||
required: true,
|
||||
pattern: regExpUserName,
|
||||
message: t('views.system.user.userNameTip'),
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
pattern: regExpPasswd,
|
||||
message: t('views.system.user.passwdTip'),
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**对话框提交确认 */
|
||||
function fnModalOk() {
|
||||
const { password } = modalState.from;
|
||||
if (!password) {
|
||||
message.error({
|
||||
content: t('views.system.user.passwdTip'),
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
updateUserPasswordForce(password).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
userStore.fnLogOut();
|
||||
modalState.confirmLoading = true;
|
||||
Modal.success({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.account.settings.submitOkTip', {
|
||||
num: modalState.from.userName,
|
||||
}),
|
||||
okText: t('views.account.settings.submitOk'),
|
||||
onOk() {
|
||||
window.location.reload();
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**组件实例挂载之后调用 */
|
||||
onMounted(() => {
|
||||
Promise.all([
|
||||
getConfigKey('sys.user.passwordPolicy'),
|
||||
getConfigKey('sys.user.passwdExpire'),
|
||||
]).then(resArr => {
|
||||
if (resArr[0].code === RESULT_CODE_SUCCESS) {
|
||||
try {
|
||||
modalState.passwordPolicy = JSON.parse(resArr[0].data);
|
||||
} catch (error) {
|
||||
console.error('passwordPolicy', error);
|
||||
}
|
||||
}
|
||||
if (resArr[1].code === RESULT_CODE_SUCCESS) {
|
||||
try {
|
||||
const data = JSON.parse(resArr[1].data);
|
||||
if (data.expHours % 24 === 0) {
|
||||
data.expHours = data.expHours / 24;
|
||||
} else {
|
||||
data.expHours = (data.expHours / 24).toFixed(2);
|
||||
}
|
||||
if (data.alertHours % 24 === 0) {
|
||||
data.alertHours = data.alertHours / 24;
|
||||
} else {
|
||||
data.alertHours = (data.alertHours / 24).toFixed(2);
|
||||
}
|
||||
modalState.passwdExpire = data;
|
||||
modalState.passwdExpireEnable = data.expHours > 0;
|
||||
} catch (error) {
|
||||
console.error('passwdExpire', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**组件实例被卸载之后调用 */
|
||||
onUnmounted(() => {});
|
||||
</script>
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:open="modalState.open"
|
||||
get-container="#app"
|
||||
:footer="null"
|
||||
:zIndex="1008"
|
||||
:closable="false"
|
||||
:keyboard="false"
|
||||
:destroyOnClose="true"
|
||||
:mask-closable="false"
|
||||
:title="modalState.title"
|
||||
>
|
||||
<a-form
|
||||
name="modalStateFromByResetPwd"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 6 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-form-item
|
||||
:label="t('views.system.user.account')"
|
||||
name="userName"
|
||||
v-bind="modalStateFrom.validateInfos.userName"
|
||||
>
|
||||
<a-input :value="modalState.from.userName" disabled :maxlength="30">
|
||||
<template #prefix>
|
||||
<UserOutlined />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('views.system.user.loginPwd')"
|
||||
name="password"
|
||||
v-bind="modalStateFrom.validateInfos.password"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="modalState.from.password"
|
||||
:disabled="modalState.confirmLoading"
|
||||
:maxlength="26"
|
||||
>
|
||||
<template #prefix>
|
||||
<LockOutlined />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-form-item name="ok" :wrapper-col="{ offset: 6 }">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="fnModalOk()"
|
||||
:disabled="modalState.confirmLoading"
|
||||
>
|
||||
{{ t('common.ok') }}
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<a-form-item name="info" :label="t('components.ForcePasswdChange.desc')">
|
||||
<div>
|
||||
<p>
|
||||
{{ t('components.ForcePasswdChange.passwordPolicy') }}<br />
|
||||
{{
|
||||
t(
|
||||
'components.ForcePasswdChange.passwordPolicyMsg',
|
||||
modalState.passwordPolicy
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<p v-if="modalState.passwdExpireEnable">
|
||||
{{ t('components.ForcePasswdChange.passwdExpire') }}<br />
|
||||
{{
|
||||
t('components.ForcePasswdChange.passwdExpireMsg', {
|
||||
expDay: modalState.passwdExpire.expHours,
|
||||
alertDay: modalState.passwdExpire.alertHours,
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -1,14 +1,14 @@
|
||||
/**响应-code加密数据 */
|
||||
export const RESULT_CODE_ENCRYPT = 2;
|
||||
export const RESULT_CODE_ENCRYPT = 200999;
|
||||
|
||||
/**响应-msg加密数据 */
|
||||
export const RESULT_MSG_ENCRYPT: Record<string, string> = {
|
||||
zh_CN: '加密!',
|
||||
en_US: 'encrypt!',
|
||||
en_US: 'Encrypt!',
|
||||
};
|
||||
|
||||
/**响应-code正常成功 */
|
||||
export const RESULT_CODE_SUCCESS = 1;
|
||||
export const RESULT_CODE_SUCCESS = 200001;
|
||||
|
||||
/**响应-msg正常成功 */
|
||||
export const RESULT_MSG_SUCCESS: Record<string, string> = {
|
||||
@@ -17,7 +17,16 @@ export const RESULT_MSG_SUCCESS: Record<string, string> = {
|
||||
};
|
||||
|
||||
/**响应-code错误失败 */
|
||||
export const RESULT_CODE_ERROR = 0;
|
||||
export const RESULT_CODE_ERROR = 400001;
|
||||
|
||||
/**响应-code错误异常 */
|
||||
export const RESULT_CODE_EXCEPTION = 500001;
|
||||
|
||||
/**响应-服务器连接出错 */
|
||||
export const RESULT_MSG_SERVER_ERROR: Record<string, string> = {
|
||||
zh_CN: '服务器连接出错!',
|
||||
en_US: 'Server Connection Error!',
|
||||
};
|
||||
|
||||
/**响应-msg错误失败 */
|
||||
export const RESULT_MSG_ERROR: Record<string, string> = {
|
||||
@@ -37,18 +46,6 @@ export const RESULT_MSG_NOT_TYPE: Record<string, string> = {
|
||||
en_US: 'Unknown Response Data Type!',
|
||||
};
|
||||
|
||||
/**响应-服务器连接出错 */
|
||||
export const RESULT_MSG_SERVER_ERROR: Record<string, string> = {
|
||||
zh_CN: '服务器连接出错!',
|
||||
en_US: 'Server Connection Error!',
|
||||
};
|
||||
|
||||
/**响应-请求地址未找到 */
|
||||
export const RESULT_MSG_URL_NOTFOUND: Record<string, string> = {
|
||||
zh_CN: '请求地址未找到!',
|
||||
en_US: 'Request Address Not Found!',
|
||||
};
|
||||
|
||||
/**响应-数据正在处理,请勿重复提交 */
|
||||
export const RESULT_MSG_URL_RESUBMIT: Record<string, string> = {
|
||||
zh_CN: '数据正在处理,请勿重复提交!',
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
export const TOKEN_RESPONSE_FIELD = 'accessToken';
|
||||
|
||||
/**令牌-请求头标识前缀 */
|
||||
export const TOKEN_KEY_PREFIX = 'Bearer ';
|
||||
export const TOKEN_KEY_PREFIX = 'Bearer';
|
||||
|
||||
/**令牌-请求头标识 */
|
||||
export const TOKEN_KEY = 'Authorization';
|
||||
|
||||
/**令牌-存放Cookie标识 */
|
||||
export const TOKEN_COOKIE = 'AuthOMC';
|
||||
/**令牌-访问令牌存放Cookie标识 */
|
||||
export const TOKEN_ACCESS_COOKIE = 'omc_access';
|
||||
|
||||
/**令牌-刷新令牌存放Cookie标识 */
|
||||
export const TOKEN_REFRESH_COOKIE = 'omc_refresh';
|
||||
|
||||
@@ -16,7 +16,6 @@ export default {
|
||||
errorFields: 'Please fill in the required information in {num} correctly!',
|
||||
tablePaginationTotal: 'Total {total} items',
|
||||
noData: "No Data",
|
||||
zebra:'Tabular zebra pattern',
|
||||
ok: 'Ok',
|
||||
cancel: 'Cancel',
|
||||
close: 'Close',
|
||||
@@ -131,7 +130,7 @@ export default {
|
||||
},
|
||||
LockScreen: {
|
||||
inputPlacePwd:'Lock Screen Password',
|
||||
validSucc:'Validation Passed',
|
||||
enter:'Enter',
|
||||
validError:'Validation Failure',
|
||||
backLogin:'Logout to Relogin',
|
||||
backReload:'Restarting now, please wait...',
|
||||
@@ -139,6 +138,14 @@ export default {
|
||||
systemReset:'Resetting now, please wait...',
|
||||
systemReset2:'Data information is being reset.',
|
||||
},
|
||||
ForcePasswdChange: {
|
||||
title: 'Password Change',
|
||||
desc: 'Instruction',
|
||||
passwordPolicy: 'Password policy strength',
|
||||
passwordPolicyMsg: 'At least {minLength} bits, containing at least {specialChars} special characters and at least {uppercase} uppercase and at least {lowercase} lowercase letters.',
|
||||
passwdExpire: 'Password expiration date',
|
||||
passwdExpireMsg: 'Valid for {expDay} days, please change your password {alertDay} days before expiration.',
|
||||
},
|
||||
},
|
||||
|
||||
// 静态路由
|
||||
@@ -163,7 +170,7 @@ export default {
|
||||
userNameReg: 'The account cannot start with a number and can contain uppercase and lowercase letters, numbers, and no less than 5 digits.',
|
||||
userNamePlease: 'Please enter the correct login account',
|
||||
userNameHit: 'Login account',
|
||||
passwordReg: 'The password should contain at least uppercase and lowercase letters, numbers, special symbols, and no less than 6 digits.',
|
||||
passwordReg: 'Please enter the correct password format',
|
||||
passwordPlease: 'Please enter the correct login password',
|
||||
passwordHit: 'Login password',
|
||||
passwordConfirmHit: 'Confirm login password',
|
||||
@@ -295,7 +302,7 @@ export default {
|
||||
oldPasswordTip: "The old password must not be empty and must be at least 6 digits long",
|
||||
oldPasswordPleace: "Please enter the old password",
|
||||
newPassword: "New Password",
|
||||
newPasswordTip: "Password contains at least upper and lower case letters, numbers, special symbols, and not less than 6 digits",
|
||||
newPasswordTip: "Please enter the correct password format",
|
||||
newPassworddPleace: "Please enter a new password",
|
||||
confirmPassword: "Confirm new password",
|
||||
confirmPasswordPleace: "Please confirm the new password",
|
||||
@@ -506,7 +513,7 @@ export default {
|
||||
delTip: 'Confirm deletion of network element information data items?',
|
||||
oam: {
|
||||
title: 'OAM Configuration',
|
||||
sync: 'Sync to NE',
|
||||
restart: 'Restart NE',
|
||||
oamEnable: 'Service',
|
||||
oamPort: 'Port',
|
||||
snmpEnable: 'Service',
|
||||
@@ -659,6 +666,19 @@ export default {
|
||||
name: "Name",
|
||||
downTip: 'Confirmed to download the backup file [{txt}]?',
|
||||
title: "Modify Backup {txt}",
|
||||
backupModal: {
|
||||
pushFileOper: "Send Current File To Remote Backup",
|
||||
title: "Setting Remote Backup Service",
|
||||
enable: "Enable",
|
||||
toIp: "Service IP",
|
||||
toIpPleace: "Please input the remote backup server IP address",
|
||||
toPort: "Service Port",
|
||||
username: "UserName",
|
||||
usernamePleace: 'Please enter the service login username',
|
||||
password: "Password",
|
||||
dir: "Save Dir",
|
||||
dirPleace: 'Please enter the service address target file directory',
|
||||
}
|
||||
},
|
||||
neQuickSetup: {
|
||||
reloadPara5G: 'Reload',
|
||||
@@ -711,8 +731,45 @@ export default {
|
||||
},
|
||||
neData: {
|
||||
common: {
|
||||
startIMSI: 'Starting IMSI',
|
||||
imsi: 'IMSI',
|
||||
imsiTip: 'IMSI=MCC+MNC+MSIN',
|
||||
imsiTip1: 'MCC=Mobile Country Code, consisting of three digits.',
|
||||
imsiTip2: 'MNC = Mobile Network Number, consisting of two digits',
|
||||
imsiTip3: 'MSIN = Mobile Subscriber Identification Number, consisting of 10 equal digits.',
|
||||
imsiPlease: "Please enter IMSI correctly",
|
||||
msisdn: 'Mobile Customer Identification Number',
|
||||
msisdnPlease: "Please enter the Mobile Customer Identification Number correctly",
|
||||
loadDataConfirm: 'Confirmed to reload data?',
|
||||
loadData: 'Load Data',
|
||||
loadDataTip: 'Successfully fetched loaded data: {num} items, the system is internally updating the data, it will take about {timer} seconds, please wait!!!!!.',
|
||||
batchOper: 'Batch Operation',
|
||||
batchAddText: 'Batch Addition',
|
||||
batchDelText: 'Batch Deletion',
|
||||
batchUpdateText: 'Batch Update',
|
||||
batchNum: 'Number of releases',
|
||||
checkDel:'Check Delete',
|
||||
importTemplate: 'Download Template',
|
||||
},
|
||||
udmVOIP: {
|
||||
startUsername: 'Starting username',
|
||||
username: 'username',
|
||||
usernamePlease: "Please enter your username correctly",
|
||||
password: "password",
|
||||
passwordPlease: "Please enter your password correctly",
|
||||
addTitle: 'Add VOIP subscriber',
|
||||
delTip: 'Confirm that you want to delete the information of VOIP user as [{num}]?',
|
||||
exportTip: "Confirm exporting xlsx table files based on search criteria?",
|
||||
},
|
||||
udmVolteIMS: {
|
||||
startMSISDN: 'Starting MSISDN',
|
||||
voipTip: 'When VoIP is selected MSISDN will be equal to IMSI',
|
||||
addTitle: 'Addition of new IMS subscribers',
|
||||
vniTip: 'Example: ims.mnc000.mcc000.3gppnetwork.org',
|
||||
vniPlease: 'Please enter VNI correctly',
|
||||
delTip: 'Are you sure you want to delete the information of IMS signing as [{num}]?',
|
||||
exportTip: "Confirm exporting xlsx table files based on search criteria?",
|
||||
},
|
||||
baseStation: {
|
||||
list: "List",
|
||||
topology: "Topology",
|
||||
@@ -735,6 +792,12 @@ export default {
|
||||
exportTip: "Confirm exporting xlsx table files based on search criteria?",
|
||||
importDataEmpty: "Imported data is empty",
|
||||
},
|
||||
backupData: {
|
||||
auth: "UDM Authentication",
|
||||
sub: "UDM Subscribers",
|
||||
voip: "VoIP Authentication",
|
||||
volte: "IMS Subscribers",
|
||||
}
|
||||
},
|
||||
neUser: {
|
||||
auth: {
|
||||
@@ -1004,25 +1067,6 @@ export default {
|
||||
},
|
||||
},
|
||||
traceManage: {
|
||||
analysis: {
|
||||
imsi: 'IMSI',
|
||||
imsiPlease: 'Please enter IMSI',
|
||||
msisdn: 'MSISDN',
|
||||
msisdnPlease: 'Please enter MSISDN',
|
||||
trackTaskId: 'Task ID',
|
||||
srcIp: 'Source IP Address',
|
||||
dstIp: 'Destination IP Address',
|
||||
signalType: 'Signaling Type',
|
||||
msgDirect: 'Message Direction',
|
||||
msgType: 'Message Type',
|
||||
rowTime: 'Record Time',
|
||||
signalData: 'Signaling Data',
|
||||
signalDetail: 'Signaling Details',
|
||||
noData: 'No information content',
|
||||
taskTitle: 'Task {num}',
|
||||
taskDownText: 'Download HTML',
|
||||
taskDownTip: 'Confirm downloading the signaling details HTML file?',
|
||||
},
|
||||
pcap: {
|
||||
capArgPlease: 'Please enter tcpdump -i any support parameter',
|
||||
cmd: 'Command',
|
||||
@@ -1073,30 +1117,35 @@ export default {
|
||||
imsiTip: 'Mobile communication IMSI number',
|
||||
srcIp: 'Source IP Address',
|
||||
srcIpPlease: 'Please enter the IP address',
|
||||
srcIpTip: 'Current sender IPv4 address',
|
||||
srcIpTip: 'sending IPv4 address',
|
||||
dstIp: 'Destination IP Address',
|
||||
dstIpPlease: 'Please enter the IP address',
|
||||
dstIpTip: 'IPv4 address of the receiving end of the other party',
|
||||
dstIpTip: 'receiving end IPv4 address',
|
||||
interfaces: 'Signaling Interface',
|
||||
interfacesPlease: 'Please enter the signaling interface',
|
||||
signalPort: 'Signal Port',
|
||||
signalPortPlease: 'Please enter the signaling port',
|
||||
signalPortTip: 'Port of the side corresponding to the destination IP address or source IP address',
|
||||
rangePicker: 'Start/End Time',
|
||||
rangePicker: 'Task Time',
|
||||
rangePickerPlease: 'Please select the start and end time of the task',
|
||||
remark: 'Remark',
|
||||
remarkPlease: 'Task description can be entered',
|
||||
addTask: 'Add Task',
|
||||
editTask: 'Modify Task',
|
||||
viewTask: 'View Task',
|
||||
errorTaskInfo: 'Failed to obtain task information',
|
||||
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} ?',
|
||||
pcapView: "Tracking Data Analysis",
|
||||
traceFile: "Tracking File",
|
||||
pcapView: "Track Data Analysis",
|
||||
traceFile: "Track File",
|
||||
errMsg: "Error Message",
|
||||
imsiORmsisdn: "imsi or msisdn is null, cannot start task",
|
||||
dataView: "Track Data",
|
||||
protocolOrInterface: "Protocol/Interface",
|
||||
msgNe: 'Network Element',
|
||||
msgEvent: 'Event',
|
||||
msgType: 'Type',
|
||||
msgDirect: 'Direction',
|
||||
msgLen: 'Length',
|
||||
rowTime: 'Time',
|
||||
taskInfo: 'Task information',
|
||||
},
|
||||
},
|
||||
faultManage: {
|
||||
@@ -1141,13 +1190,11 @@ export default {
|
||||
delSuss:'Clear successfully',
|
||||
delSure:'Whether to clear this alarm',
|
||||
showSet:'Show filter settings',
|
||||
exportTip: "Confirm exporting xlsx table files based on search criteria?",
|
||||
exportSure:'Confirm whether to export all active alarm information',
|
||||
viewIdInfo:'View {alarmId} record information',
|
||||
closeModal:'Close',
|
||||
},
|
||||
historyAlarm:{
|
||||
exportSure:'Confirm whether to export all historical alarm information',
|
||||
},
|
||||
faultSetting:{
|
||||
interfaceType:'Type',
|
||||
email:'Email',
|
||||
@@ -1216,12 +1263,17 @@ export default {
|
||||
tailLines: 'End Lines',
|
||||
},
|
||||
exportFile:{
|
||||
fileName:'File Source',
|
||||
fileSource:'File Source',
|
||||
fileSourcePlease:'Please select the source of the document',
|
||||
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",
|
||||
operateLog:'Operation Log',
|
||||
cdrIMS:'Voice CDR',
|
||||
cdrSMF:'Data CDR',
|
||||
cdrSMSC:'SMS CDR',
|
||||
cdrSGWC:'Roaming Data CDR',
|
||||
}
|
||||
},
|
||||
monitor: {
|
||||
@@ -1599,7 +1651,7 @@ export default {
|
||||
loginTime: 'Login Time',
|
||||
status: 'Status',
|
||||
userNameTip:'The account number can only contain strings of uppercase letters, lowercase letters and numbers with a minimum length of 6 digits',
|
||||
passwdTip:'The password should contain at least uppercase and lowercase letters, numbers, special symbols, and no less than 6 digits',
|
||||
passwdTip:'Please enter the correct password format',
|
||||
nickNameTip:'Nicknames no less than 2 digits',
|
||||
emailTip:'Please enter the correct email address',
|
||||
phoneTip:'Please enter the correct phone number',
|
||||
|
||||
@@ -16,7 +16,6 @@ export default {
|
||||
errorFields: '请正确填写 {num} 处必填信息!',
|
||||
tablePaginationTotal: '总共 {total} 条',
|
||||
noData: "暂无数据",
|
||||
zebra:'表格斑马纹',
|
||||
ok: '确定',
|
||||
cancel: '取消',
|
||||
close: '关闭',
|
||||
@@ -131,7 +130,7 @@ export default {
|
||||
},
|
||||
LockScreen: {
|
||||
inputPlacePwd:'请输入锁屏密码',
|
||||
validSucc:'校验通过',
|
||||
enter:'进入',
|
||||
validError:'校验失败',
|
||||
backLogin:'退出并重新登录',
|
||||
backReload:'正在重启,请稍等...',
|
||||
@@ -139,6 +138,14 @@ export default {
|
||||
systemReset:'正在重置,请稍等...',
|
||||
systemReset2:'数据信息正在重置',
|
||||
},
|
||||
ForcePasswdChange: {
|
||||
title: '密码修改',
|
||||
desc: '说明',
|
||||
passwordPolicy: '密码策略强度',
|
||||
passwordPolicyMsg: '至少{minLength}位,至少包含{specialChars}个特殊字符和至少{uppercase}大写字母和至少{lowercase}小写字母',
|
||||
passwdExpire: '密码有效期',
|
||||
passwdExpireMsg: '有效期为{expDay}天,请在过期前{alertDay}天进行密码修改',
|
||||
},
|
||||
},
|
||||
|
||||
// 静态路由
|
||||
@@ -163,7 +170,7 @@ export default {
|
||||
userNameReg: '账号不能以数字开头,可包含大写小写字母,数字,且不少于5位',
|
||||
userNamePlease: '请输入正确登录账号',
|
||||
userNameHit: '登录账号',
|
||||
passwordReg: '密码至少包含大小写字母、数字、特殊符号,且不少于6位',
|
||||
passwordReg: '请输入正确的密码格式',
|
||||
passwordPlease: '请输入正确登录密码',
|
||||
passwordHit: '登录密码',
|
||||
passwordConfirmHit: '确认登录密码',
|
||||
@@ -295,7 +302,7 @@ export default {
|
||||
oldPasswordTip: "旧密码不能为空,且不少于6位",
|
||||
oldPasswordPleace: "请输入旧密码",
|
||||
newPassword: "新密码",
|
||||
newPasswordTip: "密码至少包含大小写字母、数字、特殊符号,且不少于6位",
|
||||
newPasswordTip: "请输入正确的密码格式",
|
||||
newPassworddPleace: "请输入新密码",
|
||||
confirmPassword: "确认新密码",
|
||||
confirmPasswordPleace: "请确认新密码",
|
||||
@@ -506,7 +513,7 @@ export default {
|
||||
delTip: '确认删除网元信息数据项吗?',
|
||||
oam: {
|
||||
title: 'OAM配置',
|
||||
sync: '同步到网元',
|
||||
restart: '下发后重启网元',
|
||||
oamEnable: '服务',
|
||||
oamPort: '端口',
|
||||
snmpEnable: '服务',
|
||||
@@ -659,6 +666,19 @@ export default {
|
||||
name: "名称",
|
||||
downTip: '确认要下载备份文件【{txt}】吗?',
|
||||
title: "修改备份信息 {txt}",
|
||||
backupModal: {
|
||||
pushFileOper: "将当前文件发送到远程备份",
|
||||
title: "设置远程备份服务",
|
||||
enable: "启用",
|
||||
toIp: "服务IP",
|
||||
toIpPleace: "请输入远程备份服务器 IP 地址",
|
||||
toPort: "服务端口",
|
||||
username: "登录用户名",
|
||||
usernamePleace: '请输入服务登录用户名',
|
||||
password: "登录密码",
|
||||
dir: "保存目录",
|
||||
dirPleace: '请输入服务地址目标文件目录',
|
||||
}
|
||||
},
|
||||
neQuickSetup: {
|
||||
reloadPara5G: '刷新',
|
||||
@@ -711,8 +731,45 @@ export default {
|
||||
},
|
||||
neData: {
|
||||
common: {
|
||||
startIMSI: '起始IMSI',
|
||||
imsi: 'IMSI',
|
||||
imsiTip: 'IMSI=MCC+MNC+MSIN',
|
||||
imsiTip1: 'MCC=移动国家号码, 由三位数字组成',
|
||||
imsiTip2: 'MNC=移动网络号,由两位数字组成',
|
||||
imsiTip3: 'MSIN=移动客户识别码,采用等长10位数字构成',
|
||||
imsiPlease: "请正确输入IMSI",
|
||||
msisdn: '移动客户识别码',
|
||||
msisdnPlease: "请正确输入移动客户识别码",
|
||||
loadDataConfirm: '确认要重新加载数据吗?',
|
||||
loadData: '加载数据',
|
||||
loadDataTip: '成功获取加载数据:{num}条,系统内部正在进行数据更新,大约需要{timer}秒,请稍候!!!',
|
||||
batchOper: '批量操作',
|
||||
batchAddText: '批量新增',
|
||||
batchDelText: '批量删除',
|
||||
batchUpdateText: '批量更新',
|
||||
batchNum: '批量个数',
|
||||
checkDel:'勾选删除',
|
||||
importTemplate: '导入模板',
|
||||
},
|
||||
udmVOIP: {
|
||||
startUsername: '起始用户名',
|
||||
username: '用户名',
|
||||
usernamePlease: "请正确输入用户名",
|
||||
password: "密码",
|
||||
passwordPlease: "请正确输入密码",
|
||||
addTitle: '新增VOIP用户',
|
||||
delTip: '确认要删除VOIP用户为【{num}】的信息吗?',
|
||||
exportTip: "确认根据搜索条件导出xlsx表格文件吗?",
|
||||
},
|
||||
udmVolteIMS: {
|
||||
startMSISDN: '起始MSISDN',
|
||||
voipTip: '当选择VoIP时MSISDN会等于IMSI',
|
||||
addTitle: '新增IMS签约用户',
|
||||
vniTip: '示例:ims.mnc000.mcc000.3gppnetwork.org',
|
||||
vniPlease: '请正确输入VNI',
|
||||
delTip: '确认要删除IMS签约为【{num}】的信息吗?',
|
||||
exportTip: "确认根据搜索条件导出xlsx表格文件吗?",
|
||||
},
|
||||
baseStation: {
|
||||
list: "列表",
|
||||
topology: "拓扑图",
|
||||
@@ -735,6 +792,12 @@ export default {
|
||||
exportTip: "确认根据搜索条件导出xlsx表格文件吗?",
|
||||
importDataEmpty: "导入数据为空",
|
||||
},
|
||||
backupData: {
|
||||
auth: "UDM鉴权用户",
|
||||
sub: "UDM签约用户",
|
||||
voip: "VOIP鉴权用户",
|
||||
volte: "IMS签约用户",
|
||||
}
|
||||
},
|
||||
neUser: {
|
||||
auth: {
|
||||
@@ -1004,25 +1067,6 @@ export default {
|
||||
},
|
||||
},
|
||||
traceManage: {
|
||||
analysis: {
|
||||
imsi: 'IMSI',
|
||||
imsiPlease: '请输入IMSI',
|
||||
msisdn: 'MSISDN',
|
||||
msisdnPlease: '请输入MSISDN',
|
||||
trackTaskId: '跟踪任务标记',
|
||||
srcIp: '源IP地址',
|
||||
dstIp: '目标IP地址',
|
||||
signalType: '信令类型',
|
||||
msgDirect: '消息元',
|
||||
msgType: '消息类型',
|
||||
rowTime: '记录时间',
|
||||
signalData: '信令数据',
|
||||
signalDetail: '信令详情',
|
||||
noData: '无信息内容',
|
||||
taskTitle: '任务 {num}',
|
||||
taskDownText: '下载HTML',
|
||||
taskDownTip: '确认下载信令详情HTML文件?',
|
||||
},
|
||||
pcap: {
|
||||
capArgPlease: '请输入tcpdump -i any支持参数',
|
||||
cmd: '命令',
|
||||
@@ -1073,21 +1117,17 @@ export default {
|
||||
imsiTip: '移动通信IMSI编号',
|
||||
srcIp: '源IP地址',
|
||||
srcIpPlease: '请输入源IP地址',
|
||||
srcIpTip: '当前发送端IPv4地址',
|
||||
srcIpTip: '发送端IPv4地址',
|
||||
dstIp: '目标IP地址',
|
||||
dstIpPlease: '请输入目标IP地址',
|
||||
dstIpTip: '对方接收端IPv4地址',
|
||||
dstIpTip: '接收端IPv4地址',
|
||||
interfaces: '信令接口',
|
||||
interfacesPlease: '请输入信令接口',
|
||||
signalPort: '信令端口',
|
||||
signalPortPlease: '请输入信令端口',
|
||||
signalPortTip: '目标IP地址或源IP地址对应一方的端口',
|
||||
rangePicker: '开始结束时间',
|
||||
rangePicker: '任务时间',
|
||||
rangePickerPlease: '请选择任务时间开始结束时间',
|
||||
remark: '说明',
|
||||
remarkPlease: '可输入任务说明',
|
||||
addTask: '添加任务',
|
||||
editTask: '修改任务',
|
||||
viewTask: '查看任务',
|
||||
errorTaskInfo: '获取任务信息失败',
|
||||
delTaskTip: '确认删除记录编号为 {id} 的数据项?',
|
||||
@@ -1097,6 +1137,15 @@ export default {
|
||||
traceFile: "跟踪文件",
|
||||
errMsg: "错误信息",
|
||||
imsiORmsisdn: "imsi 或 msisdn 是空值,不能开始任务",
|
||||
dataView: "跟踪数据",
|
||||
protocolOrInterface: "协议/接口",
|
||||
msgNe: '消息网元',
|
||||
msgEvent: '消息事件',
|
||||
msgType: '消息类型',
|
||||
msgDirect: '消息方向',
|
||||
msgLen: '消息长度',
|
||||
rowTime: '消息时间',
|
||||
taskInfo: '任务信息',
|
||||
},
|
||||
},
|
||||
faultManage: {
|
||||
@@ -1141,13 +1190,11 @@ export default {
|
||||
delSuss:'清除成功',
|
||||
delSure:'是否清除该告警',
|
||||
showSet:'显示过滤设置',
|
||||
exportTip: "确认根据搜索条件导出xlsx表格文件吗?",
|
||||
exportSure:'确认是否导出全部活动告警信息',
|
||||
viewIdInfo:'查看{alarmId} 记录信息',
|
||||
closeModal:'关闭',
|
||||
},
|
||||
historyAlarm:{
|
||||
exportSure:'确认是否导出全部历史告警信息?',
|
||||
},
|
||||
faultSetting:{
|
||||
interfaceType:'类型',
|
||||
email:'Email',
|
||||
@@ -1179,7 +1226,7 @@ export default {
|
||||
type:'网元类型',
|
||||
neId:'网元唯一标识',
|
||||
MML:'MML',
|
||||
logTime:'log Time'
|
||||
logTime:'记录时间'
|
||||
},
|
||||
forwarding:{
|
||||
alarmId:'告警唯一标识',
|
||||
@@ -1216,12 +1263,17 @@ export default {
|
||||
tailLines: '末尾行数',
|
||||
},
|
||||
exportFile:{
|
||||
fileName:'文件来源',
|
||||
fileSource:'文件来源',
|
||||
fileSourcePlease:'请选择文件来源',
|
||||
downTip: "确认下载文件名为 【{fileName}】 文件?",
|
||||
downTipErr: "文件获取失败",
|
||||
deleteTip: "确认删除文件名为 【{fileName}】 文件?",
|
||||
deleteTipErr: "文件删除失败",
|
||||
selectTip:"请选择文件名",
|
||||
operateLog:'操作日志',
|
||||
cdrIMS:'语音话单',
|
||||
cdrSMF:'数据话单',
|
||||
cdrSMSC:'短信话单',
|
||||
cdrSGWC:'漫游数据话单',
|
||||
}
|
||||
},
|
||||
monitor: {
|
||||
@@ -1599,7 +1651,7 @@ export default {
|
||||
loginTime: '登录时间',
|
||||
status: '用户状态',
|
||||
userNameTip:'账号只能包含大写字母、小写字母和数字的字符串,长度至少为6位',
|
||||
passwdTip:'密码至少包含大小写字母、数字、特殊符号,且不少于6位',
|
||||
passwdTip:'请输入正确的密码格式',
|
||||
nickNameTip:'昵称不少于2位',
|
||||
emailTip:'请输入正确的邮箱地址',
|
||||
phoneTip:'请输入正确的手机号码',
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import RightContent from './components/RightContent.vue';
|
||||
import Tabs from './components/Tabs.vue';
|
||||
import GlobalMask from '@/components/GlobalMask/index.vue';
|
||||
import ForcePasswdChange from '@/components/ForcePasswdChange/index.vue';
|
||||
import { scriptUrl } from '@/assets/js/icon_font_8d5l8fzk5b87iudi';
|
||||
import {
|
||||
computed,
|
||||
@@ -362,6 +363,8 @@ onUnmounted(() => {
|
||||
|
||||
<!-- 全局遮罩 -->
|
||||
<GlobalMask />
|
||||
<!-- 强制密码修改 -->
|
||||
<ForcePasswdChange />
|
||||
</a-watermark>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,25 +1,53 @@
|
||||
import Cookies from 'js-cookie';
|
||||
import { TOKEN_COOKIE } from '@/constants/token-constants';
|
||||
import { localRemove, localSet } from '@/utils/cache-local-utils';
|
||||
import {
|
||||
CACHE_LOCAL_LOCK_PASSWD,
|
||||
CACHE_LOCAL_MASK,
|
||||
} from '@/constants/cache-keys-constants';
|
||||
import {
|
||||
TOKEN_ACCESS_COOKIE,
|
||||
TOKEN_REFRESH_COOKIE,
|
||||
} from '@/constants/token-constants';
|
||||
|
||||
/**获取cookis中Token字符串 */
|
||||
export function getToken(): string {
|
||||
return Cookies.get(TOKEN_COOKIE) || '';
|
||||
/**获取访问令牌 */
|
||||
export function getAccessToken(): string {
|
||||
return Cookies.get(TOKEN_ACCESS_COOKIE) || '';
|
||||
}
|
||||
|
||||
/**设置cookis中Token字符串 */
|
||||
export function setToken(token: string): void {
|
||||
Cookies.set(TOKEN_COOKIE, token || '');
|
||||
/**
|
||||
* 设置访问令牌
|
||||
* @param token token字符串
|
||||
* @param exp 过期时间(秒)
|
||||
*/
|
||||
export function setAccessToken(token: string, exp: number): void {
|
||||
const expires = new Date(new Date().getTime() + exp * 1000);
|
||||
Cookies.set(TOKEN_ACCESS_COOKIE, token, { expires });
|
||||
localSet(CACHE_LOCAL_MASK, 'none');
|
||||
}
|
||||
|
||||
/**移除cookis中Token字符串,localStorage中锁屏字符串 */
|
||||
export function removeToken(): void {
|
||||
Cookies.remove(TOKEN_COOKIE);
|
||||
/**移除访问令牌 */
|
||||
export function delAccessToken(): void {
|
||||
Cookies.remove(TOKEN_ACCESS_COOKIE);
|
||||
localRemove(CACHE_LOCAL_MASK);
|
||||
localRemove(CACHE_LOCAL_LOCK_PASSWD);
|
||||
}
|
||||
|
||||
/**获取刷新令牌 */
|
||||
export function getRefreshToken(): string {
|
||||
return Cookies.get(TOKEN_REFRESH_COOKIE) || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置刷新令牌
|
||||
* @param token token字符串
|
||||
* @param exp 过期时间(秒)
|
||||
*/
|
||||
export function setRefreshToken(token: string, exp: number): void {
|
||||
const expires = new Date(new Date().getTime() + exp * 1000);
|
||||
Cookies.set(TOKEN_REFRESH_COOKIE, token, { expires });
|
||||
}
|
||||
|
||||
/**移除刷新令牌 */
|
||||
export function delRefreshToken(): void {
|
||||
Cookies.remove(TOKEN_REFRESH_COOKIE);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import { getToken, removeToken } from '@/plugins/auth-token';
|
||||
import {
|
||||
getAccessToken,
|
||||
setAccessToken,
|
||||
delAccessToken,
|
||||
getRefreshToken,
|
||||
setRefreshToken,
|
||||
delRefreshToken,
|
||||
} from '@/plugins/auth-token';
|
||||
import {
|
||||
sessionGet,
|
||||
sessionGetJSON,
|
||||
sessionSetJSON,
|
||||
} from '@/utils/cache-session-utils';
|
||||
import { localGet } from '@/utils/cache-local-utils';
|
||||
import { TOKEN_KEY, TOKEN_KEY_PREFIX } from '@/constants/token-constants';
|
||||
import {
|
||||
CACHE_LOCAL_I18N,
|
||||
@@ -18,6 +24,7 @@ import {
|
||||
import {
|
||||
RESULT_CODE_ENCRYPT,
|
||||
RESULT_CODE_ERROR,
|
||||
RESULT_CODE_EXCEPTION,
|
||||
RESULT_CODE_SUCCESS,
|
||||
RESULT_MSG_ENCRYPT,
|
||||
RESULT_MSG_ERROR,
|
||||
@@ -25,10 +32,11 @@ import {
|
||||
RESULT_MSG_SERVER_ERROR,
|
||||
RESULT_MSG_SUCCESS,
|
||||
RESULT_MSG_TIMEOUT,
|
||||
RESULT_MSG_URL_NOTFOUND,
|
||||
RESULT_MSG_URL_RESUBMIT,
|
||||
} from '@/constants/result-constants';
|
||||
import { decryptAES, encryptAES } from '@/utils/encrypt-utils';
|
||||
import { localGet } from '@/utils/cache-local-utils';
|
||||
import { refreshToken } from '@/api/auth';
|
||||
|
||||
/**响应结果类型 */
|
||||
export type ResultType = {
|
||||
@@ -61,7 +69,7 @@ type OptionsType = {
|
||||
/**请求地址 */
|
||||
url: string;
|
||||
/**请求方法 */
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
||||
/**请求头 */
|
||||
headers?: HeadersInit;
|
||||
/**地址栏参数 */
|
||||
@@ -133,16 +141,21 @@ function beforeRequest(options: OptionsType): OptionsType | Promise<any> {
|
||||
Reflect.set(options.headers, 'Accept-Language', `${language};q=0.9`);
|
||||
|
||||
// 是否需要设置 token
|
||||
const token = getToken();
|
||||
if (options.whithToken && token) {
|
||||
Reflect.set(options.headers, TOKEN_KEY, TOKEN_KEY_PREFIX + token);
|
||||
const accessToken = getAccessToken();
|
||||
if (options.whithToken && accessToken) {
|
||||
Reflect.set(
|
||||
options.headers,
|
||||
TOKEN_KEY,
|
||||
TOKEN_KEY_PREFIX + ' ' + accessToken
|
||||
);
|
||||
}
|
||||
|
||||
// 是否需要防止数据重复提交
|
||||
if (
|
||||
options.repeatSubmit &&
|
||||
options.dataType === 'json' &&
|
||||
['post', 'put'].includes(options.method)
|
||||
!(options.data instanceof FormData) &&
|
||||
['POST', 'PUT'].includes(options.method)
|
||||
) {
|
||||
const requestObj: RepeatSubmitType = {
|
||||
url: options.url,
|
||||
@@ -212,13 +225,31 @@ function beforeRequest(options: OptionsType): OptionsType | Promise<any> {
|
||||
return options;
|
||||
}
|
||||
|
||||
/**请求后的拦截 */
|
||||
function interceptorResponse(res: ResultType): ResultType | Promise<any> {
|
||||
/**响应前的拦截 */
|
||||
async function beforeResponse(
|
||||
options: OptionsType,
|
||||
res: ResultType
|
||||
): Promise<any> {
|
||||
// console.log('请求后的拦截', res);
|
||||
|
||||
// 登录失效时,移除授权令牌并重新刷新页面
|
||||
if (res.code === 401) {
|
||||
removeToken();
|
||||
// 登录失效时,移除访问令牌并重新请求
|
||||
if (res.code === 401001) {
|
||||
const result = await refreshToken(getRefreshToken());
|
||||
// 更新访问令牌和刷新令牌
|
||||
if (result.code === RESULT_CODE_SUCCESS) {
|
||||
setAccessToken(result.data.accessToken, result.data.refreshExpiresIn);
|
||||
setRefreshToken(result.data.refreshToken, result.data.refreshExpiresIn);
|
||||
return await request(options);
|
||||
} else {
|
||||
delAccessToken();
|
||||
delRefreshToken();
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
if ([401002, 401003].includes(res.code)) {
|
||||
delAccessToken();
|
||||
delRefreshToken();
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
@@ -271,43 +302,45 @@ function interceptorResponse(res: ResultType): ResultType | Promise<any> {
|
||||
* @returns 返回 Promise<ResultType>
|
||||
*/
|
||||
export async function request(options: OptionsType): Promise<ResultType> {
|
||||
options = Object.assign({}, FATCH_OPTIONS, options);
|
||||
let timeoutId: any = 0;
|
||||
let reqOptions = Object.assign({}, FATCH_OPTIONS, options);
|
||||
|
||||
// 请求超时控制请求终止
|
||||
if (!options.signal) {
|
||||
let timeoutId: any = null;
|
||||
if (!reqOptions.signal) {
|
||||
const controller = new AbortController();
|
||||
const { signal } = controller;
|
||||
options.signal = signal;
|
||||
reqOptions.signal = controller.signal;
|
||||
timeoutId = setTimeout(() => {
|
||||
controller.abort(); // 终止请求
|
||||
}, options.timeout);
|
||||
}, reqOptions.timeout);
|
||||
}
|
||||
|
||||
// 检查请求拦截
|
||||
const beforeReq = beforeRequest(options);
|
||||
const beforeReq = beforeRequest(reqOptions);
|
||||
if (beforeReq instanceof Promise) {
|
||||
return await beforeReq;
|
||||
}
|
||||
options = beforeReq;
|
||||
reqOptions = beforeReq;
|
||||
|
||||
// 判断用户传递的URL是否http或/开头
|
||||
if (!options.url.startsWith('http')) {
|
||||
const uri = options.url.startsWith('/') ? options.url : `/${options.url}`;
|
||||
options.url = options.baseUrl + uri;
|
||||
if (!reqOptions.url.startsWith('http')) {
|
||||
const uri = reqOptions.url.startsWith('/')
|
||||
? reqOptions.url
|
||||
: `/${reqOptions.url}`;
|
||||
reqOptions.url = reqOptions.baseUrl + uri;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(options.url, options);
|
||||
const res = await fetch(reqOptions.url, reqOptions);
|
||||
// console.log('请求结果:', res);
|
||||
|
||||
// 状态码拦截处理
|
||||
const reqNot = stateCode(res);
|
||||
if (reqNot != false) {
|
||||
return reqNot;
|
||||
if (res.status === 500) {
|
||||
return {
|
||||
code: RESULT_CODE_EXCEPTION,
|
||||
msg: RESULT_MSG_SERVER_ERROR[language],
|
||||
};
|
||||
}
|
||||
|
||||
// 根据响应数据类型返回
|
||||
switch (options.responseType) {
|
||||
switch (reqOptions.responseType) {
|
||||
case 'text': // 文本数据
|
||||
const str = await res.text();
|
||||
return {
|
||||
@@ -317,11 +350,7 @@ export async function request(options: OptionsType): Promise<ResultType> {
|
||||
case 'json': // json格式数据
|
||||
const result = await res.json();
|
||||
// 请求后的拦截
|
||||
const beforeRes = interceptorResponse(result);
|
||||
if (beforeRes instanceof Promise) {
|
||||
return await beforeRes;
|
||||
}
|
||||
return result;
|
||||
return await beforeResponse(options, result);
|
||||
case 'blob': // 二进制数据则直接返回
|
||||
case 'arrayBuffer':
|
||||
const contentType = res.headers.get('content-type') || '';
|
||||
@@ -330,7 +359,7 @@ export async function request(options: OptionsType): Promise<ResultType> {
|
||||
return result as ResultType;
|
||||
}
|
||||
const data =
|
||||
options.responseType === 'blob'
|
||||
reqOptions.responseType === 'blob'
|
||||
? await res.blob()
|
||||
: await res.arrayBuffer();
|
||||
return {
|
||||
@@ -356,41 +385,7 @@ export async function request(options: OptionsType): Promise<ResultType> {
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
clearTimeout(timeoutId); // 请求成功,清除超时计时器
|
||||
clearTimeout(timeoutId); // 清除超时计时器
|
||||
timeoutId = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断状态码处理结果信息(不可处理)
|
||||
* @param res 请求结果
|
||||
* @returns
|
||||
*/
|
||||
function stateCode(res: Response) {
|
||||
// 网络异常
|
||||
if (res.status === 500) {
|
||||
return {
|
||||
code: RESULT_CODE_ERROR,
|
||||
msg: RESULT_MSG_SERVER_ERROR[language],
|
||||
};
|
||||
}
|
||||
// 上传文件成功无内容返回
|
||||
if (res.status === 204 || res.statusText === 'No Content') {
|
||||
return {
|
||||
code: RESULT_CODE_SUCCESS,
|
||||
msg: RESULT_MSG_SUCCESS[language],
|
||||
};
|
||||
}
|
||||
// 地址找不到
|
||||
if (res.status === 404 || res.status === 405) {
|
||||
return {
|
||||
code: RESULT_CODE_ERROR,
|
||||
msg: RESULT_MSG_URL_NOTFOUND[language],
|
||||
};
|
||||
}
|
||||
// 身份授权
|
||||
if (res.status === 401) {
|
||||
removeToken();
|
||||
window.location.reload();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { sessionGet } from '@/utils/cache-session-utils';
|
||||
import { getToken } from './auth-token';
|
||||
import { getAccessToken } from './auth-token';
|
||||
import { localGet } from '@/utils/cache-local-utils';
|
||||
import { CACHE_LOCAL_I18N } from '@/constants/cache-keys-constants';
|
||||
import { TOKEN_RESPONSE_FIELD } from '@/constants/token-constants';
|
||||
@@ -92,7 +92,7 @@ export class WS {
|
||||
// 地址栏参数
|
||||
let params = Object.assign({}, options.params, {
|
||||
// 设置 token
|
||||
[TOKEN_RESPONSE_FIELD]: getToken(),
|
||||
[TOKEN_RESPONSE_FIELD]: getAccessToken(),
|
||||
// 多语言
|
||||
['language']: localGet(CACHE_LOCAL_I18N) || 'en_US',
|
||||
});
|
||||
@@ -119,17 +119,15 @@ export class WS {
|
||||
};
|
||||
// 用于指定当从服务器接受到信息时的回调函数。
|
||||
ws.onmessage = ev => {
|
||||
if (ev.type !== 'message') return;
|
||||
// 解析文本消息
|
||||
if (ev.type === 'message') {
|
||||
const data = ev.data;
|
||||
try {
|
||||
const jsonData = JSON.parse(data);
|
||||
if (typeof options.onmessage === 'function') {
|
||||
options.onmessage(jsonData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('websocket message formatting error', error);
|
||||
try {
|
||||
const jsonData = JSON.parse(ev.data);
|
||||
if (typeof options.onmessage === 'function') {
|
||||
options.onmessage(jsonData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('websocket message formatting error', error);
|
||||
}
|
||||
};
|
||||
// 用于指定连接关闭后的回调函数。
|
||||
@@ -221,7 +219,7 @@ export class WS {
|
||||
this.heartInterval = window.setInterval(() => {
|
||||
this.send({
|
||||
requestId: `${Date.now()}`,
|
||||
type: 'ping',
|
||||
type: 'PING',
|
||||
});
|
||||
}, heartTimer);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
import BasicLayout from '../layouts/BasicLayout.vue';
|
||||
import BlankLayout from '../layouts/BlankLayout.vue';
|
||||
import { getToken } from '@/plugins/auth-token';
|
||||
import { getAccessToken } from '@/plugins/auth-token';
|
||||
import { validHttp } from '@/utils/regular-utils';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
@@ -182,7 +182,7 @@ router.beforeEach(async (to, from, next) => {
|
||||
// next({ name: 'Index' });
|
||||
// }
|
||||
|
||||
let token = getToken();
|
||||
let token = getAccessToken();
|
||||
|
||||
// 免用户登录认证
|
||||
if (!appStore.loginAuth) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { getSysConf } from '@/api';
|
||||
import { CACHE_LOCAL_I18N, CACHE_SESSION_CRYPTO_API } from '@/constants/cache-keys-constants';
|
||||
import {
|
||||
CACHE_LOCAL_I18N,
|
||||
CACHE_SESSION_CRYPTO_API,
|
||||
} from '@/constants/cache-keys-constants';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
// import { removeToken } from '@/plugins/auth-token';
|
||||
import { parseUrlPath } from '@/plugins/file-static-url';
|
||||
import { localGet, localSet } from '@/utils/cache-local-utils';
|
||||
import { sessionSet } from '@/utils/cache-session-utils';
|
||||
@@ -16,11 +18,12 @@ type AppStore = {
|
||||
/**应用版本 */
|
||||
appVersion: string;
|
||||
|
||||
/**服务版本 */
|
||||
/**版本号 */
|
||||
version: string;
|
||||
// buildTime: string;
|
||||
/**系统引导使用 */
|
||||
// bootloader: boolean;
|
||||
/**服务版本 */
|
||||
serverVersion: string;
|
||||
// 用户登录认证
|
||||
loginAuth: boolean;
|
||||
// 用户接口加密
|
||||
@@ -54,13 +57,13 @@ const useAppStore = defineStore('app', {
|
||||
appCode: import.meta.env.VITE_APP_CODE,
|
||||
appVersion: import.meta.env.VITE_APP_VERSION,
|
||||
|
||||
version: `-`,
|
||||
// buildTime: `-`,
|
||||
version: '-',
|
||||
// bootloader: false,
|
||||
serverVersion: '-',
|
||||
loginAuth: true,
|
||||
cryptoApi: true,
|
||||
serialNum: `-`,
|
||||
copyright: `Copyright ©2023 For ${import.meta.env.VITE_APP_NAME}`,
|
||||
serialNum: '-',
|
||||
copyright: `Copyright ©2023-2025 For ${import.meta.env.VITE_APP_NAME}`,
|
||||
logoType: 'icon',
|
||||
filePathIcon: '',
|
||||
filePathBrand: '',
|
||||
@@ -86,15 +89,16 @@ const useAppStore = defineStore('app', {
|
||||
const res = await getSysConf();
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
this.version = res.data.version;
|
||||
// this.buildTime = res.data.buildTime;
|
||||
this.serverVersion = res.data.serverVersion;
|
||||
// this.bootloader = res.data.bootloader === 'true';
|
||||
// // 引导时
|
||||
// if (this.bootloader) {
|
||||
// removeToken();
|
||||
// delAccessToken();
|
||||
// delRefreshToken();
|
||||
// }
|
||||
this.loginAuth = res.data.loginAuth !== 'false';
|
||||
this.cryptoApi = res.data.cryptoApi !== 'false';
|
||||
sessionSet(CACHE_SESSION_CRYPTO_API, res.data.cryptoApi);
|
||||
sessionSet(CACHE_SESSION_CRYPTO_API, res.data.cryptoApi);
|
||||
this.serialNum = res.data.serialNum;
|
||||
this.appName = res.data.title;
|
||||
this.copyright = res.data.copyright;
|
||||
|
||||
@@ -20,7 +20,7 @@ type MaskStateType = {
|
||||
const useMaskStore = defineStore('mask', {
|
||||
state: (): MaskStateType => ({
|
||||
type: (localGet(CACHE_LOCAL_MASK) || 'none') as MaskStateType['type'],
|
||||
lockPasswd: localGet(CACHE_LOCAL_LOCK_PASSWD) || '',
|
||||
lockPasswd: atob(localGet(CACHE_LOCAL_LOCK_PASSWD) || ''),
|
||||
lockTimeout: 0,
|
||||
}),
|
||||
getters: {},
|
||||
@@ -59,7 +59,7 @@ const useMaskStore = defineStore('mask', {
|
||||
}, 5_000);
|
||||
}
|
||||
if (type === 'lock') {
|
||||
localSet(CACHE_LOCAL_LOCK_PASSWD, this.lockPasswd);
|
||||
localSet(CACHE_LOCAL_LOCK_PASSWD, btoa(this.lockPasswd));
|
||||
} else {
|
||||
localRemove(CACHE_LOCAL_LOCK_PASSWD);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ 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/trace/task';
|
||||
import { getNePerformanceList } from '@/api/perfManage/taskManage';
|
||||
|
||||
/**网元信息类型 */
|
||||
@@ -13,8 +12,6 @@ type NeInfo = {
|
||||
neCascaderOptions: Record<string, any>[];
|
||||
/**选择器单级父类型 */
|
||||
neSelectOtions: Record<string, any>[];
|
||||
/**跟踪接口列表 */
|
||||
traceInterfaceList: Record<string, any>[];
|
||||
/**性能测量数据集 */
|
||||
perMeasurementList: Record<string, any>[];
|
||||
};
|
||||
@@ -24,7 +21,6 @@ const useNeInfoStore = defineStore('neinfo', {
|
||||
neList: [],
|
||||
neCascaderOptions: [],
|
||||
neSelectOtions: [],
|
||||
traceInterfaceList: [],
|
||||
perMeasurementList: [],
|
||||
}),
|
||||
getters: {
|
||||
@@ -56,7 +52,7 @@ const useNeInfoStore = defineStore('neinfo', {
|
||||
async fnNelist() {
|
||||
// 有数据不请求
|
||||
if (this.neList.length > 0) {
|
||||
return { code: 1, data: this.neList, msg: 'success' };
|
||||
return { code: RESULT_CODE_SUCCESS, data: this.neList, msg: 'success' };
|
||||
}
|
||||
const res = await listAllNeInfo({
|
||||
bandStatus: false,
|
||||
@@ -79,29 +75,11 @@ const useNeInfoStore = defineStore('neinfo', {
|
||||
}
|
||||
return res;
|
||||
},
|
||||
// 刷新跟踪接口列表
|
||||
async fnRefreshNeTraceInterface() {
|
||||
this.traceInterfaceList = [];
|
||||
const res = await this.fnNeTraceInterface();
|
||||
return res;
|
||||
},
|
||||
// 获取跟踪接口列表
|
||||
async fnNeTraceInterface() {
|
||||
// 有数据不请求
|
||||
if (this.traceInterfaceList.length > 0) {
|
||||
return { code: 1, data: this.traceInterfaceList, msg: 'success' };
|
||||
}
|
||||
const res = await getNeTraceInterfaceAll();
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
this.traceInterfaceList = res.data;
|
||||
}
|
||||
return res;
|
||||
},
|
||||
// 获取性能测量数据集列表
|
||||
async fnNeTaskPerformance() {
|
||||
// 有数据不请求
|
||||
if (this.perMeasurementList.length > 0) {
|
||||
return { code: 1, data: this.perMeasurementList, msg: 'success' };
|
||||
return { code: RESULT_CODE_SUCCESS, data: this.perMeasurementList, msg: 'success' };
|
||||
}
|
||||
const res = await getNePerformanceList();
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
RouteMeta,
|
||||
RouteRecordRaw,
|
||||
} from 'vue-router';
|
||||
import { getRouters } from '@/api/router';
|
||||
import { getRouter } from '@/api/auth';
|
||||
import BasicLayout from '@/layouts/BasicLayout.vue';
|
||||
import BlankLayout from '@/layouts/BlankLayout.vue';
|
||||
import LinkLayout from '@/layouts/LinkLayout.vue';
|
||||
@@ -46,7 +46,7 @@ const useRouterStore = defineStore('router', {
|
||||
* @returns 生成的路由菜单
|
||||
*/
|
||||
async generateRoutes() {
|
||||
const res = await getRouters();
|
||||
const res = await getRouter();
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
const buildRoutes = buildRouters(res.data.concat());
|
||||
this.buildRouterData = buildRoutes;
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import defaultAvatar from '@/assets/images/default_avatar.png';
|
||||
import useLayoutStore from './layout';
|
||||
import { login, logout, getInfo } from '@/api/login';
|
||||
import { setToken, removeToken } from '@/plugins/auth-token';
|
||||
import { login, logout, getInfo } from '@/api/auth';
|
||||
import {
|
||||
delAccessToken,
|
||||
delRefreshToken,
|
||||
setAccessToken,
|
||||
setRefreshToken,
|
||||
} from '@/plugins/auth-token';
|
||||
import { defineStore } from 'pinia';
|
||||
import { TOKEN_RESPONSE_FIELD } from '@/constants/token-constants';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { RESULT_CODE_EXCEPTION, RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { parseUrlPath } from '@/plugins/file-static-url';
|
||||
|
||||
/**用户信息类型 */
|
||||
type UserInfo = {
|
||||
/**用户ID */
|
||||
forcePasswdChange: boolean;
|
||||
/**用户ID */
|
||||
userId: string;
|
||||
/**登录账号 */
|
||||
@@ -33,6 +39,7 @@ type UserInfo = {
|
||||
|
||||
const useUserStore = defineStore('user', {
|
||||
state: (): UserInfo => ({
|
||||
forcePasswdChange: false,
|
||||
userId: '',
|
||||
userName: '',
|
||||
roles: [],
|
||||
@@ -102,8 +109,11 @@ const useUserStore = defineStore('user', {
|
||||
async fnLogin(loginBody: Record<string, string>) {
|
||||
const res = await login(loginBody);
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
const token = res.data[TOKEN_RESPONSE_FIELD];
|
||||
setToken(token);
|
||||
setAccessToken(res.data.accessToken, res.data.refreshExpiresIn);
|
||||
setRefreshToken(res.data.refreshToken, res.data.refreshExpiresIn);
|
||||
if (res.data?.forcePasswdChange) {
|
||||
this.forcePasswdChange = true;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
},
|
||||
@@ -139,10 +149,15 @@ const useUserStore = defineStore('user', {
|
||||
// }
|
||||
// useLayoutStore().changeWaterMark(waterMarkContent);
|
||||
useLayoutStore().changeWaterMark('');
|
||||
// 强制修改密码
|
||||
if (res.data?.forcePasswdChange) {
|
||||
this.forcePasswdChange = true;
|
||||
}
|
||||
}
|
||||
// 网络错误时退出登录状态
|
||||
if (res.code === 0) {
|
||||
removeToken();
|
||||
if (res.code === RESULT_CODE_EXCEPTION) {
|
||||
delAccessToken();
|
||||
delRefreshToken();
|
||||
window.location.reload();
|
||||
}
|
||||
return res;
|
||||
@@ -156,7 +171,8 @@ const useUserStore = defineStore('user', {
|
||||
} finally {
|
||||
this.roles = [];
|
||||
this.permissions = [];
|
||||
removeToken();
|
||||
delAccessToken();
|
||||
delRefreshToken();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -20,8 +20,10 @@ export const YYYYMMDDHHMMSS = 'YYYYMMDDHHmmss';
|
||||
/**年-月-日 时:分:秒 列如:2022-12-30 01:01:59 */
|
||||
export const YYYY_MM_DD_HH_MM_SS = 'YYYY-MM-DD HH:mm:ss';
|
||||
|
||||
/**特殊 列如:2025-04-28 08:00:46 GMT+08:00 */
|
||||
export const YYYY_MM_DD_HH_MM_SSZ = 'YYYY-MM-DD HH:mm:ss [GMT]Z';
|
||||
/**国际时间 列如:2022-12-30T01:01:59+08:00 */
|
||||
export const RFC3339 = 'YYYY-MM-DDTHH:mm:ssZ';
|
||||
/**国际时间 列如:Thu, Nov 14 2024 10:19 GMT+08:00 */
|
||||
export const RFC822Z = 'ddd, MMM DD YYYY HH:mm [GMT]Z';
|
||||
|
||||
/**
|
||||
* 格式时间字符串
|
||||
@@ -39,12 +41,12 @@ export function parseStrToDate(
|
||||
/**
|
||||
* 格式时间
|
||||
* @param date 可转的Date对象
|
||||
* @param formatStr 时间格式 默认YYYY-MM-DD HH:mm:ssZZ
|
||||
* @param formatStr 时间格式 默认 RFC3339
|
||||
* @returns 时间格式字符串
|
||||
*/
|
||||
export function parseDateToStr(
|
||||
date: string | number | Date,
|
||||
formatStr: string = YYYY_MM_DD_HH_MM_SSZ
|
||||
formatStr: string = RFC3339
|
||||
): string {
|
||||
return dayjs(date).format(formatStr);
|
||||
}
|
||||
|
||||
@@ -101,7 +101,14 @@ export function parseObjLineToHump(obj: any): any {
|
||||
* @param decimalPlaces 保留小数位,默认2位
|
||||
* @returns 单位 xB
|
||||
*/
|
||||
export function parseSizeFromFile(bytes: number, decimalPlaces: number = 2) {
|
||||
export function parseSizeFromFile(
|
||||
bytes: number,
|
||||
decimalPlaces: number = 2
|
||||
): string {
|
||||
if (typeof bytes !== 'number' || isNaN(bytes) || bytes < 0) {
|
||||
return `${bytes}`;
|
||||
}
|
||||
if (bytes === 0) return '0 B';
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
let i = 0;
|
||||
while (bytes >= 1024 && i < units.length - 1) {
|
||||
|
||||
@@ -27,14 +27,15 @@ export const regExpUserName = /^[A-Za-z0-9]{6,}$/;
|
||||
* 有效密码格式
|
||||
*
|
||||
* 密码至少包含大小写字母、数字、特殊符号,且不少于6位
|
||||
* /^(?![A-Za-z0-9]+$)(?![a-z0-9\W]+$)(?![A-Za-z\W]+$)(?![A-Z0-9\W]+$)[a-zA-Z0-9\W]{6,}$/
|
||||
*/
|
||||
export const regExpPasswd = /^.{5,}$/;
|
||||
// /^(?![A-Za-z0-9]+$)(?![a-z0-9\W]+$)(?![A-Za-z\W]+$)(?![A-Z0-9\W]+$)[a-zA-Z0-9\W]{6,}$/;
|
||||
export const regExpPasswd = /^.{6,}$/;
|
||||
|
||||
/**
|
||||
* 有效手机号格式
|
||||
* /^1[3|4|5|6|7|8|9][0-9]\d{8}$/
|
||||
*/
|
||||
export const regExpMobile = /^.{3,}$/; // /^1[3|4|5|6|7|8|9][0-9]\d{8}$/;
|
||||
export const regExpMobile = /^.{3,}$/;
|
||||
|
||||
/**
|
||||
* 有效邮箱格式
|
||||
@@ -46,8 +47,9 @@ export const regExpEmail =
|
||||
* 有效用户昵称格式
|
||||
*
|
||||
* 用户昵称只能包含字母、数字、中文和下划线,且不少于2位
|
||||
* /^[\w\u4e00-\u9fa5-]{2,}$/
|
||||
*/
|
||||
export const regExpNick = /^.{2,}$/; // /^[\w\u4e00-\u9fa5-]{2,}$/;
|
||||
export const regExpNick = /^.{2,}$/;
|
||||
|
||||
/**
|
||||
* 是否为http(s)://开头
|
||||
|
||||
@@ -52,6 +52,7 @@ function fnFinish() {
|
||||
updateUserPassword(state.form.oldPassword, state.form.confirmPassword)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
fnLogOut();
|
||||
Modal.success({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.account.settings.submitOkTip', {
|
||||
@@ -59,7 +60,7 @@ function fnFinish() {
|
||||
}),
|
||||
okText: t('views.account.settings.submitOk'),
|
||||
onOk() {
|
||||
fnLogOut().finally(() => router.push({ name: 'Login' }));
|
||||
router.push({ name: 'Login' });
|
||||
},
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -23,7 +23,9 @@ export const upfFlowData = ref<FDType>({
|
||||
|
||||
/**UPF-流量数据 数据解析 */
|
||||
export function upfFlowParse(data: Record<string, string>) {
|
||||
upfFlowData.value.lineXTime.push(parseDateToStr(+data['timeGroup'], 'HH:mm:ss'));
|
||||
upfFlowData.value.lineXTime.push(
|
||||
parseDateToStr(+data['timeGroup'], 'HH:mm:ss')
|
||||
);
|
||||
const upN3 = parseSizeFromKbs(+data['UPF.03'], 5);
|
||||
upfFlowData.value.lineYUp.push(upN3[0]);
|
||||
const downN6 = parseSizeFromKbs(+data['UPF.06'], 5);
|
||||
|
||||
@@ -385,7 +385,7 @@ onBeforeUnmount(() => {
|
||||
class="item toRouter"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<div @click="fnToRouter('Sub_2010')">
|
||||
<div @click="fnToRouter('UdmSub_2001')">
|
||||
<UserOutlined
|
||||
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
|
||||
/>
|
||||
@@ -399,11 +399,7 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
<template #overlay>
|
||||
<a-menu @click="fnSelectUDM">
|
||||
<a-menu-item
|
||||
v-for="v in udmOtions"
|
||||
:key="v.value"
|
||||
:disabled="udmNeId === v.value"
|
||||
>
|
||||
<a-menu-item v-for="v in udmOtions" :key="v.value" :disabled="udmNeId === v.value">
|
||||
{{ v.label }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
@@ -413,7 +409,7 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('Ims_2080')"
|
||||
@click="fnToRouter('ImsSub_2004')"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
style="margin: 0 12px"
|
||||
v-perms:has="['dashboard:overview:imsUeNum']"
|
||||
@@ -428,7 +424,7 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('Ue_2081')"
|
||||
@click="fnToRouter('SmfSub_2005')"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
v-perms:has="['dashboard:overview:smfUeNum']"
|
||||
>
|
||||
@@ -452,7 +448,7 @@ onBeforeUnmount(() => {
|
||||
<div class="data">
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('BaseStation_2096', { neType: 'AMF' })"
|
||||
@click="fnToRouter('BaseStation_2007', { neType: 'AMF' })"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<div style="align-items: flex-start">
|
||||
@@ -466,7 +462,7 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('BaseStation_2096', { neType: 'AMF' })"
|
||||
@click="fnToRouter('BaseStation_2007', { neType: 'AMF' })"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<div style="align-items: flex-start">
|
||||
@@ -489,7 +485,7 @@ onBeforeUnmount(() => {
|
||||
<div class="data">
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('BaseStation_2096', { neType: 'MME' })"
|
||||
@click="fnToRouter('BaseStation_2007', { neType: 'MME' })"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<div style="align-items: flex-start">
|
||||
@@ -503,7 +499,7 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('BaseStation_2096', { neType: 'MME' })"
|
||||
@click="fnToRouter('BaseStation_2007', { neType: 'MME' })"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<div style="align-items: flex-start">
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
showPass,
|
||||
getPass,
|
||||
exportAll,
|
||||
exportAlarm,
|
||||
} from '@/api/faultManage/actAlarm';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
@@ -730,6 +731,38 @@ function fnModalCancel() {
|
||||
modalState.helpShowView = false;
|
||||
}
|
||||
|
||||
/**列表导出 */
|
||||
function fnExportList() {
|
||||
if (modalState.confirmLoading) return;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.faultManage.activeAlarm.exportTip'),
|
||||
onOk() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
const querys = toRaw(queryParams);
|
||||
exportAlarm(querys)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.operateOk'),
|
||||
duration: 3,
|
||||
});
|
||||
saveAs(res.data, `active_alarm_export_${Date.now()}.xlsx`);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**查询列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
@@ -934,6 +967,11 @@ onMounted(() => {
|
||||
{{ t('views.faultManage.activeAlarm.syncMyself') }}
|
||||
</a-button>
|
||||
|
||||
<a-button type="primary" @click.prevent="fnShowSet()" v-if="false">
|
||||
<template #icon> <SettingOutlined /> </template>
|
||||
{{ t('views.faultManage.activeAlarm.disPlayFilfter') }}
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
type="primary"
|
||||
danger
|
||||
@@ -946,19 +984,9 @@ onMounted(() => {
|
||||
{{ t('views.faultManage.activeAlarm.clear') }}
|
||||
</a-button>
|
||||
|
||||
<a-button type="primary" @click.prevent="fnShowSet()" v-if="false">
|
||||
<template #icon> <SettingOutlined /> </template>
|
||||
{{ t('views.faultManage.activeAlarm.disPlayFilfter') }}
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
type="primary"
|
||||
@click.prevent="fnExportAll()"
|
||||
:disabled="tableState.data.length <= 0"
|
||||
v-if="false"
|
||||
>
|
||||
<template #icon> <export-outlined /> </template>
|
||||
{{ t('views.faultManage.activeAlarm.exportAll') }}
|
||||
<a-button type="dashed" @click.prevent="fnExportList()">
|
||||
<template #icon><ExportOutlined /></template>
|
||||
{{ t('common.export') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
@@ -7,11 +7,11 @@ import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||
import {
|
||||
listAct,
|
||||
updateConfirm,
|
||||
cancelConfirm,
|
||||
exportAll,
|
||||
} from '@/api/faultManage/historyAlarm';
|
||||
import { listAct, exportAlarm } from '@/api/faultManage/actAlarm';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
@@ -59,9 +59,6 @@ let rangePickerPresets = ref([
|
||||
},
|
||||
]);
|
||||
|
||||
/**表格字段列排序 */
|
||||
let tableColumnsDnd = ref<ColumnsType>([]);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
alarmStatus: 0,
|
||||
@@ -144,7 +141,7 @@ let tableState: TabeStateType = reactive({
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
let tableColumns = ref<ColumnsType>([
|
||||
{
|
||||
title: t('views.faultManage.activeAlarm.alarmType'),
|
||||
dataIndex: 'alarmType',
|
||||
@@ -241,7 +238,10 @@ let tableColumns: ColumnsType = [
|
||||
fixed: 'right',
|
||||
width: 100,
|
||||
},
|
||||
];
|
||||
]);
|
||||
|
||||
/**表格字段列排序 */
|
||||
let tableColumnsDnd = ref<ColumnsType>([]);
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
@@ -466,7 +466,7 @@ function mapKeysWithReduce(data: any[], titleMapping: Record<string, string>) {
|
||||
function fnExportAll() {
|
||||
Modal.confirm({
|
||||
title: 'Tip',
|
||||
content: t('views.faultManage.historyAlarm.exportSure'),
|
||||
content: t('views.faultManage.activeAlarm.exportSure'),
|
||||
onOk() {
|
||||
const key = 'exportAlarmHis';
|
||||
message.loading({ content: t('common.loading'), key });
|
||||
@@ -543,6 +543,38 @@ function fnModalCancel() {
|
||||
modalState.openByShowSet = false;
|
||||
}
|
||||
|
||||
/**列表导出 */
|
||||
function fnExportList() {
|
||||
if (modalState.confirmLoading) return;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.faultManage.activeAlarm.exportTip'),
|
||||
onOk() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
const querys = toRaw(queryParams);
|
||||
exportAlarm(querys)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.operateOk'),
|
||||
duration: 3,
|
||||
});
|
||||
saveAs(res.data, `history_alarm_export_${Date.now()}.xlsx`);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**查询列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
@@ -712,11 +744,17 @@ onMounted(() => {
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center" v-if="false">
|
||||
<a-space :size="8" align="center">
|
||||
<a-button type="dashed" @click.prevent="fnExportList()">
|
||||
<template #icon><ExportOutlined /></template>
|
||||
{{ t('common.export') }}
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
type="primary"
|
||||
@click.prevent="fnCancelConfirm()"
|
||||
:disabled="state.selectedRowKeys.length <= 0"
|
||||
v-if="false"
|
||||
>
|
||||
<template #icon>
|
||||
<CloseOutlined />
|
||||
@@ -727,6 +765,7 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click.prevent="fnExportAll()"
|
||||
:disabled="tableState.data.length <= 0"
|
||||
v-if="false"
|
||||
>
|
||||
<template #icon> <export-outlined /> </template>
|
||||
{{ t('views.faultManage.activeAlarm.exportAll') }}
|
||||
@@ -796,28 +835,28 @@ onMounted(() => {
|
||||
:scroll="{ x: tableColumns.length * 150, y: 'calc(100vh - 480px)' }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'origSeverity'">
|
||||
<template v-if="column?.key === 'origSeverity'">
|
||||
<DictTag
|
||||
:options="dict.activeAlarmSeverity"
|
||||
:value="record.origSeverity"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'alarmType'">
|
||||
<template v-if="column?.key === 'alarmType'">
|
||||
<DictTag
|
||||
:options="dict.activeAlarmType"
|
||||
:value="record.alarmType"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'clearType'">
|
||||
<template v-if="column?.key === 'clearType'">
|
||||
<DictTag
|
||||
:options="dict.activeClearType"
|
||||
:value="record.clearType"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'ackState'">
|
||||
<template v-if="column?.key === 'ackState'">
|
||||
<DictTag :options="dict.activeAckState" :value="record.ackState" />
|
||||
</template>
|
||||
<template v-if="column.key === 'id'">
|
||||
<template v-if="column?.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.viewText') }}</template>
|
||||
|
||||
@@ -1,38 +1,57 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { reactive, ref, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { ProModal } from 'antdv-pro-modal';
|
||||
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||
import { Form, Modal, message } from 'ant-design-vue/es';
|
||||
import { Modal, message } from 'ant-design-vue/es';
|
||||
import BackupModal from '@/views/ne/neConfigBackup/components/BackupModal.vue';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import {
|
||||
getBakFile,
|
||||
getBakFileList,
|
||||
downFile,
|
||||
delFile,
|
||||
updateFTPInfo,
|
||||
getFTPInfo,
|
||||
putFTPInfo,
|
||||
} from '@/api/logManage/exportFile';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import saveAs from 'file-saver';
|
||||
import { regExpIPv4 } from '@/utils/regular-utils';
|
||||
import { delFile, getFile, listFile } from '@/api/tool/file';
|
||||
import { parseSizeFromFile } from '@/utils/parse-utils';
|
||||
import { pushBackupFTP } from '@/api/neData/backup';
|
||||
const { t } = useI18n();
|
||||
|
||||
/**网元参数 */
|
||||
let logSelect = ref<string[]>([]);
|
||||
|
||||
/**文件列表 */
|
||||
let fileList = ref<any>([]);
|
||||
/**文件来源 */
|
||||
let sourceState = reactive({
|
||||
/**文件列表 */
|
||||
list: [
|
||||
{
|
||||
value: '/log/operate_log',
|
||||
label: t('views.logManage.exportFile.operateLog'),
|
||||
path: '/usr/local/omc/backup',
|
||||
},
|
||||
{
|
||||
value: '/cdr/ims_cdr',
|
||||
label: t('views.logManage.exportFile.cdrIMS'),
|
||||
path: '/usr/local/omc/backup',
|
||||
},
|
||||
{
|
||||
value: '/cdr/smf_cdr',
|
||||
label: t('views.logManage.exportFile.cdrSMF'),
|
||||
path: '/usr/local/omc/backup',
|
||||
},
|
||||
{
|
||||
value: '/cdr/smsc_cdr',
|
||||
label: t('views.logManage.exportFile.cdrSMSC'),
|
||||
path: '/usr/local/omc/backup',
|
||||
},
|
||||
{
|
||||
value: '/cdr/sgwc_cdr',
|
||||
label: t('views.logManage.exportFile.cdrSGWC'),
|
||||
path: '/usr/local/omc/backup',
|
||||
},
|
||||
],
|
||||
/**选择value */
|
||||
value: undefined,
|
||||
});
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**读取路径 */
|
||||
path: '',
|
||||
/**表名 */
|
||||
tableName: '',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
@@ -80,6 +99,9 @@ let tableColumns: ColumnsType = [
|
||||
title: t('views.logManage.neFile.size'),
|
||||
dataIndex: 'size',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
return parseSizeFromFile(opt.value);
|
||||
},
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
@@ -88,9 +110,9 @@ let tableColumns: ColumnsType = [
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value * 1000);
|
||||
return parseDateToStr(opt.value);
|
||||
},
|
||||
width: 150,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('views.logManage.neFile.fileName'),
|
||||
@@ -135,10 +157,6 @@ let tablePagination = reactive({
|
||||
|
||||
/**下载触发等待 */
|
||||
let downLoading = ref<boolean>(false);
|
||||
|
||||
/**删除触发等待 */
|
||||
let delLoading = ref<boolean>(false);
|
||||
|
||||
/**信息文件下载 */
|
||||
function fnDownloadFile(row: Record<string, any>) {
|
||||
if (downLoading.value) return;
|
||||
@@ -150,10 +168,7 @@ function fnDownloadFile(row: Record<string, any>) {
|
||||
onOk() {
|
||||
downLoading.value = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
downFile({
|
||||
path: queryParams.path,
|
||||
fileName: row.fileName,
|
||||
})
|
||||
getFile(queryParams.path, row.fileName)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
@@ -178,6 +193,9 @@ function fnDownloadFile(row: Record<string, any>) {
|
||||
});
|
||||
}
|
||||
|
||||
/**删除触发等待 */
|
||||
let delLoading = ref<boolean>(false);
|
||||
/**信息文件删除 */
|
||||
function fnRecordDelete(row: Record<string, any>) {
|
||||
if (delLoading.value) return;
|
||||
Modal.confirm({
|
||||
@@ -186,30 +204,27 @@ function fnRecordDelete(row: Record<string, any>) {
|
||||
fileName: row.fileName,
|
||||
}),
|
||||
onOk() {
|
||||
const key = 'delFile';
|
||||
delLoading.value = true;
|
||||
message.loading({ content: t('common.loading'), key });
|
||||
delFile({
|
||||
fileName: row.fileName,
|
||||
path: queryParams.path,
|
||||
})
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
delFile(queryParams.path, row.fileName)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('views.system.user.delSuss'),
|
||||
key,
|
||||
content: t('common.msgSuccess', {
|
||||
msg: t('common.deleteText'),
|
||||
}),
|
||||
duration: 2,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key: key,
|
||||
content: t('views.logManage.exportFile.deleteTipErr'),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
delLoading.value = false;
|
||||
});
|
||||
},
|
||||
@@ -217,17 +232,18 @@ function fnRecordDelete(row: Record<string, any>) {
|
||||
}
|
||||
|
||||
/**网元类型选择对应修改 */
|
||||
function fnNeChange(keys: any, opt: any) {
|
||||
queryParams.tableName = keys;
|
||||
queryParams.path = opt.path;
|
||||
function fnNeChange(_: any, opt: any) {
|
||||
queryParams.path = `${opt.path}${opt.value}`;
|
||||
ftpInfo.path = queryParams.path;
|
||||
ftpInfo.tag = opt.value;
|
||||
fnGetList(1);
|
||||
}
|
||||
|
||||
/**查询备份信息列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (queryParams.tableName === '') {
|
||||
if (queryParams.path === '') {
|
||||
message.warning({
|
||||
content: t('views.logManage.exportFile.selectTip'),
|
||||
content: t('views.logManage.exportFile.fileSourcePlease'),
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
@@ -237,7 +253,7 @@ function fnGetList(pageNum?: number) {
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
getBakFileList(toRaw(queryParams)).then(res => {
|
||||
listFile(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
const { total, rows } = res.data;
|
||||
tablePagination.total = total;
|
||||
@@ -259,147 +275,34 @@ function fnGetList(pageNum?: number) {
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getBakFile().then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
res.data.forEach((item: any) => {
|
||||
fileList.value.push({
|
||||
value: item.tableName,
|
||||
label: item.tableDisplay,
|
||||
path: item.filePath,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
// .finally(() => {
|
||||
// fnGetList();
|
||||
// });
|
||||
});
|
||||
/**打开FTP配置窗口 */
|
||||
const openFTPModal = ref<boolean>(false);
|
||||
function fnFTPModalOpen() {
|
||||
openFTPModal.value = !openFTPModal.value;
|
||||
}
|
||||
|
||||
/**对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**新增框或修改框是否显示 */
|
||||
openByEdit: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
type FTPInfoType = {
|
||||
path: string;
|
||||
tag: string;
|
||||
fileName: string;
|
||||
};
|
||||
|
||||
/**FTP日志对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
openByEdit: false,
|
||||
title: '设置远程备份配置',
|
||||
from: {
|
||||
username: '',
|
||||
password: '',
|
||||
toIp: '',
|
||||
toPort: 22,
|
||||
enable: false,
|
||||
dir: '',
|
||||
},
|
||||
confirmLoading: false,
|
||||
const ftpInfo = reactive<FTPInfoType>({
|
||||
path: '',
|
||||
tag: '',
|
||||
fileName: '',
|
||||
});
|
||||
|
||||
/**FTP日志对象信息内表单属性和校验规则 */
|
||||
const modalStateFrom = Form.useForm(
|
||||
modalState.from,
|
||||
reactive({
|
||||
toIp: [
|
||||
{
|
||||
required: true,
|
||||
pattern: regExpIPv4,
|
||||
message: 'Please enter the service login IP',
|
||||
},
|
||||
],
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
message: 'Please enter the service login user name',
|
||||
},
|
||||
],
|
||||
dir: [
|
||||
{
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
message: 'Please enter the service address target file directory',
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param configId 参数编号id, 不传为新增
|
||||
*/
|
||||
function fnModalVisibleByEdit() {
|
||||
if (modalState.confirmLoading) return;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
modalState.confirmLoading = true;
|
||||
getFTPInfo().then(res => {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
modalState.from = Object.assign(modalState.from, res.data);
|
||||
modalState.title = 'Setting Remote Backup';
|
||||
modalState.openByEdit = true;
|
||||
/**同步文件到FTP */
|
||||
function fnSyncFileToFTP(fileName: string) {
|
||||
ftpInfo.fileName = fileName;
|
||||
pushBackupFTP(toRaw(ftpInfo)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(t('common.operateOk'), 3);
|
||||
} else {
|
||||
message.error(res.msg, 3);
|
||||
modalState.title = 'Setting Remote Backup';
|
||||
modalState.openByEdit = false;
|
||||
message.warning(res.msg, 3);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**FTP对象保存 */
|
||||
function fnModalOk() {
|
||||
modalStateFrom.validate().then(() => {
|
||||
modalState.confirmLoading = true;
|
||||
const from = toRaw(modalState.from);
|
||||
updateFTPInfo(from)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(`Configuration saved successfully`, 3);
|
||||
fnModalCancel();
|
||||
} else {
|
||||
message.warning(`Configuration save exception`, 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.openByEdit = false;
|
||||
modalStateFrom.resetFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步文件到FTP
|
||||
* @param row
|
||||
*/
|
||||
function fnSyncFileToFTP(row: Record<string, any>) {
|
||||
putFTPInfo(row.filePath, row.fileName)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(t('common.operateOk'), 3);
|
||||
} else {
|
||||
message.warning(res.msg, 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -410,12 +313,14 @@ function fnSyncFileToFTP(row: Record<string, any>) {
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16" align="middle">
|
||||
<a-col>
|
||||
<span>{{ t('views.logManage.exportFile.fileName') }}:</span>
|
||||
<span>{{ t('views.logManage.exportFile.fileSource') }}:</span
|
||||
>
|
||||
<a-select
|
||||
v-model:value="logSelect"
|
||||
:options="fileList"
|
||||
v-model:value="sourceState.value"
|
||||
:options="sourceState.list"
|
||||
@change="fnNeChange"
|
||||
:allow-clear="false"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
style="width: 200px"
|
||||
/>
|
||||
</a-col>
|
||||
@@ -436,9 +341,11 @@ function fnSyncFileToFTP(row: Record<string, any>) {
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>Setting Remote Backup</template>
|
||||
<a-button type="text" @click.prevent="fnModalVisibleByEdit()">
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>
|
||||
{{ t('views.ne.neConfigBackup.backupModal.title') }}
|
||||
</template>
|
||||
<a-button type="text" @click.prevent="fnFTPModalOpen()">
|
||||
<template #icon><DeliveredProcedureOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
@@ -465,135 +372,45 @@ function fnSyncFileToFTP(row: Record<string, any>) {
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'fileName'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-button
|
||||
type="link"
|
||||
:loading="downLoading"
|
||||
@click.prevent="fnSyncFileToFTP(record)"
|
||||
v-if="record.fileType === 'file'"
|
||||
>
|
||||
<template #icon><CloudServerOutlined /></template>
|
||||
</a-button>
|
||||
<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-tooltip placement="topRight" v-if="record.fileType === 'file'">
|
||||
<template #title>
|
||||
{{ t('views.ne.neConfigBackup.backupModal.pushFileOper') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnSyncFileToFTP(record.fileName)"
|
||||
>
|
||||
<template #icon><CloudServerOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="topRight" v-if="record.fileType === 'file'">
|
||||
<template #title>{{ t('common.downloadText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
:loading="downLoading"
|
||||
@click.prevent="fnDownloadFile(record)"
|
||||
>
|
||||
<template #icon><DownloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="topRight" v-if="record.fileType === 'file'">
|
||||
<template #title>{{ t('common.deleteText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
:loading="delLoading"
|
||||
@click.prevent="fnRecordDelete(record)"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 新增框或修改框 -->
|
||||
<ProModal
|
||||
:drag="true"
|
||||
:width="800"
|
||||
:destroyOnClose="true"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:open="modalState.openByEdit"
|
||||
:title="modalState.title"
|
||||
:confirm-loading="modalState.confirmLoading"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form
|
||||
name="modalStateFrom"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 6 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-form-item label="Enable" name="enable" :label-col="{ span: 3 }">
|
||||
<a-switch
|
||||
v-model:checked="modalState.from.enable"
|
||||
:checked-children="t('common.switch.open')"
|
||||
:un-checked-children="t('common.switch.shut')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<template v-if="modalState.from.enable">
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
label="Service IP"
|
||||
name="toIp"
|
||||
v-bind="modalStateFrom.validateInfos.toIp"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.toIp"
|
||||
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="Service Port"
|
||||
name="toPort"
|
||||
v-bind="modalStateFrom.validateInfos.toPort"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="modalState.from.toPort"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
label="UserName"
|
||||
name="username"
|
||||
v-bind="modalStateFrom.validateInfos.username"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.username"
|
||||
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="Password"
|
||||
name="password"
|
||||
v-bind="modalStateFrom.validateInfos.password"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="modalState.from.password"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input-password>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item
|
||||
label="Save Dir"
|
||||
name="dir"
|
||||
v-bind="modalStateFrom.validateInfos.dir"
|
||||
:label-col="{ span: 3 }"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.dir"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-form>
|
||||
</ProModal>
|
||||
<!-- FTP配置窗口 -->
|
||||
<BackupModal v-model:open="openFTPModal"></BackupModal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ let tableColumns: ColumnsType = reactive([
|
||||
title: t('views.logManage.mml.logTime'),
|
||||
dataIndex: 'logTime',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
width: 200,
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { message } from 'ant-design-vue/es';
|
||||
import { reactive, onMounted, computed, toRaw } from 'vue';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { getCaptchaImage } from '@/api/login';
|
||||
import { getCaptchaImage } from '@/api/auth';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useLayoutStore from '@/store/modules/layout';
|
||||
@@ -75,18 +75,28 @@ function fnFinish() {
|
||||
function fnGetCaptcha() {
|
||||
if (state.captchaClick) return;
|
||||
state.captchaClick = true;
|
||||
getCaptchaImage().then(res => {
|
||||
state.captchaClick = false;
|
||||
if (res.code != 1) {
|
||||
message.warning(`${res.msg}`, 3);
|
||||
return;
|
||||
}
|
||||
state.captcha.enabled = Boolean(res.captchaEnabled);
|
||||
if (state.captcha.enabled) {
|
||||
state.captcha.codeImg = res.img;
|
||||
state.from.uuid = res.uuid;
|
||||
}
|
||||
});
|
||||
getCaptchaImage()
|
||||
.then(res => {
|
||||
if (res.code !== RESULT_CODE_SUCCESS) {
|
||||
message.warning({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { enabled, img, uuid } = res.data;
|
||||
state.captcha.enabled = Boolean(enabled);
|
||||
if (state.captcha.enabled) {
|
||||
state.captcha.codeImg = img;
|
||||
state.from.uuid = uuid;
|
||||
}
|
||||
if (res.data?.text) {
|
||||
state.from.code = res.data.text;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
state.captchaClick = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 判断是否有背景地址
|
||||
|
||||
@@ -181,9 +181,9 @@ function fnSendMML() {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
let resultArr = res.data;
|
||||
for (let i = 0; i < resultArr.length; i++) {
|
||||
const str = resultArr[i];
|
||||
const str = resultArr[i] || '';
|
||||
const logStr = str.replace(/(\r\n|\n)/g, '\n');
|
||||
const cmdStr = cmdArr[i];
|
||||
const cmdStr = cmdArr[i] || '';
|
||||
state.mmlCmdLog += `${cmdStr}\n${logStr}\n`;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -183,9 +183,9 @@ function fnSendMML() {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
let resultArr = res.data;
|
||||
for (let i = 0; i < resultArr.length; i++) {
|
||||
const str = resultArr[i];
|
||||
const str = resultArr[i] || '';
|
||||
const logStr = str.replace(/(\r\n|\n)/g, '\n');
|
||||
const cmdStr = cmdArr[i];
|
||||
const cmdStr = cmdArr[i] || '';
|
||||
state.mmlCmdLog += `${cmdStr}\n${logStr}\n`;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -183,9 +183,9 @@ function fnSendMML() {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
let resultArr = res.data;
|
||||
for (let i = 0; i < resultArr.length; i++) {
|
||||
const str = resultArr[i] || "";
|
||||
const str = resultArr[i] || '';
|
||||
const logStr = str.replace(/(\r\n|\n)/g, '\n');
|
||||
const cmdStr = cmdArr[i] || "";
|
||||
const cmdStr = cmdArr[i] || '';
|
||||
state.mmlCmdLog += `${cmdStr}\n${logStr}\n`;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1100,7 +1100,7 @@ onMounted(() => {
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.targetParams"
|
||||
:auto-size="{ minRows: 2, maxRows: 6 }"
|
||||
:maxlength="400"
|
||||
:maxlength="2000"
|
||||
:placeholder="t('views.monitor.job.targetParamsPlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
@@ -118,8 +118,8 @@ let tableState: TabeStateType = reactive({
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'jobLogId',
|
||||
align: 'center',
|
||||
dataIndex: 'logId',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
@@ -132,7 +132,7 @@ let tableColumns: ColumnsType = [
|
||||
title: t('views.monitor.jobLog.jobGroup'),
|
||||
dataIndex: 'jobGroup',
|
||||
key: 'jobGroup',
|
||||
align: 'center',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
@@ -142,17 +142,17 @@ let tableColumns: ColumnsType = [
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.monitor.jobLog.statusFlag'),
|
||||
title: t('views.monitor.jobLog.status'),
|
||||
dataIndex: 'statusFlag',
|
||||
key: 'statusFlag',
|
||||
align: 'center',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.monitor.jobLog.createTime'),
|
||||
dataIndex: 'createTime',
|
||||
align: 'center',
|
||||
width: 150,
|
||||
align: 'left',
|
||||
width: 200,
|
||||
customRender(opt) {
|
||||
if (+opt.value <= 0) return '';
|
||||
return parseDateToStr(+opt.value);
|
||||
@@ -170,7 +170,7 @@ let tableColumns: ColumnsType = [
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'jobLogId',
|
||||
key: 'logId',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
@@ -229,7 +229,7 @@ let modalState: ModalStateType = reactive({
|
||||
open: false,
|
||||
title: '任务日志',
|
||||
from: {
|
||||
jobLogId: undefined,
|
||||
logId: undefined,
|
||||
jobName: '',
|
||||
jobGroup: 'DEFAULT',
|
||||
invokeTarget: '',
|
||||
@@ -588,7 +588,7 @@ onMounted(() => {
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="jobLogId"
|
||||
row-key="logId"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
@@ -615,7 +615,7 @@ onMounted(() => {
|
||||
}}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'jobLogId'">
|
||||
<template v-if="column.key === 'logId'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.viewText') }}</template>
|
||||
@@ -644,8 +644,8 @@ onMounted(() => {
|
||||
<a-form layout="horizontal" :label-col="{ span: 6 }" :label-wrap="true">
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item :label="t('common.rowId')" name="jobLogId">
|
||||
{{ modalState.from.jobLogId }}
|
||||
<a-form-item :label="t('common.rowId')" name="logId">
|
||||
{{ modalState.from.logId }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
|
||||
230
src/views/ne/neConfigBackup/components/BackupModal.vue
Normal file
230
src/views/ne/neConfigBackup/components/BackupModal.vue
Normal file
@@ -0,0 +1,230 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, watch, toRaw } from 'vue';
|
||||
import { ProModal } from 'antdv-pro-modal';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { regExpIPv4 } from '@/utils/regular-utils';
|
||||
import { getBackupFTP, updateBackupFTP } from '@/api/neData/backup';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(['ok', 'cancel', 'update:open']);
|
||||
const props = defineProps({
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type StateType = {
|
||||
/**框是否显示 */
|
||||
open: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**查看命令 */
|
||||
form: Record<string, any>;
|
||||
/**等待 */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let state: StateType = reactive({
|
||||
open: false,
|
||||
title: '设置远程备份配置',
|
||||
form: {
|
||||
username: '',
|
||||
password: '',
|
||||
toIp: '',
|
||||
toPort: 22,
|
||||
enable: false,
|
||||
dir: '',
|
||||
},
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**FTP对象信息内表单属性和校验规则 */
|
||||
const stateFrom = Form.useForm(
|
||||
state.form,
|
||||
reactive({
|
||||
toIp: [
|
||||
{
|
||||
required: true,
|
||||
pattern: regExpIPv4,
|
||||
message: t('views.ne.neConfigBackup.backupModal.toIpPleace'),
|
||||
},
|
||||
],
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
message: t('views.ne.neConfigBackup.backupModal.usernamePleace'),
|
||||
},
|
||||
],
|
||||
dir: [
|
||||
{
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
message: t('views.ne.neConfigBackup.backupModal.dirPleace'),
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**对象保存 */
|
||||
function fnModalOk() {
|
||||
stateFrom.validate().then(() => {
|
||||
state.confirmLoading = true;
|
||||
const from = toRaw(state.form);
|
||||
updateBackupFTP(from)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(t('common.operateOk'), 3);
|
||||
fnModalCancel();
|
||||
} else {
|
||||
message.warning(t('common.operateErr'), 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
state.confirmLoading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
stateFrom.resetFields();
|
||||
state.open = false;
|
||||
emit('cancel');
|
||||
emit('update:open', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
*/
|
||||
function fnModalVisible() {
|
||||
if (state.confirmLoading) return;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
state.confirmLoading = true;
|
||||
getBackupFTP()
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
state.form = Object.assign(state.form, res.data);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
state.confirmLoading = false;
|
||||
hide();
|
||||
state.title = t('views.ne.neConfigBackup.backupModal.title');
|
||||
state.open = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**监听是否显示,初始数据 */
|
||||
watch(
|
||||
() => props.open,
|
||||
val => {
|
||||
if (val) {
|
||||
fnModalVisible();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ProModal
|
||||
:drag="true"
|
||||
:width="520"
|
||||
:destroyOnClose="true"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:open="state.open"
|
||||
:title="state.title"
|
||||
:confirm-loading="state.confirmLoading"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form
|
||||
name="modalStateFTPFrom"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 6 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-form-item
|
||||
:label="t('views.ne.neConfigBackup.backupModal.enable')"
|
||||
name="enable"
|
||||
>
|
||||
<a-switch
|
||||
v-model:checked="state.form.enable"
|
||||
:checked-children="t('common.switch.open')"
|
||||
:un-checked-children="t('common.switch.shut')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<template v-if="state.form.enable">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neConfigBackup.backupModal.toIp')"
|
||||
name="toIp"
|
||||
v-bind="stateFrom.validateInfos.toIp"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="state.form.toIp"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('views.ne.neConfigBackup.backupModal.toPort')"
|
||||
name="toPort"
|
||||
v-bind="stateFrom.validateInfos.toPort"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="state.form.toPort"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.ne.neConfigBackup.backupModal.username')"
|
||||
name="username"
|
||||
v-bind="stateFrom.validateInfos.username"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="state.form.username"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('views.ne.neConfigBackup.backupModal.password')"
|
||||
name="password"
|
||||
v-bind="stateFrom.validateInfos.password"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="state.form.password"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.ne.neConfigBackup.backupModal.dir')"
|
||||
name="dir"
|
||||
v-bind="stateFrom.validateInfos.dir"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="state.form.dir"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-form>
|
||||
</ProModal>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -5,22 +5,20 @@ import { ProModal } from 'antdv-pro-modal';
|
||||
import { Form, Modal, TableColumnsType, message } from 'ant-design-vue/es';
|
||||
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||
import BackupModal from './components/BackupModal.vue';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import { NE_TYPE_LIST } from '@/constants/ne-constants';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { regExpIPv4 } from '@/utils/regular-utils';
|
||||
import {
|
||||
delNeConfigBackup,
|
||||
downNeConfigBackup,
|
||||
listNeConfigBackup,
|
||||
updateNeConfigBackup,
|
||||
getFTPInfo,
|
||||
putFTPInfo,
|
||||
updateFTPInfo,
|
||||
} from '@/api/ne/neConfigBackup';
|
||||
import { pushBackupFTP } from '@/api/neData/backup';
|
||||
import saveAs from 'file-saver';
|
||||
const { t } = useI18n();
|
||||
const { getDict } = useDictStore();
|
||||
@@ -389,119 +387,26 @@ onMounted(() => {
|
||||
});
|
||||
});
|
||||
|
||||
/**FTP日志对象信息状态 */
|
||||
let modalStateFTP: ModalStateType = reactive({
|
||||
openByEdit: false,
|
||||
title: '设置远程备份配置',
|
||||
from: {
|
||||
username: '',
|
||||
password: '',
|
||||
toIp: '',
|
||||
toPort: 22,
|
||||
enable: false,
|
||||
dir: '',
|
||||
},
|
||||
confirmLoading: false,
|
||||
});
|
||||
/**打开FTP配置窗口 */
|
||||
const openFTPModal = ref<boolean>(false);
|
||||
function fnFTPModalOpen() {
|
||||
openFTPModal.value = !openFTPModal.value;
|
||||
}
|
||||
|
||||
/**FTP日志对象信息内表单属性和校验规则 */
|
||||
const modalStateFTPFrom = Form.useForm(
|
||||
modalStateFTP.from,
|
||||
reactive({
|
||||
toIp: [
|
||||
{
|
||||
required: true,
|
||||
pattern: regExpIPv4,
|
||||
message: 'Please enter the service login IP',
|
||||
},
|
||||
],
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
message: 'Please enter the service login user name',
|
||||
},
|
||||
],
|
||||
dir: [
|
||||
{
|
||||
required: true,
|
||||
trigger: 'blur',
|
||||
message: 'Please enter the service address target file directory',
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param configId 参数编号id, 不传为新增
|
||||
*/
|
||||
function fnModalFTPVisibleByEdit() {
|
||||
if (modalStateFTP.confirmLoading) return;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
modalStateFTP.confirmLoading = true;
|
||||
getFTPInfo().then(res => {
|
||||
modalStateFTP.confirmLoading = false;
|
||||
hide();
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
modalStateFTP.from = Object.assign(modalStateFTP.from, res.data);
|
||||
modalStateFTP.title = 'Setting Remote Backup';
|
||||
modalStateFTP.openByEdit = true;
|
||||
/**同步文件到FTP */
|
||||
function fnSyncFileToFTP(row: Record<string, any>) {
|
||||
pushBackupFTP({
|
||||
path: row.path.substring(0, row.path.lastIndexOf('/')),
|
||||
fileName: row.name,
|
||||
tag: 'ne_config',
|
||||
}).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(t('common.operateOk'), 3);
|
||||
} else {
|
||||
message.error(res.msg, 3);
|
||||
modalStateFTP.title = 'Setting Remote Backup';
|
||||
modalStateFTP.openByEdit = false;
|
||||
message.warning(res.msg, 3);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**FTP对象保存 */
|
||||
function fnModalFTPOk() {
|
||||
modalStateFTPFrom.validate().then(() => {
|
||||
modalStateFTP.confirmLoading = true;
|
||||
const from = toRaw(modalStateFTP.from);
|
||||
updateFTPInfo(from)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(`Configuration saved successfully`, 3);
|
||||
fnModalFTPCancel();
|
||||
} else {
|
||||
message.warning(`Configuration save exception`, 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
modalStateFTP.confirmLoading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalFTPCancel() {
|
||||
modalStateFTP.openByEdit = false;
|
||||
modalStateFTPFrom.resetFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步文件到FTP
|
||||
* @param row
|
||||
*/
|
||||
function fnSyncFileToFTP(row: Record<string, any>) {
|
||||
modalStateFTP.confirmLoading = true;
|
||||
putFTPInfo(row.path)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(t('common.operateOk'), 3);
|
||||
} else {
|
||||
message.warning(res.msg, 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
modalStateFTP.confirmLoading = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -572,8 +477,10 @@ function fnSyncFileToFTP(row: Record<string, any>) {
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>Setting Remote Backup</template>
|
||||
<a-button type="text" @click.prevent="fnModalFTPVisibleByEdit()">
|
||||
<template #title>
|
||||
{{ t('views.ne.neConfigBackup.backupModal.title') }}
|
||||
</template>
|
||||
<a-button type="text" @click.prevent="fnFTPModalOpen()">
|
||||
<template #icon><DeliveredProcedureOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
@@ -632,12 +539,10 @@ function fnSyncFileToFTP(row: Record<string, any>) {
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>Send Current File To Remote Backup</template>
|
||||
<a-button
|
||||
type="link"
|
||||
:loading="modalStateFTP.confirmLoading"
|
||||
@click.prevent="fnSyncFileToFTP(record)"
|
||||
>
|
||||
<template #title>
|
||||
{{ t('views.ne.neConfigBackup.backupModal.pushFileOper') }}
|
||||
</template>
|
||||
<a-button type="link" @click.prevent="fnSyncFileToFTP(record)">
|
||||
<template #icon><CloudServerOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
@@ -676,7 +581,7 @@ function fnSyncFileToFTP(row: Record<string, any>) {
|
||||
<!-- 新增框或修改框 -->
|
||||
<ProModal
|
||||
:drag="true"
|
||||
:width="512"
|
||||
:width="520"
|
||||
:destroyOnClose="true"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
@@ -719,105 +624,8 @@ function fnSyncFileToFTP(row: Record<string, any>) {
|
||||
</a-form>
|
||||
</ProModal>
|
||||
|
||||
<!-- FTP -->
|
||||
<ProModal
|
||||
:drag="true"
|
||||
:width="800"
|
||||
:destroyOnClose="true"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:open="modalStateFTP.openByEdit"
|
||||
:title="modalStateFTP.title"
|
||||
:confirm-loading="modalStateFTP.confirmLoading"
|
||||
@ok="fnModalFTPOk"
|
||||
@cancel="fnModalFTPCancel"
|
||||
>
|
||||
<a-form
|
||||
name="modalStateFTPFrom"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 6 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-form-item label="Enable" name="enable" :label-col="{ span: 3 }">
|
||||
<a-switch
|
||||
v-model:checked="modalStateFTP.from.enable"
|
||||
:checked-children="t('common.switch.open')"
|
||||
:un-checked-children="t('common.switch.shut')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<template v-if="modalStateFTP.from.enable">
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
label="Service IP"
|
||||
name="toIp"
|
||||
v-bind="modalStateFTPFrom.validateInfos.toIp"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalStateFTP.from.toIp"
|
||||
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="Service Port"
|
||||
name="toPort"
|
||||
v-bind="modalStateFTPFrom.validateInfos.toPort"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="modalStateFTP.from.toPort"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
label="UserName"
|
||||
name="username"
|
||||
v-bind="modalStateFTPFrom.validateInfos.username"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalStateFTP.from.username"
|
||||
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="Password"
|
||||
name="password"
|
||||
v-bind="modalStateFTPFrom.validateInfos.password"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="modalStateFTP.from.password"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input-password>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item
|
||||
label="Save Dir"
|
||||
name="dir"
|
||||
v-bind="modalStateFTPFrom.validateInfos.dir"
|
||||
:label-col="{ span: 3 }"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalStateFTP.from.dir"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-form>
|
||||
</ProModal>
|
||||
<!-- FTP配置窗口 -->
|
||||
<BackupModal v-model:open="openFTPModal"></BackupModal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import useI18n from '@/hooks/useI18n';
|
||||
import ViewDrawer from './components/ViewDrawer.vue';
|
||||
import saveAs from 'file-saver';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { parseSizeFromFile } from '@/utils/parse-utils';
|
||||
const neInfoStore = useNeInfoStore();
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
@@ -79,16 +80,19 @@ let tableColumns: ColumnsType = [
|
||||
dataIndex: 'size',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
customRender(opt) {
|
||||
return parseSizeFromFile(opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.logManage.neFile.modifiedTime'),
|
||||
dataIndex: 'modifiedTime',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value * 1000);
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.logManage.neFile.fileName'),
|
||||
@@ -238,7 +242,7 @@ function fnGetList(pageNum?: number) {
|
||||
}
|
||||
queryParams.path = nePathArr.value.join('/');
|
||||
listNeFiles(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
const { total, rows } = res.data;
|
||||
tablePagination.total = total;
|
||||
tableState.data = rows;
|
||||
@@ -10,7 +10,6 @@ import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { delNeHost, listNeHost } from '@/api/ne/neHost';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { number } from 'echarts';
|
||||
const { getDict } = useDictStore();
|
||||
const { t } = useI18n();
|
||||
const EditModal = defineAsyncComponent(
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ProModal } from 'antdv-pro-modal';
|
||||
import { message, Form } from 'ant-design-vue/es';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { getOAMFile, saveOAMFile } from '@/api/ne/neInfo';
|
||||
import { getOAMFile, saveOAMFile, serviceNeAction } from '@/api/ne/neInfo';
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(['ok', 'cancel', 'update:open']);
|
||||
const props = defineProps({
|
||||
@@ -29,8 +29,8 @@ type ModalStateType = {
|
||||
openByEdit: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**是否同步 */
|
||||
sync: boolean;
|
||||
/**是否重启 */
|
||||
restart: boolean;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**确定按钮 loading */
|
||||
@@ -41,7 +41,7 @@ type ModalStateType = {
|
||||
let modalState: ModalStateType = reactive({
|
||||
openByEdit: false,
|
||||
title: 'OAM Configuration',
|
||||
sync: true,
|
||||
restart: false,
|
||||
from: {
|
||||
omcIP: '',
|
||||
oamEnable: true,
|
||||
@@ -114,12 +114,19 @@ function fnModalOk() {
|
||||
neType: props.neType,
|
||||
neId: props.neId,
|
||||
content: from,
|
||||
sync: modalState.sync,
|
||||
sync: true,
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(t('common.operateOk'), 3);
|
||||
emit('ok');
|
||||
if (modalState.restart) {
|
||||
serviceNeAction({
|
||||
neType: props.neType,
|
||||
neId: props.neId,
|
||||
action: 'restart',
|
||||
});
|
||||
}
|
||||
fnModalCancel();
|
||||
} else {
|
||||
message.error({
|
||||
@@ -145,6 +152,7 @@ function fnModalOk() {
|
||||
function fnModalCancel() {
|
||||
modalState.openByEdit = false;
|
||||
modalState.confirmLoading = false;
|
||||
modalState.restart = false;
|
||||
modalStateFrom.resetFields();
|
||||
emit('cancel');
|
||||
emit('update:open', false);
|
||||
@@ -183,15 +191,15 @@ watch(
|
||||
:labelWrap="true"
|
||||
>
|
||||
<a-form-item
|
||||
:label="t('views.ne.neInfo.oam.sync')"
|
||||
name="sync"
|
||||
:label="t('views.ne.neInfo.oam.restart')"
|
||||
name="restart"
|
||||
:label-col="{ span: 6 }"
|
||||
:labelWrap="true"
|
||||
>
|
||||
<a-switch
|
||||
:checked-children="t('common.switch.open')"
|
||||
:un-checked-children="t('common.switch.shut')"
|
||||
v-model:checked="modalState.sync"
|
||||
v-model:checked="modalState.restart"
|
||||
></a-switch>
|
||||
</a-form-item>
|
||||
|
||||
|
||||
412
src/views/neData/backup-data/index.vue
Normal file
412
src/views/neData/backup-data/index.vue
Normal file
@@ -0,0 +1,412 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||
import { Modal, message } from 'ant-design-vue/es';
|
||||
import BackupModal from '@/views/ne/neConfigBackup/components/BackupModal.vue';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import saveAs from 'file-saver';
|
||||
import { delFile, getFile, listFile } from '@/api/tool/file';
|
||||
import { parseSizeFromFile } from '@/utils/parse-utils';
|
||||
import { pushBackupFTP } from '@/api/neData/backup';
|
||||
const { t } = useI18n();
|
||||
|
||||
/**文件来源 */
|
||||
let sourceState = reactive({
|
||||
/**文件列表 */
|
||||
list: [
|
||||
{
|
||||
value: '/auth',
|
||||
label: t('views.neData.backupData.auth'),
|
||||
path: '/usr/local/omc/backup/udm_data',
|
||||
},
|
||||
{
|
||||
value: '/sub',
|
||||
label: t('views.neData.backupData.sub'),
|
||||
path: '/usr/local/omc/backup/udm_data',
|
||||
},
|
||||
{
|
||||
value: '/voip',
|
||||
label: t('views.neData.backupData.voip'),
|
||||
path: '/usr/local/omc/backup/udm_data',
|
||||
},
|
||||
{
|
||||
value: '/volte',
|
||||
label: t('views.neData.backupData.volte'),
|
||||
path: '/usr/local/omc/backup/udm_data',
|
||||
},
|
||||
],
|
||||
/**选择value */
|
||||
value: undefined,
|
||||
});
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**读取路径 */
|
||||
path: '',
|
||||
/**当前页数 */
|
||||
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',
|
||||
customRender(opt) {
|
||||
return parseSizeFromFile(opt.value);
|
||||
},
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.logManage.neFile.modifiedTime'),
|
||||
dataIndex: 'modifiedTime',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value);
|
||||
},
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
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);
|
||||
/**信息文件下载 */
|
||||
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);
|
||||
getFile(queryParams.path, 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;
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**删除触发等待 */
|
||||
let delLoading = ref<boolean>(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() {
|
||||
delLoading.value = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
delFile(queryParams.path, row.fileName)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', {
|
||||
msg: t('common.deleteText'),
|
||||
}),
|
||||
duration: 2,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: t('views.logManage.exportFile.deleteTipErr'),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
delLoading.value = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**网元类型选择对应修改 */
|
||||
function fnNeChange(_: any, opt: any) {
|
||||
queryParams.path = `${opt.path}${opt.value}`;
|
||||
ftpInfo.path = queryParams.path;
|
||||
ftpInfo.tag = opt.value;
|
||||
fnGetList(1);
|
||||
}
|
||||
|
||||
/**查询备份信息列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (queryParams.path === '') {
|
||||
message.warning({
|
||||
content: t('views.logManage.exportFile.fileSourcePlease'),
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
listFile(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
const { total, rows } = res.data;
|
||||
tablePagination.total = total;
|
||||
tableState.data = 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;
|
||||
});
|
||||
}
|
||||
|
||||
/**打开FTP配置窗口 */
|
||||
const openFTPModal = ref<boolean>(false);
|
||||
function fnFTPModalOpen() {
|
||||
openFTPModal.value = !openFTPModal.value;
|
||||
}
|
||||
|
||||
type FTPInfoType = {
|
||||
path: string;
|
||||
tag: string;
|
||||
fileName: string;
|
||||
};
|
||||
const ftpInfo = reactive<FTPInfoType>({
|
||||
path: '',
|
||||
tag: '',
|
||||
fileName: '',
|
||||
});
|
||||
|
||||
/**同步文件到FTP */
|
||||
function fnSyncFileToFTP(fileName: string) {
|
||||
ftpInfo.fileName = fileName;
|
||||
pushBackupFTP(toRaw(ftpInfo)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(t('common.operateOk'), 3);
|
||||
} else {
|
||||
message.warning(res.msg, 3);
|
||||
}
|
||||
});
|
||||
}
|
||||
</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" align="middle">
|
||||
<a-col>
|
||||
<span>{{ t('views.logManage.exportFile.fileSource') }}:</span
|
||||
>
|
||||
<a-select
|
||||
v-model:value="sourceState.value"
|
||||
:options="sourceState.list"
|
||||
@change="fnNeChange"
|
||||
:allow-clear="false"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
style="width: 200px"
|
||||
/>
|
||||
</a-col>
|
||||
<template v-if="queryParams.path">
|
||||
<span>{{ t('views.logManage.neFile.nePath') }}:</span>
|
||||
<a-col>
|
||||
<a-breadcrumb>
|
||||
<a-breadcrumb-item>
|
||||
{{ queryParams.path }}
|
||||
</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
</a-col>
|
||||
</template>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>
|
||||
{{ t('views.ne.neConfigBackup.backupModal.title') }}
|
||||
</template>
|
||||
<a-button type="text" @click.prevent="fnFTPModalOpen()">
|
||||
<template #icon><DeliveredProcedureOutlined /></template>
|
||||
</a-button>
|
||||
</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-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-tooltip placement="topRight" v-if="record.fileType === 'file'">
|
||||
<template #title>
|
||||
{{ t('views.ne.neConfigBackup.backupModal.pushFileOper') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnSyncFileToFTP(record.fileName)"
|
||||
>
|
||||
<template #icon><CloudServerOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="topRight" v-if="record.fileType === 'file'">
|
||||
<template #title>{{ t('common.downloadText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
:loading="downLoading"
|
||||
@click.prevent="fnDownloadFile(record)"
|
||||
>
|
||||
<template #icon><DownloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="topRight" v-if="record.fileType === 'file'">
|
||||
<template #title>{{ t('common.deleteText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
:loading="delLoading"
|
||||
@click.prevent="fnRecordDelete(record)"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- FTP配置窗口 -->
|
||||
<BackupModal v-model:open="openFTPModal"></BackupModal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -910,6 +910,7 @@ onMounted(() => {
|
||||
|
||||
<!-- 状态历史框 -->
|
||||
<HistoryModal
|
||||
v-if="neTypeAndId.length > 1"
|
||||
v-model:open="modalState.openByHistory"
|
||||
:title="t('views.neData.baseStation.history')"
|
||||
:ne-type="neTypeAndId[0]"
|
||||
@@ -545,9 +545,13 @@ function fnExportList(type: string) {
|
||||
}
|
||||
|
||||
/**查询列表, pageNum初始页数 */
|
||||
function fnGetList() {
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
tablePagination.current = pageNum;
|
||||
}
|
||||
listRules(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 取消勾选
|
||||
@@ -705,6 +709,7 @@ onMounted(() => {
|
||||
v-model:value="queryParams.neId"
|
||||
:options="neOtions"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
@change="fnGetList(1)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
@@ -831,7 +836,7 @@ onMounted(() => {
|
||||
/>
|
||||
</a-tooltip>
|
||||
<TableColumnsDnd
|
||||
cache-id="pcfData"
|
||||
cache-id="pcfSubData"
|
||||
:columns="tableColumns"
|
||||
v-model:columns-dnd="tableColumnsDnd"
|
||||
></TableColumnsDnd>
|
||||
@@ -1190,7 +1195,6 @@ onMounted(() => {
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-textarea
|
||||
:disabled="true"
|
||||
:hidden="!uploadImportState.msg"
|
||||
@@ -780,11 +780,12 @@ onMounted(() => {
|
||||
<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.neUser.auth.neType')" name="neId ">
|
||||
<a-form-item label="UDM" name="neId ">
|
||||
<a-select
|
||||
v-model:value="queryParams.neId"
|
||||
:options="neOtions"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
:disabled="modalState.loadDataLoading"
|
||||
@change="fnGetList(1)"
|
||||
/>
|
||||
</a-form-item>
|
||||
@@ -1268,7 +1269,6 @@ onMounted(() => {
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-input-password
|
||||
v-if="uploadImportState.from.typeVal === 'k4'"
|
||||
v-model:value="uploadImportState.from.typeData"
|
||||
@@ -1282,11 +1282,12 @@ onMounted(() => {
|
||||
<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.neUser.sub.neType')" name="neId ">
|
||||
<a-form-item label="UDM" name="neId ">
|
||||
<a-select
|
||||
v-model:value="queryParams.neId"
|
||||
:options="neOtions"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
:disabled="modalState.loadDataLoading"
|
||||
@change="fnGetList(1)"
|
||||
/>
|
||||
</a-form-item>
|
||||
@@ -2284,7 +2285,6 @@ onMounted(() => {
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-alert
|
||||
:message="uploadImportState.msg"
|
||||
:type="uploadImportState.hasFail ? 'warning' : 'info'"
|
||||
944
src/views/neData/udm-voip/index.vue
Normal file
944
src/views/neData/udm-voip/index.vue
Normal file
@@ -0,0 +1,944 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { ProModal } from 'antdv-pro-modal';
|
||||
import {
|
||||
message,
|
||||
Modal,
|
||||
Form,
|
||||
TableColumnsType,
|
||||
notification,
|
||||
} from 'ant-design-vue';
|
||||
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||
import UploadModal from '@/components/UploadModal/index.vue';
|
||||
import {
|
||||
addUDMVOIP,
|
||||
batchAddUDMVOIP,
|
||||
batchDelUDMVOIP,
|
||||
delUDMVOIP,
|
||||
exportUDMVOIP,
|
||||
importUDMVOIP,
|
||||
listUDMVOIP,
|
||||
resetUDMVOIP,
|
||||
} from '@/api/neData/udm_voip';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { uploadFileToNE } from '@/api/tool/file';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
/**网元参数 */
|
||||
let neOtions = ref<Record<string, any>[]>([]);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**网元ID */
|
||||
neId: undefined,
|
||||
/**用户名 */
|
||||
username: '',
|
||||
/**排序字段 */
|
||||
sortField: 'username',
|
||||
/**排序方式 */
|
||||
sortOrder: 'asc',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
username: '',
|
||||
sortField: 'username',
|
||||
sortOrder: 'asc',
|
||||
});
|
||||
fnGetList(1);
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'small',
|
||||
seached: true,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns = ref<TableColumnsType>([
|
||||
{
|
||||
title: t('views.neData.udmVOIP.username'),
|
||||
dataIndex: 'username',
|
||||
sorter: true,
|
||||
align: 'left',
|
||||
resizable: true,
|
||||
width: 250,
|
||||
minWidth: 100,
|
||||
maxWidth: 300,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
align: 'left',
|
||||
},
|
||||
]);
|
||||
|
||||
/**表格字段列排序 */
|
||||
let tableColumnsDnd = ref<TableColumnsType>([]);
|
||||
|
||||
/**表格分页器参数 */
|
||||
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();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格分页、排序、筛选变化时触发操作, 排序方式,取值为 ascend descend */
|
||||
function fnTableChange(pagination: any, filters: any, sorter: any, extra: any) {
|
||||
const { field, order } = sorter;
|
||||
if (order) {
|
||||
queryParams.sortField = field;
|
||||
queryParams.sortOrder = order.replace('end', '');
|
||||
} else {
|
||||
queryParams.sortOrder = 'asc';
|
||||
}
|
||||
fnGetList(1);
|
||||
}
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**新增框或修改框是否显示 */
|
||||
openByEdit: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**是否批量操作 */
|
||||
isBatch: boolean;
|
||||
/**操作类型 */
|
||||
type: 'delete' | 'add';
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
/**更新加载数据按钮 loading */
|
||||
loadDataLoading: boolean;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
openByEdit: false,
|
||||
title: 'UDMVOIP',
|
||||
from: {
|
||||
num: 1,
|
||||
username: undefined,
|
||||
password: undefined,
|
||||
},
|
||||
isBatch: false,
|
||||
type: 'add',
|
||||
confirmLoading: false,
|
||||
loadDataLoading: false,
|
||||
});
|
||||
|
||||
/**对话框内表单属性和校验规则 */
|
||||
const modalStateFrom = Form.useForm(
|
||||
modalState.from,
|
||||
reactive({
|
||||
num: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.neData.common.batchNum'),
|
||||
},
|
||||
],
|
||||
username: [
|
||||
{ required: true, message: t('views.neData.udmVOIP.usernamePlease') },
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: t('views.neData.udmVOIP.passwordPlease') },
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param noticeId 网元id, 不传为新增
|
||||
*/
|
||||
function fnModalVisibleByEdit(row?: Record<string, any>) {
|
||||
modalState.isBatch = false;
|
||||
if (!row) {
|
||||
modalStateFrom.resetFields(); //重置表单
|
||||
modalState.title = t('views.neData.udmVOIP.addTitle');
|
||||
modalState.openByEdit = true;
|
||||
modalState.type = 'add';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalOk() {
|
||||
const from = JSON.parse(JSON.stringify(modalState.from));
|
||||
from.neId = queryParams.neId || '-';
|
||||
from.username = `${from.username}`;
|
||||
|
||||
// 校验规则
|
||||
let validateArr = ['username', 'password'];
|
||||
if (modalState.isBatch) {
|
||||
validateArr.push('num');
|
||||
if (modalState.type === 'delete') {
|
||||
validateArr = ['num', 'username'];
|
||||
}
|
||||
}
|
||||
modalStateFrom
|
||||
.validate(validateArr)
|
||||
.then(e => {
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
|
||||
// 根据类型选择函数
|
||||
let result: any = null;
|
||||
if (modalState.isBatch) {
|
||||
if (modalState.type === 'add') {
|
||||
result = batchAddUDMVOIP(
|
||||
from.neId,
|
||||
{ username: from.username, password: from.password },
|
||||
from.num
|
||||
);
|
||||
}
|
||||
if (modalState.type === 'delete') {
|
||||
result = batchDelUDMVOIP(from.neId, from.username, from.num);
|
||||
}
|
||||
} else {
|
||||
if (modalState.type === 'add') {
|
||||
result = addUDMVOIP(from.neId, {
|
||||
username: from.username,
|
||||
password: from.password,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
.then((res: any) => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.operateOk'),
|
||||
duration: 3,
|
||||
});
|
||||
|
||||
fnModalCancel();
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.type = 'add';
|
||||
modalState.isBatch = false;
|
||||
modalState.openByEdit = false;
|
||||
modalStateFrom.resetFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 批量操作
|
||||
* @param type 类型
|
||||
*/
|
||||
function fnModalVisibleByBatch(type: 'delete' | 'add') {
|
||||
modalStateFrom.resetFields(); //重置表单
|
||||
modalState.isBatch = true;
|
||||
modalState.type = type;
|
||||
if (type === 'add') {
|
||||
modalState.title = t('views.neData.common.batchAddText');
|
||||
modalState.openByEdit = true;
|
||||
}
|
||||
if (type === 'delete') {
|
||||
modalState.title = t('views.neData.common.batchDelText');
|
||||
modalState.openByEdit = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录删除
|
||||
* @param username 网元编号ID
|
||||
*/
|
||||
function fnRecordDelete(username: string) {
|
||||
const neID = queryParams.neId;
|
||||
if (!neID) return;
|
||||
let msg = username;
|
||||
if (username === '0') {
|
||||
msg = `${tableState.selectedRowKeys[0]}... ${tableState.selectedRowKeys.length}`;
|
||||
username = tableState.selectedRowKeys.join(',');
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.neData.udmVOIP.delTip', { num: msg }),
|
||||
onOk() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
delUDMVOIP(neID, username)
|
||||
.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();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**列表导出 */
|
||||
function fnExportList(type: string) {
|
||||
const neId = queryParams.neId;
|
||||
if (!neId) return;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
exportUDMVOIP(Object.assign({ type: type }, queryParams))
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.operateOk'),
|
||||
duration: 2,
|
||||
});
|
||||
saveAs(res.data, `UDM_VOIP_${neId}_${Date.now()}.${type}`);
|
||||
} 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;
|
||||
tablePagination.current = pageNum;
|
||||
}
|
||||
listUDMVOIP(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
const { total, rows } = res.data;
|
||||
tablePagination.total = total;
|
||||
tableState.data = rows;
|
||||
} else {
|
||||
tableState.data = [];
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**重新加载数据 */
|
||||
function fnLoadData() {
|
||||
const neId = queryParams.neId;
|
||||
if (tableState.loading || !neId) return;
|
||||
modalState.loadDataLoading = true;
|
||||
tablePagination.total = 0;
|
||||
tableState.data = [];
|
||||
tableState.loading = true; // 表格loading
|
||||
resetUDMVOIP(neId).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
const num = res.data;
|
||||
const timerS = Math.ceil(+num / 800) + 3;
|
||||
notification.success({
|
||||
message: t('views.neData.common.loadData'),
|
||||
description: t('views.neData.common.loadDataTip', {
|
||||
num,
|
||||
timer: timerS,
|
||||
}),
|
||||
duration: timerS,
|
||||
});
|
||||
// 延迟10s后关闭loading刷新列表
|
||||
setTimeout(() => {
|
||||
modalState.loadDataLoading = false;
|
||||
tableState.loading = false; // 表格loading
|
||||
fnQueryReset();
|
||||
}, timerS * 1000);
|
||||
} else {
|
||||
modalState.loadDataLoading = false;
|
||||
tableState.loading = false; // 表格loading
|
||||
fnQueryReset();
|
||||
message.error({
|
||||
content: t('common.getInfoFail'),
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**对话框表格信息导入对象信息状态类型 */
|
||||
type ModalUploadImportStateType = {
|
||||
/**是否显示 */
|
||||
open: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**是否上传中 */
|
||||
loading: boolean;
|
||||
/**上传结果信息 */
|
||||
msg: string;
|
||||
/**含失败信息 */
|
||||
hasFail: boolean;
|
||||
};
|
||||
|
||||
/**对话框表格信息导入对象信息状态 */
|
||||
let uploadImportState: ModalUploadImportStateType = reactive({
|
||||
open: false,
|
||||
title: t('components.UploadModal.uploadTitle'),
|
||||
loading: false,
|
||||
msg: '',
|
||||
hasFail: false,
|
||||
});
|
||||
|
||||
/**对话框表格信息导入弹出窗口 */
|
||||
function fnModalUploadImportOpen() {
|
||||
uploadImportState.msg = '';
|
||||
uploadImportState.hasFail = false;
|
||||
uploadImportState.loading = false;
|
||||
uploadImportState.open = true;
|
||||
}
|
||||
|
||||
/**对话框表格信息导入关闭窗口 */
|
||||
function fnModalUploadImportClose() {
|
||||
uploadImportState.open = false;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**对话框表格信息导入上传 */
|
||||
function fnModalUploadImportUpload(file: File) {
|
||||
const neID = queryParams.neId;
|
||||
if (!neID) {
|
||||
return Promise.reject('Unknown network element');
|
||||
}
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
uploadImportState.loading = true;
|
||||
uploadFileToNE('UDM', neID, file, 5)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
return importUDMVOIP({
|
||||
neId: neID,
|
||||
uploadPath: res.data,
|
||||
});
|
||||
}
|
||||
return res;
|
||||
})
|
||||
.then(res => {
|
||||
if (!res) return;
|
||||
uploadImportState.msg = res.msg;
|
||||
const regex = /fail num: (\d+)/;
|
||||
const match = res.msg.match(regex);
|
||||
if (match) {
|
||||
const failNum = Number(match[1]);
|
||||
uploadImportState.hasFail = failNum > 0;
|
||||
} else {
|
||||
uploadImportState.hasFail = false;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
uploadImportState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**对话框表格信息导入模板 */
|
||||
function fnModalDownloadImportTemplate() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
|
||||
const baseUrl = import.meta.env.VITE_HISTORY_BASE_URL;
|
||||
const templateUrl = `${
|
||||
baseUrl.length === 1 && baseUrl.indexOf('/') === 0
|
||||
? ''
|
||||
: baseUrl.indexOf('/') === -1
|
||||
? '/' + baseUrl
|
||||
: baseUrl
|
||||
}/neDataImput`;
|
||||
saveAs(
|
||||
`${templateUrl}/udm_voip_template.txt`,
|
||||
`import_udmvoip_template_${Date.now()}.txt`
|
||||
);
|
||||
|
||||
hide();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取网元网元列表
|
||||
useNeInfoStore()
|
||||
.fnNelist()
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data?.length > 0) {
|
||||
let arr: Record<string, any>[] = [];
|
||||
res.data.forEach((v: any) => {
|
||||
if (v.neType === 'UDM') {
|
||||
arr.push({ value: v.neId, label: v.neName });
|
||||
}
|
||||
});
|
||||
neOtions.value = arr;
|
||||
if (arr.length > 0) {
|
||||
queryParams.neId = arr[0].value;
|
||||
}
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
// 获取列表数据
|
||||
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="UDM" name="neId ">
|
||||
<a-select
|
||||
v-model:value="queryParams.neId"
|
||||
:options="neOtions"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
:disabled="modalState.loadDataLoading"
|
||||
@change="fnGetList(1)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.neData.udmVOIP.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="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList()">
|
||||
<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="fnRecordDelete('0')"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
{{ t('views.neData.common.checkDel') }}
|
||||
</a-button>
|
||||
|
||||
<a-dropdown trigger="click">
|
||||
<a-button>
|
||||
{{ t('views.neData.common.batchOper') }}
|
||||
<DownOutlined />
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu @click="({ key }:any) => fnModalVisibleByBatch(key)">
|
||||
<a-menu-item key="add">
|
||||
<PlusOutlined />
|
||||
{{ t('views.neData.common.batchAddText') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="delete">
|
||||
<DeleteOutlined />
|
||||
{{ t('views.neData.common.batchDelText') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
|
||||
<a-popconfirm
|
||||
placement="topRight"
|
||||
:title="t('views.neData.common.loadDataConfirm')"
|
||||
:ok-text="t('common.ok')"
|
||||
:cancel-text="t('common.cancel')"
|
||||
:disabled="modalState.loadDataLoading"
|
||||
@confirm="fnLoadData"
|
||||
>
|
||||
<a-button
|
||||
type="dashed"
|
||||
danger
|
||||
:disabled="modalState.loadDataLoading"
|
||||
:loading="modalState.loadDataLoading"
|
||||
>
|
||||
<template #icon><SyncOutlined /></template>
|
||||
{{ t('views.neData.common.loadData') }}
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
|
||||
<a-button type="dashed" @click.prevent="fnModalUploadImportOpen">
|
||||
<template #icon><ImportOutlined /></template>
|
||||
{{ t('common.import') }}
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
placement="topRight"
|
||||
:title="t('views.neData.udmVOIP.exportTip')"
|
||||
ok-text="TXT"
|
||||
ok-type="default"
|
||||
@confirm="fnExportList('txt')"
|
||||
>
|
||||
<a-button type="dashed">
|
||||
<template #icon><ExportOutlined /></template>
|
||||
{{ t('common.export') }}
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>
|
||||
{{
|
||||
tableState.seached
|
||||
? t('common.switch.show')
|
||||
: t('common.switch.hide')
|
||||
}}
|
||||
</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
:checked-children="t('common.searchBarText')"
|
||||
:un-checked-children="t('common.searchBarText')"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<TableColumnsDnd
|
||||
cache-id="udmVOIPData"
|
||||
:columns="tableColumns"
|
||||
v-model:columns-dnd="tableColumnsDnd"
|
||||
></TableColumnsDnd>
|
||||
<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-tooltip placement="topRight">
|
||||
<template #title>{{ t('common.sizeText') }}</template>
|
||||
<a-dropdown placement="bottomRight" 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="username"
|
||||
:columns="tableColumnsDnd"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ y: 'calc(100vh - 480px)' }"
|
||||
@change="fnTableChange"
|
||||
@resizeColumn="(w:number, col:any) => (col.width = w)"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
selectedRowKeys: tableState.selectedRowKeys,
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column?.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.deleteText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordDelete(record.username)"
|
||||
>
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 新增框或修改框 -->
|
||||
<ProModal
|
||||
:drag="true"
|
||||
:width="520"
|
||||
:destroyOnClose="true"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:open="modalState.openByEdit"
|
||||
:title="modalState.title"
|
||||
:confirm-loading="modalState.confirmLoading"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form
|
||||
name="modalStateFrom"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 6 }"
|
||||
:labelWrap="true"
|
||||
>
|
||||
<!--批量删除-->
|
||||
<template v-if="modalState.isBatch && modalState.type === 'delete'">
|
||||
<a-form-item
|
||||
:label="t('views.neData.common.batchNum')"
|
||||
name="num"
|
||||
v-bind="modalStateFrom.validateInfos.num"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="modalState.from.num"
|
||||
style="width: 100%"
|
||||
:min="1"
|
||||
:max="500"
|
||||
placeholder="<=500"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="
|
||||
modalState.isBatch
|
||||
? t('views.neData.udmVOIP.startUsername')
|
||||
: t('views.neData.udmVOIP.username')
|
||||
"
|
||||
name="username"
|
||||
v-bind="modalStateFrom.validateInfos.username"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="modalState.from.username"
|
||||
style="width: 100%"
|
||||
:min="4"
|
||||
:maxlangth="16"
|
||||
:placeholder="t('views.neData.udmVOIP.username')"
|
||||
>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<!--批量数-->
|
||||
<a-form-item
|
||||
v-if="modalState.isBatch"
|
||||
:label="t('views.neData.common.batchNum')"
|
||||
name="num"
|
||||
v-bind="modalStateFrom.validateInfos.num"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="modalState.from.num"
|
||||
style="width: 100%"
|
||||
:min="1"
|
||||
:max="500"
|
||||
placeholder="<=500"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="
|
||||
modalState.isBatch
|
||||
? t('views.neData.udmVOIP.startUsername')
|
||||
: t('views.neData.udmVOIP.username')
|
||||
"
|
||||
name="username"
|
||||
v-bind="modalStateFrom.validateInfos.username"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="modalState.from.username"
|
||||
style="width: 100%"
|
||||
:min="4"
|
||||
:maxlength="16"
|
||||
:placeholder="t('views.neData.udmVOIP.username')"
|
||||
>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('views.neData.udmVOIP.password')"
|
||||
name="password"
|
||||
v-bind="modalStateFrom.validateInfos.password"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="modalState.from.password"
|
||||
style="width: 100%"
|
||||
:min="4"
|
||||
:max="64"
|
||||
:placeholder="t('views.neData.udmVOIP.password')"
|
||||
>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-form>
|
||||
</ProModal>
|
||||
|
||||
<!-- 上传导入表格数据文件框 -->
|
||||
<UploadModal
|
||||
:title="uploadImportState.title"
|
||||
:loading="uploadImportState.loading"
|
||||
@upload="fnModalUploadImportUpload"
|
||||
@close="fnModalUploadImportClose"
|
||||
v-model:open="uploadImportState.open"
|
||||
:ext="['.txt']"
|
||||
>
|
||||
<template #default>
|
||||
<a-row justify="space-between" align="middle">
|
||||
<a-col :span="12"> </a-col>
|
||||
<a-col>
|
||||
<a-button
|
||||
type="link"
|
||||
:title="t('views.neData.common.importTemplate')"
|
||||
@click.prevent="fnModalDownloadImportTemplate"
|
||||
>
|
||||
{{ t('views.neData.common.importTemplate') }}
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-textarea
|
||||
:disabled="true"
|
||||
:hidden="!uploadImportState.msg"
|
||||
:value="uploadImportState.msg"
|
||||
:auto-size="{ minRows: 2, maxRows: 8 }"
|
||||
style="background-color: transparent; color: rgba(0, 0, 0, 0.85)"
|
||||
/>
|
||||
</template>
|
||||
</UploadModal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
1125
src/views/neData/udm-volte/index.vue
Normal file
1125
src/views/neData/udm-volte/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
import { GlobalFooter } from 'antdv-pro-layout';
|
||||
import { Modal, message } from 'ant-design-vue/es';
|
||||
import { computed, onMounted, reactive, toRaw } from 'vue';
|
||||
import { register } from '@/api/login';
|
||||
import { register } from '@/api/auth';
|
||||
import { regExpPasswd, regExpUserName } from '@/utils/regular-utils';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
@@ -272,7 +272,7 @@ function fnUnlock() {
|
||||
hide();
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('views.system.log.login.unlockSure', {
|
||||
content: t('views.system.log.login.unlockSuss', {
|
||||
userName: username,
|
||||
}),
|
||||
duration: 3,
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
import { stepState, fnToStepName } from '../hooks/useStep';
|
||||
import { Modal } from 'ant-design-vue/es';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { removeToken } from '@/plugins/auth-token';
|
||||
import { delAccessToken, delRefreshToken } from '@/plugins/auth-token';
|
||||
import { useRouter } from 'vue-router';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { bootloaderDone } from '@/api/system/quick-start/bootloader';
|
||||
@@ -56,8 +55,9 @@ function fnGuideDone() {
|
||||
bootloaderDone()
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
removeToken();
|
||||
// useAppStore().bootloader = false;
|
||||
delAccessToken();
|
||||
delRefreshToken();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
import { onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { TOKEN_RESPONSE_FIELD } from '@/constants/token-constants';
|
||||
import { getToken, setToken } from '@/plugins/auth-token';
|
||||
import { getAccessToken, setAccessToken } from '@/plugins/auth-token';
|
||||
import { bootloaderStart } from '@/api/system/quick-start/bootloader';
|
||||
import { fnToStepName } from '../hooks/useStep';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
@@ -19,11 +18,10 @@ function fnChangeLocale(e: any) {
|
||||
|
||||
/**引导开始 */
|
||||
function fnGuideStart() {
|
||||
if (getToken()) return;
|
||||
if (getAccessToken()) return;
|
||||
bootloaderStart().then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
const token = res.data[TOKEN_RESPONSE_FIELD];
|
||||
setToken(token);
|
||||
setAccessToken(res.data.accessToken, res.data.expiresIn);
|
||||
} else {
|
||||
router.push({ name: 'Login' });
|
||||
}
|
||||
|
||||
@@ -32,8 +32,10 @@ import { hasPermissions } from '@/plugins/auth-user';
|
||||
import { MENU_PATH_INLINE } from '@/constants/menu-constants';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
const { t } = useI18n();
|
||||
const { getDict } = useDictStore();
|
||||
const userStore = useUserStore();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const routePath = route.path;
|
||||
@@ -732,7 +734,7 @@ function fnGetList(pageNum?: number) {
|
||||
queryParams.beginTime = queryRangePicker.value[0];
|
||||
queryParams.endTime = queryRangePicker.value[1];
|
||||
listRole(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
@@ -941,6 +943,7 @@ onMounted(() => {
|
||||
v-if="
|
||||
dict.sysNormalDisable.length > 0 &&
|
||||
record.roleId !== 1 &&
|
||||
!userStore.roles?.includes(record.roleKey) &&
|
||||
hasPermissions(['system:role:edit'])
|
||||
"
|
||||
v-model:checked="record.statusFlag"
|
||||
@@ -969,7 +972,12 @@ onMounted(() => {
|
||||
<template #icon><ProfileOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="record.roleId !== 1">
|
||||
<a-tooltip
|
||||
v-if="
|
||||
record.roleId !== 1 &&
|
||||
!userStore.roles?.includes(record.roleKey)
|
||||
"
|
||||
>
|
||||
<template #title>{{ t('common.editText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@@ -979,7 +987,12 @@ onMounted(() => {
|
||||
<template #icon><FormOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="record.roleId !== 1">
|
||||
<a-tooltip
|
||||
v-if="
|
||||
record.roleId !== 1 &&
|
||||
!userStore.roles?.includes(record.roleKey)
|
||||
"
|
||||
>
|
||||
<template #title>{{ t('common.deleteText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@@ -1001,7 +1014,7 @@ onMounted(() => {
|
||||
<template #icon><SecurityScanOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="topRight" v-if="record.roleId !== 1">
|
||||
<a-tooltip placement="topRight" v-if="false">
|
||||
<template #title>{{
|
||||
t('views.system.role.distributeUser')
|
||||
}}</template>
|
||||
|
||||
@@ -29,7 +29,6 @@ const password = ref('');
|
||||
/**解锁 */
|
||||
function handleUnlock() {
|
||||
if (maskStore.lockPasswd === password.value) {
|
||||
message.success(t('components.LockScreen.validSucc'), 3);
|
||||
password.value = '';
|
||||
maskStore.handleMaskType('none');
|
||||
const redirectPath = route.query?.redirect || '/index';
|
||||
@@ -79,7 +78,7 @@ onMounted(() => {
|
||||
</span>
|
||||
</div>
|
||||
<div class="lock-screen_login-from">
|
||||
<a-input-group compact>
|
||||
<a-input-group compact v-if="maskStore.lockPasswd">
|
||||
<a-input
|
||||
type="password"
|
||||
v-model:value="password"
|
||||
@@ -92,6 +91,10 @@ onMounted(() => {
|
||||
<LoginOutlined />
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<a-button type="primary" block @click="handleUnlock" v-else>
|
||||
{{ t('components.LockScreen.enter') }}
|
||||
</a-button>
|
||||
|
||||
<a-button type="text" class="logout" @click="handleBackLogin">
|
||||
{{ t('components.LockScreen.backLogin') }}
|
||||
</a-button>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Modal, message } from 'ant-design-vue/es';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { getNeDirZip, getNeFile, listNeFiles } from '@/api/tool/neFile';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import ViewDrawer from '@/views/logManage/neFile/components/ViewDrawer.vue';
|
||||
import ViewDrawer from '@/views/ne/neFile/components/ViewDrawer.vue';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import useTabsStore from '@/store/modules/tabs';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
|
||||
@@ -1,35 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, ref, watch } from 'vue';
|
||||
import { onBeforeUnmount, onMounted, reactive, ref, toRaw } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { message, Modal } from 'ant-design-vue/es';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||
import DissectionTree from '../tshark/components/DissectionTree.vue';
|
||||
import DissectionDump from '../tshark/components/DissectionDump.vue';
|
||||
import PacketTable from '../tshark/components/PacketTable.vue';
|
||||
import { usePCAP, NO_SELECTION } from '../tshark/hooks/usePCAP';
|
||||
import {
|
||||
RESULT_CODE_ERROR,
|
||||
RESULT_CODE_SUCCESS,
|
||||
} from '@/constants/result-constants';
|
||||
import { filePullTask } from '@/api/trace/task';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import { filePullTask, getTraceData, listTraceData } from '@/api/trace/task';
|
||||
import { scriptUrl as wkUrl } from '@/assets/js/wiregasm_worker';
|
||||
import * as wkUtil from '@/plugins/wk-worker';
|
||||
import * as wsUtil from '@/plugins/ws-websocket';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import useTabsStore from '@/store/modules/tabs';
|
||||
import saveAs from 'file-saver';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
const { getDict } = useDictStore();
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const tabsStore = useTabsStore();
|
||||
const ws = new WS();
|
||||
const { t } = useI18n();
|
||||
const {
|
||||
state,
|
||||
handleSelectedTreeEntry,
|
||||
handleSelectedFindSelection,
|
||||
handleSelectedFrame,
|
||||
handleScrollBottom,
|
||||
handleFilterFrames,
|
||||
handleLoadFile,
|
||||
} = usePCAP();
|
||||
const ws = new wsUtil.WS();
|
||||
const wk = new wkUtil.WK();
|
||||
|
||||
/**跟踪编号 */
|
||||
const traceId = ref<string>(route.query.traceId as string);
|
||||
@@ -44,6 +41,17 @@ function fnClose() {
|
||||
}
|
||||
}
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**跟踪消息类型 */
|
||||
traceMsgType: DictType[];
|
||||
/**跟踪消息方向 */
|
||||
traceMsgDirect: DictType[];
|
||||
} = reactive({
|
||||
traceMsgType: [],
|
||||
traceMsgDirect: [],
|
||||
});
|
||||
|
||||
/**下载触发等待 */
|
||||
let downLoading = ref<boolean>(false);
|
||||
|
||||
@@ -82,15 +90,266 @@ function fnDownloadFile() {
|
||||
});
|
||||
}
|
||||
|
||||
/**获取PCAP文件 */
|
||||
function fnFilePCAP() {
|
||||
filePullTask(traceId.value).then(res => {
|
||||
// =========== 表格数据 ==============
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**点击选择行 */
|
||||
row: Record<string, any>;
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'small',
|
||||
data: [],
|
||||
row: {},
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('views.traceManage.task.msgNe'),
|
||||
dataIndex: 'msgNe',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.task.rowTime'),
|
||||
dataIndex: 'timestamp',
|
||||
align: 'left',
|
||||
width: 250,
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
const nanoseconds = opt.value; // 纳秒时间戳
|
||||
const milliseconds = Math.floor(nanoseconds / 1000000);
|
||||
const nanosecondsPart = (nanoseconds % 1000000)
|
||||
.toString()
|
||||
.padStart(6, '0');
|
||||
|
||||
return `${parseDateToStr(
|
||||
milliseconds,
|
||||
'YYYY-MM-DD HH:mm:ss.SSS'
|
||||
)}.${nanosecondsPart}`;
|
||||
},
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.task.protocolOrInterface'),
|
||||
dataIndex: 'ifType',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.task.msgEvent'),
|
||||
dataIndex: 'msgEvent',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.task.msgType'),
|
||||
dataIndex: 'msgType',
|
||||
key: 'msgType',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.task.msgDirect'),
|
||||
dataIndex: 'msgDirect',
|
||||
key: 'msgDirect',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.task.srcIp'),
|
||||
dataIndex: 'srcAddr',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.task.dstIp'),
|
||||
dataIndex: 'dstAddr',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 10,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 10,
|
||||
/**指定每页可以显示多少条 */
|
||||
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();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格分页、排序、筛选变化时触发操作, 排序方式,取值为 ascend descend */
|
||||
function fnTableChange(pagination: any, filters: any, sorter: any, extra: any) {
|
||||
const { field, order } = sorter;
|
||||
if (order) {
|
||||
queryParams.sortBy = field;
|
||||
queryParams.sortOrder = order.replace('end', '');
|
||||
} else {
|
||||
queryParams.sortOrder = 'asc';
|
||||
}
|
||||
fnGetList(1);
|
||||
}
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
traceId: traceId.value,
|
||||
sortBy: 'timestamp',
|
||||
sortOrder: 'asc',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 10,
|
||||
});
|
||||
/**查询备份信息列表, 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) {
|
||||
handleLoadFile(res.data);
|
||||
const { total, rows } = res.data;
|
||||
tablePagination.total = total;
|
||||
tableState.data = rows;
|
||||
if (
|
||||
tablePagination.total <=
|
||||
(queryParams.pageNum - 1) * tablePagination.pageSize &&
|
||||
queryParams.pageNum !== 1
|
||||
) {
|
||||
tableState.loading = false;
|
||||
fnGetList(queryParams.pageNum - 1);
|
||||
}
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看帧数据
|
||||
* @param row 记录信息
|
||||
*/
|
||||
function fnVisible(row: Record<string, any>) {
|
||||
// 选中行重复点击时显示隐藏
|
||||
if (row.id === tableState.row.id && state.selectedFrame !== 0) {
|
||||
state.selectedFrame = 0;
|
||||
return;
|
||||
}
|
||||
getTraceData(row.id).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
Object.assign(tableState.row, res.data);
|
||||
const blob = generatePCAP(res.data.timestamp / 1e3, res.data.rawMsg);
|
||||
wk.send({ type: 'process', file: blob });
|
||||
}
|
||||
});
|
||||
}
|
||||
/**生成PCAP-blob */
|
||||
function generatePCAP(timestamp: number, base64Data: string): Blob {
|
||||
// 1. 转换数据
|
||||
const binaryString = atob(base64Data);
|
||||
const len = binaryString.length;
|
||||
const bytes = new Uint8Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
|
||||
// 2. 创建PCAP文件头
|
||||
const fileHeader = new Uint8Array([
|
||||
0xd4,
|
||||
0xc3,
|
||||
0xb2,
|
||||
0xa1, // magic_number (微秒级)
|
||||
0x02,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00, // version_major(2) + version_minor(4)
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // thiszone (UTC)
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // sigfigs (固定0)
|
||||
0x00,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00, // snaplen (1024)
|
||||
0x71,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // network (LINKTYPE_LINUX_SLL)
|
||||
]);
|
||||
|
||||
// 3. 构造Linux cooked头
|
||||
const cookedHeader = new Uint8Array(16);
|
||||
const view = new DataView(cookedHeader.buffer);
|
||||
view.setUint16(0, 0x0000, false); // 数据包类型(主机→网络)
|
||||
view.setUint16(2, 0x0304, false); // 地址类型(ARPHRD_ETHER)
|
||||
view.setUint16(4, 0x0008, false); // 协议类型(ETH_P_IP)
|
||||
view.setUint16(14, 0x0800, false); // 数据包类型(PACKET_HOST)
|
||||
|
||||
// 4. 合并链路层头与数据
|
||||
const fullData = new Uint8Array(cookedHeader.length + bytes.length);
|
||||
fullData.set(cookedHeader);
|
||||
fullData.set(bytes, cookedHeader.length);
|
||||
|
||||
// 5. 生成时间戳
|
||||
const date = new Date(timestamp);
|
||||
const secs = Math.floor(date.getTime() / 1000);
|
||||
const usecs = (date.getTime() % 1000) * 1000;
|
||||
|
||||
// 6. 构造PCAP报文头
|
||||
const packetHeader = new Uint8Array(16);
|
||||
const headerView = new DataView(packetHeader.buffer);
|
||||
headerView.setUint32(0, secs, true); // 时间戳秒
|
||||
headerView.setUint32(4, usecs, true); // 时间戳微秒
|
||||
headerView.setUint32(8, fullData.length, true); // 捕获长度
|
||||
headerView.setUint32(12, fullData.length, true); // 原始长度
|
||||
|
||||
// 7. 合并所有数据
|
||||
const finalData = new Uint8Array(
|
||||
fileHeader.length + packetHeader.length + fullData.length
|
||||
);
|
||||
finalData.set(fileHeader);
|
||||
finalData.set(packetHeader, fileHeader.length);
|
||||
finalData.set(fullData, fileHeader.length + packetHeader.length);
|
||||
|
||||
// 8. 文件Blob对象
|
||||
return new Blob([finalData], { type: 'application/octet-stream' });
|
||||
}
|
||||
|
||||
// =========== WS ==============
|
||||
/**接收数据后回调 */
|
||||
function wsMessage(res: Record<string, any>) {
|
||||
const { code, requestId, data } = res;
|
||||
@@ -101,7 +360,6 @@ function wsMessage(res: Record<string, any>) {
|
||||
|
||||
// 建联时发送请求
|
||||
if (!requestId && data.clientId) {
|
||||
fnFilePCAP();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -110,13 +368,16 @@ function wsMessage(res: Record<string, any>) {
|
||||
return;
|
||||
}
|
||||
if (data.groupId === `2_${traceId.value}`) {
|
||||
fnFilePCAP();
|
||||
// 第一页降序时实时添加记录
|
||||
if (queryParams.pageNum === 1 && queryParams.sortOrder === 'desc') {
|
||||
tableState.data.unshift(data);
|
||||
}
|
||||
tablePagination.total += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**建立WS连接 */
|
||||
function fnWS() {
|
||||
const options: OptionsType = {
|
||||
const options: wsUtil.OptionsType = {
|
||||
url: '/ws',
|
||||
params: {
|
||||
/**订阅通道组
|
||||
@@ -135,15 +396,167 @@ function fnWS() {
|
||||
ws.connect(options);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => state.initialized,
|
||||
v => {
|
||||
v && fnWS();
|
||||
// =========== WK ==============
|
||||
const NO_SELECTION = { id: '', idx: 0, start: 0, length: 0 };
|
||||
|
||||
type StateType = {
|
||||
/**初始化 */
|
||||
initialized: boolean;
|
||||
/**当前选中的帧编号 */
|
||||
selectedFrame: number;
|
||||
/**当前选中的帧数据 */
|
||||
packetFrame: { tree: any[]; data_sources: any[] };
|
||||
/**pcap包帧数据 */
|
||||
packetFrameTreeMap: Map<string, any> | null;
|
||||
/**当前选中的帧数据 */
|
||||
selectedTree: typeof NO_SELECTION;
|
||||
/**选择帧的Dump数据标签 */
|
||||
selectedDataSourceIndex: number;
|
||||
};
|
||||
|
||||
const state = reactive<StateType>({
|
||||
initialized: false,
|
||||
selectedFrame: 0,
|
||||
/**当前选中的帧数据 */
|
||||
packetFrame: { tree: [], data_sources: [] },
|
||||
packetFrameTreeMap: null, // 注意:Map 需要额外处理
|
||||
selectedTree: NO_SELECTION, // NO_SELECTION 需要定义
|
||||
/**选择帧的Dump数据标签 */
|
||||
selectedDataSourceIndex: 0,
|
||||
});
|
||||
|
||||
/**解析帧数据为简单结构 */
|
||||
function parseFrameTree(id: string, node: Record<string, any>) {
|
||||
let map = new Map();
|
||||
|
||||
if (node.tree && node.tree.length > 0) {
|
||||
for (let i = 0; i < node.tree.length; i++) {
|
||||
const subMap = parseFrameTree(`${id}-${i}`, node.tree[i]);
|
||||
subMap.forEach((value, key) => {
|
||||
map.set(key, value);
|
||||
});
|
||||
}
|
||||
} else if (node.length > 0) {
|
||||
map.set(id, {
|
||||
id: id,
|
||||
idx: node.data_source_idx,
|
||||
start: node.start,
|
||||
length: node.length,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return map;
|
||||
}
|
||||
/**报文数据点击选中 */
|
||||
function handleSelectedFindSelection(src_idx: number, pos: number) {
|
||||
// console.log('fnSelectedFindSelection', pos);
|
||||
if (state.packetFrameTreeMap == null) return;
|
||||
// find the smallest one
|
||||
let current = null;
|
||||
for (let [k, pp] of state.packetFrameTreeMap) {
|
||||
if (pp.idx !== src_idx) continue;
|
||||
|
||||
if (pos >= pp.start && pos <= pp.start + pp.length) {
|
||||
if (
|
||||
current != null &&
|
||||
state.packetFrameTreeMap.get(current).length > pp.length
|
||||
) {
|
||||
current = k;
|
||||
} else {
|
||||
current = k;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (current != null) {
|
||||
state.selectedTree = state.packetFrameTreeMap.get(current);
|
||||
}
|
||||
}
|
||||
/**帧数据点击选中 */
|
||||
function handleSelectedTree(e: any) {
|
||||
state.selectedTree = e;
|
||||
}
|
||||
|
||||
/**接收数据后回调 */
|
||||
function wkMessage(res: Record<string, any>) {
|
||||
// console.log('wkMessage', res);
|
||||
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;
|
||||
fnGetList();
|
||||
break;
|
||||
case 'frames':
|
||||
const { frames } = res.data;
|
||||
|
||||
// 有匹配的选择第一个
|
||||
if (frames.length > 0) {
|
||||
state.selectedFrame = frames[0].number;
|
||||
wk.send({ type: 'select', number: state.selectedFrame });
|
||||
}
|
||||
break;
|
||||
case 'selected':
|
||||
// 去掉两层
|
||||
res.data.tree.shift();
|
||||
res.data.tree.shift();
|
||||
state.packetFrame = res.data;
|
||||
state.packetFrameTreeMap = parseFrameTree('root', res.data);
|
||||
state.selectedTree = NO_SELECTION;
|
||||
state.selectedDataSourceIndex = 0;
|
||||
break;
|
||||
case 'processed':
|
||||
// setStatus(`Error: non-zero return code (${e.data.code})`);
|
||||
// 加载数据
|
||||
wk.send({
|
||||
type: 'frames',
|
||||
filter: '',
|
||||
skip: 0,
|
||||
limit: 1,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
console.warn(res);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**建立WK连接 */
|
||||
function fnWK() {
|
||||
const options: wkUtil.OptionsType = {
|
||||
url: wkUrl,
|
||||
onmessage: wkMessage,
|
||||
onerror: (ev: any) => {
|
||||
console.error(ev);
|
||||
},
|
||||
};
|
||||
//建立连接
|
||||
wk.connect(options);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('trace_msg_type'), getDict('trace_msg_direct')])
|
||||
.then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.traceMsgType = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.traceMsgDirect = resArr[1].value;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
fnWK();
|
||||
fnWS();
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
ws.close();
|
||||
wk.send({ type: 'close' }) && wk.close();
|
||||
if (ws.state() <= WebSocket.OPEN) ws.close();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -154,8 +567,9 @@ onBeforeUnmount(() => {
|
||||
:loading="!state.initialized"
|
||||
:body-style="{ padding: '12px' }"
|
||||
>
|
||||
<div class="toolbar">
|
||||
<a-space :size="8" class="toolbar-oper">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button type="default" @click.prevent="fnClose()">
|
||||
<template #icon><CloseOutlined /></template>
|
||||
{{ t('common.close') }}
|
||||
@@ -173,89 +587,65 @@ onBeforeUnmount(() => {
|
||||
<strong>{{ traceId }}</strong>
|
||||
</span>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<div class="toolbar-info">
|
||||
<a-tag color="green" v-show="!!state.currentFilter">
|
||||
{{ state.currentFilter }}
|
||||
</a-tag>
|
||||
<span> Matched Frame: {{ state.totalFrames }} </span>
|
||||
</div>
|
||||
|
||||
<!-- 包信息 -->
|
||||
<a-popover
|
||||
trigger="click"
|
||||
placement="bottomLeft"
|
||||
v-if="state.summary.filename"
|
||||
>
|
||||
<template #content>
|
||||
<div class="summary">
|
||||
<div class="summary-item">
|
||||
<span>Type:</span>
|
||||
<span>{{ state.summary.file_type }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span>Encapsulation:</span>
|
||||
<span>{{ state.summary.file_encap_type }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span>Packets:</span>
|
||||
<span>{{ state.summary.packet_count }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span>Duration:</span>
|
||||
<span>{{ Math.round(state.summary.elapsed_time) }}s</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<InfoCircleOutlined />
|
||||
</a-popover>
|
||||
</div>
|
||||
|
||||
<!-- 包数据表过滤 -->
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
v-model:value="state.filter"
|
||||
placeholder="display filter, example: tcp"
|
||||
:allow-clear="true"
|
||||
style="width: calc(100% - 100px)"
|
||||
@pressEnter="handleFilterFrames"
|
||||
>
|
||||
<template #prefix>
|
||||
<FilterOutlined />
|
||||
</template>
|
||||
</a-input>
|
||||
<a-button
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
style="width: 100px"
|
||||
@click="handleFilterFrames"
|
||||
>
|
||||
Filter
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<a-alert
|
||||
:message="state.filterError"
|
||||
type="error"
|
||||
v-if="state.filterError != null"
|
||||
/>
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<!-- 包数据表 -->
|
||||
<PacketTable
|
||||
:columns="state.columns"
|
||||
:data="state.packetFrames"
|
||||
:selectedFrame="state.selectedFrame"
|
||||
:onSelectedFrame="handleSelectedFrame"
|
||||
:onScrollBottom="handleScrollBottom"
|
||||
></PacketTable>
|
||||
|
||||
<a-row>
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:row-class-name="(record:any) => {
|
||||
if (record.id === tableState.row.id) {
|
||||
return 'table-striped-select';
|
||||
}
|
||||
return record.msgDirect === 0 ? 'table-striped-recv' : 'table-striped-send';
|
||||
}"
|
||||
:customRow="
|
||||
record => {
|
||||
return {
|
||||
onClick: () => fnVisible(record),
|
||||
};
|
||||
}
|
||||
"
|
||||
@change="fnTableChange"
|
||||
:scroll="{ x: tableColumns.length * 150, y: 'calc(100vh - 300px)' }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'msgType'">
|
||||
<DictTag :options="dict.traceMsgType" :value="record.msgType" />
|
||||
</template>
|
||||
<template v-if="column.key === 'msgDirect'">
|
||||
<DictTag :options="dict.traceMsgDirect" :value="record.msgDirect" />
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<!-- 帧数据 -->
|
||||
<a-row
|
||||
:gutter="16"
|
||||
style="border: 2px rgb(217 217 217) solid; border-radius: 6px"
|
||||
v-show="state.selectedFrame == 1"
|
||||
>
|
||||
<a-col :lg="12" :md="12" :xs="24" class="tree">
|
||||
<!-- 帧数据 -->
|
||||
<DissectionTree
|
||||
id="root"
|
||||
:select="handleSelectedTreeEntry"
|
||||
:selected="state.selectedTreeEntry"
|
||||
:tree="state.selectedPacket.tree"
|
||||
:select="handleSelectedTree"
|
||||
:selected="state.selectedTree"
|
||||
:tree="state.packetFrame.tree"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24" class="dump">
|
||||
@@ -268,15 +658,15 @@ onBeforeUnmount(() => {
|
||||
<a-tab-pane
|
||||
:key="idx"
|
||||
:tab="v.name"
|
||||
v-for="(v, idx) in state.selectedPacket.data_sources"
|
||||
v-for="(v, idx) in state.packetFrame.data_sources"
|
||||
style="overflow: auto"
|
||||
>
|
||||
<DissectionDump
|
||||
:base64="v.data"
|
||||
:select="(pos:number)=>handleSelectedFindSelection(idx, pos)"
|
||||
:selected="
|
||||
idx === state.selectedTreeEntry.idx
|
||||
? state.selectedTreeEntry
|
||||
idx === state.selectedTree.idx
|
||||
? state.selectedTree
|
||||
: NO_SELECTION
|
||||
"
|
||||
/>
|
||||
@@ -289,24 +679,20 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
.toolbar-info {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
padding-right: 8px;
|
||||
.table :deep(.table-striped-select) td {
|
||||
background-color: #c2c2c2;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.table :deep(.table-striped-recv) td {
|
||||
background-color: #a9e2ff;
|
||||
cursor: pointer;
|
||||
}
|
||||
.summary-item > span:first-child {
|
||||
font-weight: 600;
|
||||
margin-right: 6px;
|
||||
.table :deep(.table-striped-send) td {
|
||||
background-color: #bcfbb3;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tree {
|
||||
|
||||
@@ -1,24 +1,60 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw } from 'vue';
|
||||
import { reactive, onMounted, toRaw, ref } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { ProModal } from 'antdv-pro-modal';
|
||||
import { Modal } from 'ant-design-vue/es';
|
||||
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { saveAs } from 'file-saver';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import useTabsStore from '@/store/modules/tabs';
|
||||
import { type Dayjs } from 'dayjs';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { listTraceData } from '@/api/trace/analysis';
|
||||
import { getTraceData, listTraceData } from '@/api/trace/task';
|
||||
import { decode } from 'js-base64';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
const { t } = useI18n();
|
||||
const { getDict } = useDictStore();
|
||||
const tabsStore = useTabsStore();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
/**跟踪编号 */
|
||||
const traceId = ref<string>(route.query.traceId as string);
|
||||
|
||||
/**关闭跳转 */
|
||||
function fnClose() {
|
||||
const to = tabsStore.tabClose(route.path);
|
||||
if (to) {
|
||||
router.push(to);
|
||||
} else {
|
||||
router.back();
|
||||
}
|
||||
}
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**跟踪消息类型 */
|
||||
traceMsgType: DictType[];
|
||||
/**跟踪消息方向 */
|
||||
traceMsgDirect: DictType[];
|
||||
} = reactive({
|
||||
traceMsgType: [],
|
||||
traceMsgDirect: [],
|
||||
});
|
||||
|
||||
/**开始结束时间 */
|
||||
let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>(undefined);
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**移动号 */
|
||||
imsi: '',
|
||||
/**移动号 */
|
||||
msisdn: '',
|
||||
traceId: traceId.value,
|
||||
sortBy: 'timestamp',
|
||||
sortOrder: 'asc',
|
||||
/**开始时间 */
|
||||
beginTime: undefined as undefined | number,
|
||||
/**结束时间 */
|
||||
endTime: undefined as undefined | number,
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
@@ -28,10 +64,10 @@ let queryParams = reactive({
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
imsi: '',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
queryRangePicker.value = undefined;
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
@@ -43,8 +79,6 @@ type TabeStateType = {
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
};
|
||||
@@ -52,66 +86,87 @@ type TabeStateType = {
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
seached: true,
|
||||
size: 'small',
|
||||
data: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('views.traceManage.analysis.trackTaskId'),
|
||||
dataIndex: 'taskId',
|
||||
align: 'center',
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'id',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.imsi'),
|
||||
dataIndex: 'imsi',
|
||||
align: 'center',
|
||||
title: t('views.traceManage.task.msgNe'),
|
||||
dataIndex: 'msgNe',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
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'),
|
||||
title: t('views.traceManage.task.rowTime'),
|
||||
dataIndex: 'timestamp',
|
||||
align: 'center',
|
||||
align: 'left',
|
||||
width: 250,
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value);
|
||||
const nanoseconds = opt.value; // 纳秒时间戳
|
||||
const milliseconds = Math.floor(nanoseconds / 1000000);
|
||||
const nanosecondsPart = (nanoseconds % 1000000)
|
||||
.toString()
|
||||
.padStart(6, '0');
|
||||
|
||||
return `${parseDateToStr(
|
||||
milliseconds,
|
||||
'YYYY-MM-DD HH:mm:ss.SSS'
|
||||
)}.${nanosecondsPart}`;
|
||||
},
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.task.protocolOrInterface'),
|
||||
dataIndex: 'ifType',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.task.msgEvent'),
|
||||
dataIndex: 'msgEvent',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.task.msgType'),
|
||||
dataIndex: 'msgType',
|
||||
key: 'msgType',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.task.msgDirect'),
|
||||
dataIndex: 'msgDirect',
|
||||
key: 'msgDirect',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.task.srcIp'),
|
||||
dataIndex: 'srcAddr',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.task.dstIp'),
|
||||
dataIndex: 'dstAddr',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -148,6 +203,18 @@ function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格分页、排序、筛选变化时触发操作, 排序方式,取值为 ascend descend */
|
||||
function fnTableChange(pagination: any, filters: any, sorter: any, extra: any) {
|
||||
const { field, order } = sorter;
|
||||
if (order) {
|
||||
queryParams.sortBy = field;
|
||||
queryParams.sortOrder = order.replace('end', '');
|
||||
} else {
|
||||
queryParams.sortOrder = 'asc';
|
||||
}
|
||||
fnGetList(1);
|
||||
}
|
||||
|
||||
/**查询备份信息列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
@@ -155,6 +222,19 @@ function fnGetList(pageNum?: number) {
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
|
||||
// 时间范围
|
||||
if (
|
||||
Array.isArray(queryRangePicker.value) &&
|
||||
queryRangePicker.value.length > 0
|
||||
) {
|
||||
queryParams.beginTime = queryRangePicker.value[0].valueOf();
|
||||
queryParams.endTime = queryRangePicker.value[1].valueOf();
|
||||
} else {
|
||||
queryParams.beginTime = undefined;
|
||||
queryParams.endTime = undefined;
|
||||
}
|
||||
|
||||
listTraceData(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
const { total, rows } = res.data;
|
||||
@@ -199,24 +279,17 @@ let modalState: ModalStateType = reactive({
|
||||
* @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,
|
||||
getTraceData(row.id).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
Object.assign(modalState.from, res.data);
|
||||
// 进制转数据
|
||||
const hexString = parseBase64Data(res.data.rawMsg);
|
||||
const rawData = convertToReadableFormat(hexString);
|
||||
modalState.from.rawData = rawData;
|
||||
modalState.title = t('views.traceManage.task.taskInfo');
|
||||
modalState.open = true;
|
||||
}
|
||||
});
|
||||
modalState.open = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,15 +297,13 @@ function fnModalVisible(row: Record<string, any>) {
|
||||
*/
|
||||
function fnModalVisibleClose() {
|
||||
modalState.open = false;
|
||||
modalState.from.downBtn = false;
|
||||
modalState.from.rawDataHTML = '';
|
||||
modalState.from.rawData = '';
|
||||
}
|
||||
|
||||
// 将Base64编码解码为字节数组
|
||||
function parseBase64Data(hexData: string) {
|
||||
function parseBase64Data(base64Data: string) {
|
||||
// 将Base64编码解码为字节数组
|
||||
const byteString = atob(hexData);
|
||||
const byteString = decode(base64Data);
|
||||
const byteArray = new Uint8Array(byteString.length);
|
||||
for (let i = 0; i < byteString.length; i++) {
|
||||
byteArray[i] = byteString.charCodeAt(i);
|
||||
@@ -252,7 +323,7 @@ function convertToReadableFormat(hexString: string) {
|
||||
let result = '';
|
||||
let asciiResult = '';
|
||||
let arr = [];
|
||||
let row = 100;
|
||||
let row = 10000;
|
||||
for (let i = 0; i < hexString.length; i += 2) {
|
||||
const hexChars = hexString.substring(i, i + 2);
|
||||
const decimal = parseInt(hexChars, 16);
|
||||
@@ -286,100 +357,46 @@ function convertToReadableFormat(hexString: string) {
|
||||
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();
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('trace_msg_type'), getDict('trace_msg_direct')])
|
||||
.then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.traceMsgType = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.traceMsgDirect = resArr[1].value;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
// 获取列表数据
|
||||
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-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.analysis.imsi')"
|
||||
name="imsi"
|
||||
:label="t('views.traceManage.task.rowTime')"
|
||||
name="queryRangePicker"
|
||||
>
|
||||
<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-range-picker
|
||||
v-model:value="queryRangePicker"
|
||||
:bordered="true"
|
||||
:allow-clear="false"
|
||||
style="width: 100%"
|
||||
:show-time="{ format: 'HH:mm:ss' }"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
@@ -402,20 +419,22 @@ onMounted(() => {
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title> </template>
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button type="default" @click.prevent="fnClose()">
|
||||
<template #icon><CloseOutlined /></template>
|
||||
{{ t('common.close') }}
|
||||
</a-button>
|
||||
<span>
|
||||
{{ t('views.traceManage.task.traceId') }}:
|
||||
<strong>{{ traceId }}</strong>
|
||||
</span>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.searchBarText') }}</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
:checked-children="t('common.switch.show')"
|
||||
:un-checked-children="t('common.switch.hide')"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
@@ -459,11 +478,18 @@ onMounted(() => {
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: true }"
|
||||
@change="fnTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'msgType'">
|
||||
<DictTag :options="dict.traceMsgType" :value="record.msgType" />
|
||||
</template>
|
||||
<template v-if="column.key === 'msgDirect'">
|
||||
<DictTag :options="dict.traceMsgDirect" :value="record.msgDirect" />
|
||||
</template>
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>{{ t('common.viewText') }}</template>
|
||||
<a-button type="link" @click.prevent="fnModalVisible(record)">
|
||||
<template #icon><ProfileOutlined /></template>
|
||||
@@ -482,31 +508,111 @@ onMounted(() => {
|
||||
:title="modalState.title"
|
||||
:open="modalState.open"
|
||||
@cancel="fnModalVisibleClose"
|
||||
:footer="false"
|
||||
>
|
||||
<div class="raw-title">
|
||||
{{ t('views.traceManage.analysis.signalData') }}
|
||||
</div>
|
||||
<a-row class="raw" 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"
|
||||
<a-form
|
||||
name="modalStateFrom"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 8 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.msgType')"
|
||||
name="msgType"
|
||||
>
|
||||
<DictTag
|
||||
:options="dict.traceMsgType"
|
||||
:value="modalState.from.msgType"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.msgDirect')"
|
||||
name="msgDirect"
|
||||
>
|
||||
<DictTag
|
||||
:options="dict.traceMsgDirect"
|
||||
:value="modalState.from.msgDirect"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.srcIp')"
|
||||
name="srcAddr"
|
||||
>
|
||||
{{ modalState.from.srcAddr }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.dstIp')"
|
||||
name="dstAddr"
|
||||
>
|
||||
{{ modalState.from.dstAddr }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.msgNe')"
|
||||
name="msgNe"
|
||||
>
|
||||
{{ modalState.from.msgNe }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.msgEvent')"
|
||||
name="msgEvent"
|
||||
>
|
||||
{{ modalState.from.msgEvent }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item
|
||||
v-if="modalState.from.imsi"
|
||||
:label="t('views.traceManage.task.imsi')"
|
||||
name="imsi"
|
||||
:label-col="{ span: 4 }"
|
||||
>
|
||||
<template #icon>
|
||||
<DownloadOutlined />
|
||||
</template>
|
||||
{{ t('views.traceManage.analysis.taskDownText') }}
|
||||
</a-button>
|
||||
</div> -->
|
||||
<!-- <div class="raw-html" v-html="modalState.from.rawDataHTML"></div> -->
|
||||
{{ modalState.from.imsi }}
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
v-if="modalState.from.ifType"
|
||||
:label="t('views.traceManage.task.protocolOrInterface')"
|
||||
name="ifType"
|
||||
:label-col="{ span: 4 }"
|
||||
>
|
||||
{{ modalState.from.ifType }}
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.msgLen')"
|
||||
name="length"
|
||||
:label-col="{ span: 4 }"
|
||||
>
|
||||
{{ modalState.from.length }}
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-row class="raw" 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-form-item>
|
||||
</a-form>
|
||||
</ProModal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
@@ -517,24 +623,15 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.raw {
|
||||
&-title {
|
||||
color: #000000d9;
|
||||
font-size: 24px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.num {
|
||||
background-color: #e5e5e5;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
.code {
|
||||
background-color: #e7e6ff;
|
||||
background-color: #0078d4;
|
||||
color: #fff;
|
||||
}
|
||||
.txt {
|
||||
background-color: #ffe3e5;
|
||||
}
|
||||
|
||||
&-html {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
background-color: #d9d9d9;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
updateTraceTask,
|
||||
} from '@/api/trace/task';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import { regExpIPv4, regExpPort } from '@/utils/regular-utils';
|
||||
import { regExpIPv4 } from '@/utils/regular-utils';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { parseObjHumpToLine } from '@/utils/parse-utils';
|
||||
const neInfoStore = useNeInfoStore();
|
||||
@@ -33,8 +33,11 @@ const route = useRoute();
|
||||
let dict: {
|
||||
/**跟踪类型 */
|
||||
traceType: DictType[];
|
||||
/**跟踪接口 */
|
||||
traceInterfaces: DictType[];
|
||||
} = reactive({
|
||||
traceType: [],
|
||||
traceInterfaces: [],
|
||||
});
|
||||
|
||||
/**网元类型_多neId */
|
||||
@@ -98,40 +101,28 @@ let tableState: TabeStateType = reactive({
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('views.ne.common.neType'),
|
||||
dataIndex: 'neType',
|
||||
align: 'left',
|
||||
sorter: {
|
||||
compare: (a, b) => 1,
|
||||
multiple: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.ne.common.neId'),
|
||||
dataIndex: 'neId',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.task.traceId'),
|
||||
dataIndex: 'traceId',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.task.trackType'),
|
||||
dataIndex: 'traceType',
|
||||
key: 'traceType',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.task.startTime'),
|
||||
dataIndex: 'startTime',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value);
|
||||
},
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.task.endTime'),
|
||||
@@ -141,6 +132,7 @@ let tableColumns: ColumnsType = [
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value);
|
||||
},
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
@@ -274,18 +266,15 @@ type ModalStateType = {
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**网元类型设备对象 */
|
||||
neType: string[];
|
||||
neType: any[] | undefined;
|
||||
/**网元类型设备对象接口 */
|
||||
neTypeInterface: Record<string, any>[];
|
||||
/**网元类型设备对象接口选择 */
|
||||
neTypeInterfaceSelect: string[];
|
||||
neTypeInterface: string[];
|
||||
/**任务开始结束时间 */
|
||||
timeRangePicker: [string, string];
|
||||
timeRangePicker: [Dayjs, Dayjs] | undefined;
|
||||
/**表单数据 */
|
||||
from: {
|
||||
id?: string;
|
||||
neType: string;
|
||||
neId: string;
|
||||
neList: string; // 网元列表 neType_neId 例如 UDM_001,AMF_001
|
||||
/**1-Interface,2-Device,3-User */
|
||||
traceType: string;
|
||||
startTime?: number;
|
||||
@@ -312,13 +301,11 @@ let modalState: ModalStateType = reactive({
|
||||
title: '',
|
||||
neType: [],
|
||||
neTypeInterface: [],
|
||||
neTypeInterfaceSelect: [],
|
||||
timeRangePicker: ['', ''],
|
||||
timeRangePicker: undefined,
|
||||
from: {
|
||||
id: undefined,
|
||||
neType: '',
|
||||
neId: '',
|
||||
traceId: '',
|
||||
neList: '',
|
||||
traceId: undefined,
|
||||
traceType: '3',
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
@@ -330,8 +317,8 @@ let modalState: ModalStateType = reactive({
|
||||
dstIp: '',
|
||||
signalPort: undefined,
|
||||
/**3用户跟踪 */
|
||||
imsi: '',
|
||||
msisdn: '',
|
||||
imsi: undefined,
|
||||
// msisdn: undefined,
|
||||
},
|
||||
confirmLoading: false,
|
||||
});
|
||||
@@ -346,7 +333,7 @@ const modalStateFrom = Form.useForm(
|
||||
message: t('views.traceManage.task.trackTypePlease'),
|
||||
},
|
||||
],
|
||||
neId: [
|
||||
neList: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.ne.common.neTypePlease'),
|
||||
@@ -393,43 +380,48 @@ const modalStateFrom = Form.useForm(
|
||||
message: t('views.traceManage.task.dstIpPlease'),
|
||||
},
|
||||
],
|
||||
signalPort: [
|
||||
{
|
||||
required: false,
|
||||
pattern: regExpPort,
|
||||
message: t('views.traceManage.task.signalPortPlease'),
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**网元类型选择对应修改 */
|
||||
function fnNeChange(_: any, item: any) {
|
||||
modalState.from.neType = item[1].neType;
|
||||
modalState.from.neId = item[1].neId;
|
||||
// 网元信令接口可选列表
|
||||
modalState.from.interfaces = '';
|
||||
modalState.neTypeInterfaceSelect = [];
|
||||
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 fnNeChange(p: any, c: any) {
|
||||
let neList: string[] = [];
|
||||
for (let i = 0; i < p.length; i++) {
|
||||
const v = p[i];
|
||||
if (v.length === 1) {
|
||||
c[i][0].children.forEach((item: any) => {
|
||||
neList.push(`${item.neType}_${item.neId}`);
|
||||
});
|
||||
} else if (v.length === 2) {
|
||||
neList.push(`${v[0]}_${v[1]}`);
|
||||
}
|
||||
}
|
||||
if (neList.length > 0) {
|
||||
modalState.from.neList = neList.join(',');
|
||||
} else {
|
||||
modalState.from.neList = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**开始结束时间选择对应修改 */
|
||||
function fnRangePickerChange(item: any, _: any) {
|
||||
modalState.from.startTime = +item[0];
|
||||
modalState.from.endTime = +item[1];
|
||||
if (!item || item.length !== 2) {
|
||||
modalState.from.startTime = undefined;
|
||||
modalState.from.endTime = undefined;
|
||||
return;
|
||||
}
|
||||
// 获取当前时间
|
||||
const now = dayjs();
|
||||
// 如果开始时间小于当前时间,则设置为当前时间
|
||||
const startTime = item[0].isBefore(now) ? now : item[0];
|
||||
const endTime = item[1].isBefore(now) ? now : item[1];
|
||||
modalState.timeRangePicker = [startTime, endTime];
|
||||
|
||||
modalState.from.startTime = startTime.valueOf();
|
||||
modalState.from.endTime = endTime.valueOf();
|
||||
}
|
||||
function fnRangePickerDisabledDate(current: Dayjs) {
|
||||
return current && current < dayjs().subtract(1, 'day').endOf('day');
|
||||
return current && current < dayjs().startOf('day');
|
||||
}
|
||||
|
||||
/**信令接口选择对应修改 */
|
||||
@@ -437,19 +429,6 @@ function fnSelectInterface(s: any, _: any) {
|
||||
modalState.from.interfaces = s.join(',');
|
||||
}
|
||||
|
||||
/**信令接口选择初始 */
|
||||
function fnSelectInterfaceInit(neType: string) {
|
||||
const interfaces = neInfoStore.traceInterfaceList;
|
||||
modalState.neTypeInterface = interfaces
|
||||
.filter(i => i.neType === neType)
|
||||
.map(i => {
|
||||
return {
|
||||
value: i.interface,
|
||||
label: i.interface,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param noticeId 网元id, 不传为新增
|
||||
@@ -467,20 +446,45 @@ function fnModalOpenByEdit(id?: string) {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
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);
|
||||
// 回显网元类型
|
||||
const neType: any[] = [];
|
||||
const neList = res.data.neList.split(',');
|
||||
const neListMap: any = {};
|
||||
for (const v of neList) {
|
||||
const item: string[] = v.split('_');
|
||||
if (!neListMap[item[0]]) {
|
||||
neListMap[item[0]] = [];
|
||||
}
|
||||
fnSelectInterfaceInit(res.data.neType);
|
||||
neListMap[item[0]].push(item[1]);
|
||||
}
|
||||
modalState.title = t('views.traceManage.task.editTask');
|
||||
for (const op of neCascaderOptions.value) {
|
||||
const arr = neListMap[op.value];
|
||||
if (!arr) {
|
||||
continue;
|
||||
}
|
||||
const all = op.children.every((c: any) => {
|
||||
return arr.includes(c.neId);
|
||||
});
|
||||
if (all) {
|
||||
neType.push([op.value]);
|
||||
} else {
|
||||
arr.forEach((v: string) => {
|
||||
neType.push([op.value, v]);
|
||||
});
|
||||
}
|
||||
}
|
||||
modalState.neType = neType;
|
||||
// 回显时间
|
||||
modalState.timeRangePicker = [
|
||||
dayjs(res.data.startTime),
|
||||
dayjs(res.data.endTime),
|
||||
];
|
||||
// 回显接口
|
||||
if (res.data.traceType === '1') {
|
||||
modalState.neTypeInterface = res.data.interfaces.split(',');
|
||||
}
|
||||
modalState.from = Object.assign(modalState.from, res.data);
|
||||
modalState.title = t('views.traceManage.task.viewTask');
|
||||
modalState.openByEdit = true;
|
||||
} else {
|
||||
message.error(t('views.traceManage.task.errorTaskInfo'), 3);
|
||||
@@ -495,15 +499,15 @@ function fnModalOpenByEdit(id?: string) {
|
||||
*/
|
||||
function fnModalOk() {
|
||||
const from = toRaw(modalState.from);
|
||||
let valids = ['traceType', 'neId', 'endTime'];
|
||||
let valids = ['traceType', 'neList', 'endTime'];
|
||||
if (from.traceType === '1') {
|
||||
valids = valids.concat(['interfaces']);
|
||||
}
|
||||
if (from.traceType === '2') {
|
||||
valids = valids.concat(['srcIp', 'dstIp', 'signalPort']);
|
||||
valids = valids.concat(['srcIp', 'dstIp']);
|
||||
}
|
||||
if (from.traceType === '3') {
|
||||
valids = valids.concat(['imsi', 'msisdn']);
|
||||
valids = valids.concat(['imsi']);
|
||||
}
|
||||
|
||||
modalStateFrom
|
||||
@@ -546,29 +550,31 @@ function fnModalCancel() {
|
||||
modalState.openByEdit = false;
|
||||
modalState.confirmLoading = false;
|
||||
modalStateFrom.resetFields();
|
||||
modalState.timeRangePicker = ['', ''];
|
||||
modalState.neTypeInterfaceSelect = [];
|
||||
modalState.neType = [];
|
||||
modalState.neTypeInterface = [];
|
||||
modalState.timeRangePicker = undefined;
|
||||
}
|
||||
|
||||
/**跳转PCAP文件详情页面 */
|
||||
function fnRecordPCAPView(row: Record<string, any>) {
|
||||
/**跳转内嵌详情页面 */
|
||||
function fnRecordView(traceId: any, type: 'analyze' | 'data') {
|
||||
router.push({
|
||||
path: `${route.path}${MENU_PATH_INLINE}/analyze`,
|
||||
query: {
|
||||
traceId: row.traceId,
|
||||
},
|
||||
path: `${route.path}${MENU_PATH_INLINE}/${type}`,
|
||||
query: { traceId },
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('trace_type')]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.traceType = resArr[0].value;
|
||||
Promise.allSettled([getDict('trace_type'), getDict('trace_interfaces')]).then(
|
||||
resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.traceType = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.traceInterfaces = resArr[1].value;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// 获取网元网元列表
|
||||
useNeInfoStore()
|
||||
@@ -579,13 +585,13 @@ onMounted(() => {
|
||||
// 过滤不可用的网元
|
||||
neCascaderOptions.value = neInfoStore.getNeSelectOtions.filter(
|
||||
(item: any) => {
|
||||
return ['UDM'].includes(item.value);
|
||||
return ['AMF', 'AUSF', 'SMF', 'UDM', 'PCF'].includes(item.value);
|
||||
}
|
||||
);
|
||||
if (neCascaderOptions.value.length === 0) {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
duration: 3,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -593,13 +599,11 @@ onMounted(() => {
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
// 获取跟踪接口列表
|
||||
neInfoStore.fnNeTraceInterface();
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
@@ -765,21 +769,40 @@ onMounted(() => {
|
||||
</template>
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
<div v-perms:has="['traceManage:task:data']">
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>
|
||||
{{ t('views.traceManage.task.dataView') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordView(record.traceId, 'data')"
|
||||
>
|
||||
<template #icon><ContainerOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div v-perms:has="['traceManage:task:analyze']">
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>
|
||||
{{ t('views.traceManage.task.pcapView') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordView(record.traceId, 'analyze')"
|
||||
>
|
||||
<template #icon><BarsOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>{{ t('common.editText') }}</template>
|
||||
<template #title>{{ t('common.viewText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalOpenByEdit(record.id)"
|
||||
>
|
||||
<template #icon><FormOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>{{
|
||||
t('views.traceManage.task.pcapView')
|
||||
}}</template>
|
||||
<a-button type="link" @click.prevent="fnRecordPCAPView(record)">
|
||||
<template #icon><ContainerOutlined /></template>
|
||||
<template #icon><ProfileOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="topRight">
|
||||
@@ -810,12 +833,14 @@ onMounted(() => {
|
||||
:confirm-loading="modalState.confirmLoading"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
:footer="modalState.from.id ? null : undefined"
|
||||
>
|
||||
<a-form
|
||||
name="modalStateFrom"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 4 }"
|
||||
:label-wrap="true"
|
||||
:disabled="!!modalState.from.id"
|
||||
>
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
@@ -823,12 +848,13 @@ onMounted(() => {
|
||||
:label="t('views.ne.common.neType')"
|
||||
:label-col="{ span: 8 }"
|
||||
name="neType"
|
||||
v-bind="modalStateFrom.validateInfos.neId"
|
||||
v-bind="modalStateFrom.validateInfos.neList"
|
||||
>
|
||||
<a-cascader
|
||||
v-model:value="modalState.neType"
|
||||
:options="neCascaderOptions"
|
||||
@change="fnNeChange"
|
||||
multiple
|
||||
:allow-clear="false"
|
||||
:placeholder="t('views.ne.common.neTypePlease')"
|
||||
/>
|
||||
@@ -844,7 +870,6 @@ onMounted(() => {
|
||||
<a-select
|
||||
v-model:value="modalState.from.traceType"
|
||||
:options="dict.traceType"
|
||||
@change="fnTraceTypeChange"
|
||||
:allow-clear="false"
|
||||
:placeholder="t('views.traceManage.task.trackTypePlease')"
|
||||
>
|
||||
@@ -861,11 +886,9 @@ onMounted(() => {
|
||||
<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="[
|
||||
@@ -894,8 +917,8 @@ onMounted(() => {
|
||||
<a-select
|
||||
mode="multiple"
|
||||
:placeholder="t('views.traceManage.task.interfacesPlease')"
|
||||
v-model:value="modalState.neTypeInterfaceSelect"
|
||||
:options="modalState.neTypeInterface"
|
||||
v-model:value="modalState.neTypeInterface"
|
||||
:options="dict.traceInterfaces"
|
||||
@change="fnSelectInterface"
|
||||
>
|
||||
</a-select>
|
||||
@@ -904,26 +927,6 @@ onMounted(() => {
|
||||
|
||||
<!-- 设备跟踪 -->
|
||||
<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="opacity: 0.45; color: inherit" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
@@ -1000,7 +1003,7 @@ onMounted(() => {
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
<!-- <a-form-item
|
||||
:label="t('views.traceManage.task.msisdn')"
|
||||
name="msisdn"
|
||||
v-bind="modalStateFrom.validateInfos.msisdn"
|
||||
@@ -1019,7 +1022,7 @@ onMounted(() => {
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-form-item> -->
|
||||
</template>
|
||||
</a-form>
|
||||
</ProModal>
|
||||
|
||||
@@ -238,15 +238,13 @@ watchEffect(() => {
|
||||
.thead-item:nth-child(3),
|
||||
.tbody-item:nth-child(3),
|
||||
.thead-item:nth-child(4),
|
||||
.tbody-item:nth-child(4) {
|
||||
.tbody-item:nth-child(4),
|
||||
.thead-item:nth-child(5),
|
||||
.tbody-item:nth-child(5) {
|
||||
flex-basis: 8rem;
|
||||
width: 8rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.thead-item:nth-child(5),
|
||||
.tbody-item:nth-child(5) {
|
||||
flex-basis: 7rem;
|
||||
width: 7rem;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
.thead-item:nth-child(6),
|
||||
.tbody-item:nth-child(6) {
|
||||
@@ -270,6 +268,7 @@ watchEffect(() => {
|
||||
.tbody-item:nth-child(2)::-webkit-scrollbar,
|
||||
.tbody-item:nth-child(3)::-webkit-scrollbar,
|
||||
.tbody-item:nth-child(4)::-webkit-scrollbar,
|
||||
.tbody-item:nth-child(5)::-webkit-scrollbar,
|
||||
.tbody-item:nth-child(7)::-webkit-scrollbar {
|
||||
width: 4px; /* 设置滚动条宽度 */
|
||||
height: 4px;
|
||||
@@ -278,6 +277,7 @@ watchEffect(() => {
|
||||
.tbody-item:nth-child(2)::-webkit-scrollbar-track,
|
||||
.tbody-item:nth-child(3)::-webkit-scrollbar-track,
|
||||
.tbody-item:nth-child(4)::-webkit-scrollbar-track,
|
||||
.tbody-item:nth-child(5)::-webkit-scrollbar-track,
|
||||
.tbody-item:nth-child(7)::-webkit-scrollbar-track {
|
||||
background-color: #f0f0f0; /* 设置滚动条轨道背景颜色 */
|
||||
}
|
||||
@@ -285,6 +285,7 @@ watchEffect(() => {
|
||||
.tbody-item:nth-child(2)::-webkit-scrollbar-thumb,
|
||||
.tbody-item:nth-child(3)::-webkit-scrollbar-thumb,
|
||||
.tbody-item:nth-child(4)::-webkit-scrollbar-thumb,
|
||||
.tbody-item:nth-child(5)::-webkit-scrollbar-thumb,
|
||||
.tbody-item:nth-child(7)::-webkit-scrollbar-thumb {
|
||||
background-color: #bfbfbf; /* 设置滚动条滑块颜色 */
|
||||
}
|
||||
@@ -292,6 +293,7 @@ watchEffect(() => {
|
||||
.tbody-item:nth-child(2)::-webkit-scrollbar-thumb:hover,
|
||||
.tbody-item:nth-child(3)::-webkit-scrollbar-thumb:hover,
|
||||
.tbody-item:nth-child(4)::-webkit-scrollbar-thumb:hover,
|
||||
.tbody-item:nth-child(5)::-webkit-scrollbar-thumb:hover,
|
||||
.tbody-item:nth-child(7)::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #1890ff; /* 设置鼠标悬停时滚动条滑块颜色 */
|
||||
}
|
||||
|
||||
@@ -31,11 +31,11 @@ type StateType = {
|
||||
/**当前选中的帧编号 */
|
||||
selectedFrame: number;
|
||||
/**当前选中的帧数据 */
|
||||
selectedPacket: { tree: any[]; data_sources: any[] };
|
||||
packetFrame: { tree: any[]; data_sources: any[] };
|
||||
/**pcap包帧数据 */
|
||||
packetFrameData: Map<string, any> | null;
|
||||
/**当前选中的帧数据-空占位 */
|
||||
selectedTreeEntry: typeof NO_SELECTION;
|
||||
packetFrameTreeMap: Map<string, any> | null;
|
||||
/**当前选中的帧数据 */
|
||||
selectedTree: typeof NO_SELECTION;
|
||||
/**选择帧的Dump数据标签 */
|
||||
selectedDataSourceIndex: number;
|
||||
/**处理完成状态 */
|
||||
@@ -69,11 +69,11 @@ export function usePCAP() {
|
||||
filter: '',
|
||||
filterError: null,
|
||||
currentFilter: '',
|
||||
selectedFrame: 1,
|
||||
selectedFrame: 0,
|
||||
/**当前选中的帧数据 */
|
||||
selectedPacket: { tree: [], data_sources: [] },
|
||||
packetFrameData: null, // 注意:Map 需要额外处理
|
||||
selectedTreeEntry: NO_SELECTION, // NO_SELECTION 需要定义
|
||||
packetFrame: { tree: [], data_sources: [] },
|
||||
packetFrameTreeMap: null, // 注意:Map 需要额外处理
|
||||
selectedTree: NO_SELECTION, // NO_SELECTION 需要定义
|
||||
/**选择帧的Dump数据标签 */
|
||||
selectedDataSourceIndex: 0,
|
||||
/**处理完成状态 */
|
||||
@@ -91,9 +91,9 @@ export function usePCAP() {
|
||||
state.nextPageNum = 1;
|
||||
// 选择帧的数据
|
||||
state.selectedFrame = 0;
|
||||
state.selectedPacket = { tree: [], data_sources: [] };
|
||||
state.packetFrameData = null;
|
||||
state.selectedTreeEntry = NO_SELECTION;
|
||||
state.packetFrame = { tree: [], data_sources: [] };
|
||||
state.packetFrameTreeMap = null;
|
||||
state.selectedTree = NO_SELECTION;
|
||||
state.selectedDataSourceIndex = 0;
|
||||
}
|
||||
|
||||
@@ -121,23 +121,23 @@ export function usePCAP() {
|
||||
}
|
||||
|
||||
/**帧数据点击选中 */
|
||||
function handleSelectedTreeEntry(e: any) {
|
||||
console.log('fnSelectedTreeEntry', e);
|
||||
state.selectedTreeEntry = e;
|
||||
function handleSelectedTree(e: any) {
|
||||
// console.log('fnSelectedTree', e);
|
||||
state.selectedTree = e;
|
||||
}
|
||||
/**报文数据点击选中 */
|
||||
function handleSelectedFindSelection(src_idx: number, pos: number) {
|
||||
console.log('fnSelectedFindSelection', pos);
|
||||
if (state.packetFrameData == null) return;
|
||||
// console.log('fnSelectedFindSelection', pos);
|
||||
if (state.packetFrameTreeMap == null) return;
|
||||
// find the smallest one
|
||||
let current = null;
|
||||
for (let [k, pp] of state.packetFrameData) {
|
||||
for (let [k, pp] of state.packetFrameTreeMap) {
|
||||
if (pp.idx !== src_idx) continue;
|
||||
|
||||
if (pos >= pp.start && pos <= pp.start + pp.length) {
|
||||
if (
|
||||
current != null &&
|
||||
state.packetFrameData.get(current).length > pp.length
|
||||
state.packetFrameTreeMap.get(current).length > pp.length
|
||||
) {
|
||||
current = k;
|
||||
} else {
|
||||
@@ -146,19 +146,19 @@ export function usePCAP() {
|
||||
}
|
||||
}
|
||||
if (current != null) {
|
||||
state.selectedTreeEntry = state.packetFrameData.get(current);
|
||||
state.selectedTree = state.packetFrameTreeMap.get(current);
|
||||
}
|
||||
}
|
||||
/**包数据表点击选中 */
|
||||
function handleSelectedFrame(no: number) {
|
||||
console.log('fnSelectedFrame', no, state.totalFrames);
|
||||
// console.log('fnSelectedFrame', no, state.totalFrames);
|
||||
state.selectedFrame = no;
|
||||
wk.send({ type: 'select', number: state.selectedFrame });
|
||||
}
|
||||
/**包数据表滚动底部加载 */
|
||||
function handleScrollBottom() {
|
||||
const totalFetched = state.packetFrames.length;
|
||||
console.log('fnScrollBottom', totalFetched);
|
||||
// console.log('fnScrollBottom', totalFetched);
|
||||
if (!state.nextPageLoad && totalFetched < state.totalFrames) {
|
||||
state.nextPageLoad = true;
|
||||
state.nextPageNum++;
|
||||
@@ -167,7 +167,7 @@ export function usePCAP() {
|
||||
}
|
||||
/**包数据表过滤 */
|
||||
function handleFilterFrames() {
|
||||
console.log('fnFilterFinish', state.filter);
|
||||
// console.log('fnFilterFinish', state.filter);
|
||||
wk.send({ type: 'check-filter', filter: state.filter });
|
||||
}
|
||||
/**包数据表加载 */
|
||||
@@ -254,9 +254,9 @@ export function usePCAP() {
|
||||
}
|
||||
break;
|
||||
case 'selected':
|
||||
state.selectedPacket = res.data;
|
||||
state.packetFrameData = parseFrameData('root', res.data);
|
||||
state.selectedTreeEntry = NO_SELECTION;
|
||||
state.packetFrame = res.data;
|
||||
state.packetFrameTreeMap = parseFrameData('root', res.data);
|
||||
state.selectedTree = NO_SELECTION;
|
||||
state.selectedDataSourceIndex = 0;
|
||||
break;
|
||||
case 'processed':
|
||||
@@ -306,7 +306,7 @@ export function usePCAP() {
|
||||
|
||||
return {
|
||||
state,
|
||||
handleSelectedTreeEntry,
|
||||
handleSelectedTree,
|
||||
handleSelectedFindSelection,
|
||||
handleSelectedFrame,
|
||||
handleScrollBottom,
|
||||
|
||||
@@ -8,11 +8,12 @@ import DissectionDump from './components/DissectionDump.vue';
|
||||
import PacketTable from './components/PacketTable.vue';
|
||||
import { usePCAP, NO_SELECTION } from './hooks/usePCAP';
|
||||
import { parseSizeFromFile } from '@/utils/parse-utils';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
const { t } = useI18n();
|
||||
const {
|
||||
state,
|
||||
handleSelectedTreeEntry,
|
||||
handleSelectedTree,
|
||||
handleSelectedFindSelection,
|
||||
handleSelectedFrame,
|
||||
handleScrollBottom,
|
||||
@@ -49,8 +50,9 @@ function fnUpload(up: UploadRequestOption) {
|
||||
:loading="!state.initialized"
|
||||
:body-style="{ padding: '12px' }"
|
||||
>
|
||||
<div class="toolbar">
|
||||
<a-space :size="8" class="toolbar-oper">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-upload
|
||||
name="file"
|
||||
list-type="picture"
|
||||
@@ -64,61 +66,94 @@ function fnUpload(up: UploadRequestOption) {
|
||||
</a-upload>
|
||||
<a-button @click="handleLoadExample()">Example</a-button>
|
||||
</a-space>
|
||||
|
||||
<div class="toolbar-info">
|
||||
</template>
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<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>
|
||||
<!-- 包信息 -->
|
||||
<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>Start Time:</span>
|
||||
<span>
|
||||
{{
|
||||
parseDateToStr(
|
||||
state.summary.start_time * 1000,
|
||||
'YYYY-MM-DD HH:mm:ss.SSS'
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span>Stop Time:</span>
|
||||
<span>
|
||||
{{
|
||||
parseDateToStr(
|
||||
state.summary.stop_time * 1000,
|
||||
'YYYY-MM-DD HH:mm:ss.SSS'
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span>Duration:</span>
|
||||
<span>{{ Math.round(state.summary.elapsed_time) }}s</span>
|
||||
</div>
|
||||
</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>
|
||||
</template>
|
||||
<InfoCircleOutlined />
|
||||
</a-popover>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 包数据表过滤 -->
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
<a-auto-complete
|
||||
v-model:value="state.filter"
|
||||
placeholder="display filter, example: tcp"
|
||||
:allow-clear="true"
|
||||
:options="[
|
||||
{ value: 'http || tcp.port == 33030 || http2' },
|
||||
{ value: 'ip.src== 172.17.0.19 && ip.dst == 172.17.0.77' },
|
||||
{ value: 'sip || ngap' },
|
||||
]"
|
||||
style="width: calc(100% - 100px)"
|
||||
@pressEnter="handleFilterFrames"
|
||||
:allow-clear="true"
|
||||
>
|
||||
<template #prefix>
|
||||
<FilterOutlined />
|
||||
</template>
|
||||
</a-input>
|
||||
<a-input
|
||||
placeholder="display filter, example: tcp"
|
||||
@pressEnter="handleFilterFrames"
|
||||
>
|
||||
<template #prefix>
|
||||
<FilterOutlined />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-auto-complete>
|
||||
<a-button
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
@@ -143,14 +178,18 @@ function fnUpload(up: UploadRequestOption) {
|
||||
:onScrollBottom="handleScrollBottom"
|
||||
></PacketTable>
|
||||
|
||||
<a-row>
|
||||
<a-row
|
||||
:gutter="16"
|
||||
style="border: 2px rgb(217 217 217) solid; border-radius: 6px"
|
||||
v-show="state.selectedFrame > 0"
|
||||
>
|
||||
<a-col :lg="12" :md="12" :xs="24" class="tree">
|
||||
<!-- 帧数据 -->
|
||||
<DissectionTree
|
||||
id="root"
|
||||
:select="handleSelectedTreeEntry"
|
||||
:selected="state.selectedTreeEntry"
|
||||
:tree="state.selectedPacket.tree"
|
||||
:select="handleSelectedTree"
|
||||
:selected="state.selectedTree"
|
||||
:tree="state.packetFrame.tree"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24" class="dump">
|
||||
@@ -163,15 +202,15 @@ function fnUpload(up: UploadRequestOption) {
|
||||
<a-tab-pane
|
||||
:key="idx"
|
||||
:tab="v.name"
|
||||
v-for="(v, idx) in state.selectedPacket.data_sources"
|
||||
v-for="(v, idx) in state.packetFrame.data_sources"
|
||||
style="overflow: auto"
|
||||
>
|
||||
<DissectionDump
|
||||
:base64="v.data"
|
||||
:select="(pos:number)=>handleSelectedFindSelection(idx, pos)"
|
||||
:selected="
|
||||
idx === state.selectedTreeEntry.idx
|
||||
? state.selectedTreeEntry
|
||||
idx === state.selectedTree.idx
|
||||
? state.selectedTree
|
||||
: NO_SELECTION
|
||||
"
|
||||
/>
|
||||
@@ -184,17 +223,6 @@ function fnUpload(up: UploadRequestOption) {
|
||||
</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;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue';
|
||||
import { onBeforeUnmount, onMounted, reactive, ref, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { message, Modal } from 'ant-design-vue/es';
|
||||
import DissectionTree from '../tshark/components/DissectionTree.vue';
|
||||
import DissectionDump from '../tshark/components/DissectionDump.vue';
|
||||
import PacketTable from '../tshark/components/PacketTable.vue';
|
||||
import {
|
||||
RESULT_CODE_ERROR,
|
||||
RESULT_CODE_SUCCESS,
|
||||
} from '@/constants/result-constants';
|
||||
import { filePullTask } from '@/api/trace/task';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import { scriptUrl as wkUrl } from '@/assets/js/wiregasm_worker';
|
||||
import * as wkUtil from '@/plugins/wk-worker';
|
||||
import * as wsUtil from '@/plugins/ws-websocket';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import saveAs from 'file-saver';
|
||||
import {
|
||||
@@ -19,34 +19,20 @@ import {
|
||||
packetStop,
|
||||
packetFilter,
|
||||
packetKeep,
|
||||
packetPCAPFile,
|
||||
} from '@/api/trace/packet';
|
||||
const ws = new WS();
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||
const ws = new wsUtil.WS();
|
||||
const wk = new wkUtil.WK();
|
||||
const { t } = useI18n();
|
||||
|
||||
// =========== WK ==============
|
||||
const NO_SELECTION = { id: '', idx: 0, start: 0, length: 0 };
|
||||
|
||||
type StateType = {
|
||||
/**网卡设备列表 */
|
||||
devices: { id: string; label: string; children: any[] }[];
|
||||
/**初始化 */
|
||||
initialized: boolean;
|
||||
/**保活调度器 */
|
||||
keepTimer: any;
|
||||
/**任务 */
|
||||
task: {
|
||||
taskNo: string;
|
||||
device: string;
|
||||
filter: string;
|
||||
outputPCAP: boolean;
|
||||
};
|
||||
/**字段 */
|
||||
columns: string[];
|
||||
|
||||
/**过滤条件 */
|
||||
filter: string;
|
||||
/**过滤条件错误信息 */
|
||||
filterError: string | null;
|
||||
|
||||
/**当前选中的帧编号 */
|
||||
selectedFrame: number;
|
||||
/**当前选中的帧数据 */
|
||||
@@ -57,63 +43,19 @@ type StateType = {
|
||||
selectedTree: typeof NO_SELECTION;
|
||||
/**选择帧的Dump数据标签 */
|
||||
selectedDataSourceIndex: number;
|
||||
|
||||
/**包总数 */
|
||||
totalPackets: number;
|
||||
/**包数据 */
|
||||
packetList: any[];
|
||||
};
|
||||
|
||||
const state = reactive<StateType>({
|
||||
devices: [],
|
||||
initialized: false,
|
||||
keepTimer: null,
|
||||
task: {
|
||||
taskNo: 'laYlTbq',
|
||||
device: '192.168.5.58',
|
||||
filter: 'tcp and (port 33030 or 8080)',
|
||||
outputPCAP: false,
|
||||
},
|
||||
columns: [
|
||||
'No.',
|
||||
'Time',
|
||||
'Source',
|
||||
'Destination',
|
||||
'Protocol',
|
||||
'Length',
|
||||
'Info',
|
||||
],
|
||||
filter: 'tcp and (port 33030 or 8080)',
|
||||
filterError: null,
|
||||
selectedFrame: 1,
|
||||
selectedFrame: 0,
|
||||
/**当前选中的帧数据 */
|
||||
packetFrame: { tree: [], data_sources: [] },
|
||||
packetFrameTreeMap: null, // 注意:Map 需要额外处理
|
||||
selectedTree: NO_SELECTION, // NO_SELECTION 需要定义
|
||||
/**选择帧的Dump数据标签 */
|
||||
selectedDataSourceIndex: 0,
|
||||
// 包数据
|
||||
totalPackets: 0,
|
||||
packetList: [],
|
||||
});
|
||||
|
||||
/**清除帧数据和报文信息状态 */
|
||||
function fnReset() {
|
||||
state.initialized = false;
|
||||
// 选择帧的数据
|
||||
state.selectedFrame = 0;
|
||||
state.packetFrame = { tree: [], data_sources: [] };
|
||||
state.packetFrameTreeMap = null;
|
||||
state.selectedTree = NO_SELECTION;
|
||||
state.selectedDataSourceIndex = 0;
|
||||
// 过滤条件
|
||||
state.filter = 'tcp and (port 33030 or 8080)';
|
||||
state.filterError = null;
|
||||
// 包数据
|
||||
state.totalPackets = 0;
|
||||
state.packetList = [];
|
||||
}
|
||||
|
||||
/**解析帧数据为简单结构 */
|
||||
function parseFrameTree(id: string, node: Record<string, any>) {
|
||||
let map = new Map();
|
||||
@@ -136,15 +78,9 @@ function parseFrameTree(id: string, node: Record<string, any>) {
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**帧数据点击选中 */
|
||||
function handleSelectedTreeEntry(e: any) {
|
||||
console.log('fnSelectedTreeEntry', e);
|
||||
state.selectedTree = e;
|
||||
}
|
||||
/**报文数据点击选中 */
|
||||
function handleSelectedFindSelection(src_idx: number, pos: number) {
|
||||
console.log('fnSelectedFindSelection', pos);
|
||||
// console.log('fnSelectedFindSelection', pos);
|
||||
if (state.packetFrameTreeMap == null) return;
|
||||
// find the smallest one
|
||||
let current = null;
|
||||
@@ -166,83 +102,274 @@ function handleSelectedFindSelection(src_idx: number, pos: number) {
|
||||
state.selectedTree = state.packetFrameTreeMap.get(current);
|
||||
}
|
||||
}
|
||||
/**帧数据点击选中 */
|
||||
function handleSelectedTree(e: any) {
|
||||
state.selectedTree = e;
|
||||
}
|
||||
|
||||
/**包数据表点击选中 */
|
||||
function handleSelectedFrame(num: number) {
|
||||
console.log('fnSelectedFrame', num, state.totalPackets);
|
||||
const packet = state.packetList.find((v: any) => v.number === num);
|
||||
if (!packet) return;
|
||||
const packetFrame = packet.frame;
|
||||
state.selectedFrame = packet.number;
|
||||
state.packetFrame = packetFrame;
|
||||
state.packetFrameTreeMap = parseFrameTree('root', packetFrame);
|
||||
state.selectedTree = NO_SELECTION;
|
||||
state.selectedDataSourceIndex = 0;
|
||||
/**接收数据后回调 */
|
||||
function wkMessage(res: Record<string, any>) {
|
||||
// console.log('wkMessage', res);
|
||||
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 'frames':
|
||||
const { frames } = res.data;
|
||||
|
||||
// 有匹配的选择第一个
|
||||
if (frames.length > 0) {
|
||||
state.selectedFrame = frames[0].number;
|
||||
wk.send({ type: 'select', number: state.selectedFrame });
|
||||
}
|
||||
break;
|
||||
case 'selected':
|
||||
// 首行修改帧编号
|
||||
const fristFrame = res.data.tree[0];
|
||||
res.data.tree[0].label = fristFrame.label.replace(
|
||||
'Frame 1:',
|
||||
`Frame ${tableState.selectedNumber}:`
|
||||
);
|
||||
const item = fristFrame.tree.find(
|
||||
(item: any) => item.label === 'Frame Number: 1'
|
||||
);
|
||||
if (item) {
|
||||
item.label = `Frame Number: ${tableState.selectedNumber}:`;
|
||||
}
|
||||
|
||||
state.packetFrame = res.data;
|
||||
state.packetFrameTreeMap = parseFrameTree('root', res.data);
|
||||
state.selectedTree = NO_SELECTION;
|
||||
state.selectedDataSourceIndex = 0;
|
||||
break;
|
||||
case 'processed':
|
||||
// setStatus(`Error: non-zero return code (${e.data.code})`);
|
||||
// 加载数据
|
||||
wk.send({
|
||||
type: 'frames',
|
||||
filter: '',
|
||||
skip: 0,
|
||||
limit: 1,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
console.warn(res);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**包数据表滚动底部加载 */
|
||||
function handleScrollBottom(index: any) {
|
||||
console.log('handleScrollBottom', index);
|
||||
/**建立WK连接 */
|
||||
function fnWK() {
|
||||
const options: wkUtil.OptionsType = {
|
||||
url: wkUrl,
|
||||
onmessage: wkMessage,
|
||||
onerror: (ev: any) => {
|
||||
console.error(ev);
|
||||
},
|
||||
};
|
||||
//建立连接
|
||||
wk.connect(options);
|
||||
}
|
||||
|
||||
// =========== WS ==============
|
||||
/**接收数据后回调 */
|
||||
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) {
|
||||
tableState.data = [];
|
||||
tableState.total = 0;
|
||||
taskState.keepTimer = setInterval(() => {
|
||||
if (ws.state() != WebSocket.OPEN) {
|
||||
clearInterval(taskState.keepTimer);
|
||||
return;
|
||||
}
|
||||
packetKeep(taskState.task.taskNo, 120);
|
||||
}, 90 * 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
// 订阅组信息
|
||||
if (!data?.groupId) {
|
||||
return;
|
||||
}
|
||||
if (data.groupId === `4_${taskState.task.taskNo}`) {
|
||||
const packetData = data.data;
|
||||
tableState.total = packetData.number;
|
||||
tableState.data.unshift(packetData);
|
||||
if (tableState.data.length > 100) {
|
||||
tableState.data.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
/**建立WS连接 */
|
||||
function fnWS() {
|
||||
const options: wsUtil.OptionsType = {
|
||||
url: '/ws',
|
||||
params: {
|
||||
/**订阅通道组
|
||||
*
|
||||
* 信令跟踪Packet (GroupID:4_taskNo)
|
||||
*/
|
||||
subGroupID: `4_${taskState.task.taskNo}`,
|
||||
},
|
||||
heartTimer: 30 * 1000,
|
||||
onmessage: wsMessage,
|
||||
onerror: (ev: any) => {
|
||||
// 接收数据后回调
|
||||
console.error(ev);
|
||||
},
|
||||
};
|
||||
//建立连接
|
||||
ws.connect(options);
|
||||
}
|
||||
|
||||
// =========== 任务 ==============
|
||||
type TaskStateType = {
|
||||
/**网卡设备列表 */
|
||||
devices: { id: string; label: string; children: any[] }[];
|
||||
|
||||
/**任务 */
|
||||
task: {
|
||||
taskNo: string;
|
||||
device: string;
|
||||
filter: string;
|
||||
outputPCAP: boolean;
|
||||
};
|
||||
|
||||
/**过滤条件 */
|
||||
filter: string;
|
||||
/**过滤条件错误信息 */
|
||||
filterError: string | null;
|
||||
|
||||
/**保活调度器 */
|
||||
keepTimer: any;
|
||||
|
||||
/**停止标记 */
|
||||
stop: boolean;
|
||||
stopOutputPCAP: boolean;
|
||||
};
|
||||
|
||||
const taskState = reactive<TaskStateType>({
|
||||
devices: [],
|
||||
|
||||
task: {
|
||||
taskNo: '',
|
||||
device: '192.168.5.58',
|
||||
filter: '',
|
||||
outputPCAP: false,
|
||||
},
|
||||
|
||||
filter: '',
|
||||
filterError: null,
|
||||
|
||||
keepTimer: null,
|
||||
|
||||
stop: false,
|
||||
stopOutputPCAP: false,
|
||||
});
|
||||
|
||||
/**开始跟踪 */
|
||||
function fnStart() {
|
||||
// state.task.taskNo = 'laYlTbq';
|
||||
state.task.taskNo = Number(Date.now()).toString(16);
|
||||
state.task.outputPCAP = false;
|
||||
packetStart(state.task).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
fnReset();
|
||||
fnWS();
|
||||
} else {
|
||||
message.error(t('common.operateErr'), 3);
|
||||
}
|
||||
});
|
||||
if (taskState.keepTimer) return;
|
||||
taskState.keepTimer = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
// taskState.task.taskNo = 'laYlTbq';
|
||||
taskState.task.taskNo = Number(Date.now()).toString(16);
|
||||
taskState.task.filter = taskState.filter;
|
||||
packetStart(toRaw(taskState.task))
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 清空选择帧的数据
|
||||
state.selectedFrame = 0;
|
||||
state.packetFrame = { tree: [], data_sources: [] };
|
||||
state.packetFrameTreeMap = null;
|
||||
state.selectedTree = NO_SELECTION;
|
||||
state.selectedDataSourceIndex = 0;
|
||||
// 开启
|
||||
if (!state.initialized) {
|
||||
fnWK();
|
||||
}
|
||||
clearInterval(taskState.keepTimer);
|
||||
taskState.keepTimer = null;
|
||||
fnWS();
|
||||
// 记录状态
|
||||
taskState.stop = false;
|
||||
taskState.stopOutputPCAP = taskState.task.outputPCAP;
|
||||
} else {
|
||||
message.error(t('common.operateErr'), 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
});
|
||||
}
|
||||
|
||||
/**停止跟踪 */
|
||||
function fnStop() {
|
||||
packetStop(state.task.taskNo).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
if (typeof taskState.keepTimer !== 'number') return;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
packetStop(taskState.task.taskNo)
|
||||
.then(res => {
|
||||
if (res.code !== RESULT_CODE_SUCCESS) {
|
||||
message.warning(res.msg, 3);
|
||||
}
|
||||
// 关闭监听
|
||||
ws.close();
|
||||
state.initialized = false;
|
||||
state.filter = '';
|
||||
state.filterError = null;
|
||||
} else {
|
||||
message.warning(res.msg, 3);
|
||||
}
|
||||
});
|
||||
clearInterval(taskState.keepTimer);
|
||||
taskState.keepTimer = null;
|
||||
|
||||
taskState.filter = '';
|
||||
taskState.filterError = null;
|
||||
|
||||
taskState.stop = true;
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
});
|
||||
}
|
||||
|
||||
/**跟踪数据表过滤 */
|
||||
function handleFilterFrames() {
|
||||
packetFilter(state.task.taskNo, state.filter).then(res => {
|
||||
packetFilter(taskState.task.taskNo, taskState.filter).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
state.task.filter = state.filter;
|
||||
taskState.task.filter = taskState.filter;
|
||||
taskState.filterError = null;
|
||||
} else {
|
||||
state.filterError = res.msg;
|
||||
taskState.filterError = res.msg;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**开始跟踪 */
|
||||
/**选择网卡 */
|
||||
function fnDevice(v: string) {
|
||||
state.task.device = v;
|
||||
taskState.task.device = v;
|
||||
}
|
||||
|
||||
/**下载触发等待 */
|
||||
let downLoading = ref<boolean>(false);
|
||||
|
||||
/**信息文件下载 */
|
||||
function fnDownloadPCAP() {
|
||||
if (downLoading.value) return;
|
||||
const fileName = `trace_packet_${state.task.taskNo}.pcap`;
|
||||
const fileName = `packet_${taskState.task.taskNo}.pcap`;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.logManage.neFile.downTip', { fileName }),
|
||||
onOk() {
|
||||
downLoading.value = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
filePullTask(state.task.taskNo)
|
||||
packetPCAPFile(taskState.task.taskNo)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
@@ -267,90 +394,203 @@ function fnDownloadPCAP() {
|
||||
});
|
||||
}
|
||||
|
||||
/**接收数据后回调 */
|
||||
function wsMessage(res: Record<string, any>) {
|
||||
const { code, requestId, data } = res;
|
||||
if (code === RESULT_CODE_ERROR) {
|
||||
console.warn(res.msg);
|
||||
return;
|
||||
}
|
||||
// =========== 表格数据 ==============
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**记录数据 */
|
||||
data: Record<string, any>[];
|
||||
/**总记录数 */
|
||||
total: number;
|
||||
/**选择帧编号 */
|
||||
selectedNumber: number;
|
||||
};
|
||||
|
||||
// 建联时发送请求
|
||||
if (!requestId && data.clientId) {
|
||||
state.initialized = true;
|
||||
state.keepTimer = setInterval(() => {
|
||||
packetKeep(state.task.taskNo, 120);
|
||||
}, 90 * 1000);
|
||||
return;
|
||||
}
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
data: [],
|
||||
total: 0,
|
||||
selectedNumber: 0,
|
||||
});
|
||||
|
||||
// 订阅组信息
|
||||
if (!data?.groupId) {
|
||||
/**表格字段列 */
|
||||
let tableColumns = ref<ColumnsType>([
|
||||
{
|
||||
title: 'Number',
|
||||
dataIndex: 'number',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'Time',
|
||||
dataIndex: 'time',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
const nanoseconds = opt.value; // 纳秒时间戳
|
||||
const milliseconds = Math.floor(nanoseconds / 1000000);
|
||||
const nanosecondsPart = (nanoseconds % 1000000)
|
||||
.toString()
|
||||
.padStart(6, '0');
|
||||
|
||||
return `${parseDateToStr(
|
||||
milliseconds,
|
||||
'HH:mm:ss.SSS'
|
||||
)}.${nanosecondsPart}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Source',
|
||||
dataIndex: 'source',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: 'Destination',
|
||||
dataIndex: 'destination',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: 'Protocol',
|
||||
dataIndex: 'protocol',
|
||||
key: 'protocol',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'length',
|
||||
dataIndex: 'length',
|
||||
align: 'right',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'Info',
|
||||
dataIndex: 'info',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
},
|
||||
]);
|
||||
|
||||
/**
|
||||
* 查看帧数据
|
||||
* @param row 记录信息
|
||||
*/
|
||||
function fnVisible(row: Record<string, any>) {
|
||||
// 选中行重复点击时显示隐藏
|
||||
if (row.id === tableState.selectedNumber && state.selectedFrame !== 0) {
|
||||
state.selectedFrame = 0;
|
||||
return;
|
||||
}
|
||||
if (data.groupId === `4_${state.task.taskNo}`) {
|
||||
const packetData = data.data;
|
||||
state.totalPackets = packetData.number;
|
||||
state.packetList.push(packetData);
|
||||
}
|
||||
tableState.selectedNumber = row.number;
|
||||
const blob = generatePCAP(row.time / 1e3, row.data);
|
||||
wk.send({ type: 'process', file: blob });
|
||||
}
|
||||
/**生成PCAP-blob */
|
||||
function generatePCAP(timestamp: number, base64Data: string): Blob {
|
||||
// 1. 转换数据
|
||||
const binaryString = atob(base64Data);
|
||||
const len = binaryString.length;
|
||||
const bytes = new Uint8Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
|
||||
/**建立WS连接 */
|
||||
function fnWS() {
|
||||
const options: OptionsType = {
|
||||
url: '/ws',
|
||||
params: {
|
||||
/**订阅通道组
|
||||
*
|
||||
* 信令跟踪Packet (GroupID:4_taskNo)
|
||||
*/
|
||||
subGroupID: `4_${state.task.taskNo}`,
|
||||
},
|
||||
onmessage: wsMessage,
|
||||
onerror: (ev: any) => {
|
||||
// 接收数据后回调
|
||||
console.error(ev);
|
||||
},
|
||||
};
|
||||
//建立连接
|
||||
ws.connect(options);
|
||||
// 2. 创建PCAP文件头
|
||||
const fileHeader = new Uint8Array([
|
||||
0xd4,
|
||||
0xc3,
|
||||
0xb2,
|
||||
0xa1, // magic_number (微秒级)
|
||||
0x02,
|
||||
0x00, // version_major (2)
|
||||
0x04,
|
||||
0x00, // version_minor (4)
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // thiszone (UTC)
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // sigfigs (固定0)
|
||||
0xff,
|
||||
0xff,
|
||||
0x00,
|
||||
0x00, // snaplen (最大捕获长度)
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // network (LINKTYPE_ETHERNET)
|
||||
]);
|
||||
|
||||
// 3. 生成时间戳
|
||||
const date = new Date(timestamp);
|
||||
const secs = Math.floor(date.getTime() / 1000);
|
||||
const usecs = (date.getTime() % 1000) * 1000;
|
||||
|
||||
// 4. 构造PCAP报文头
|
||||
const packetHeader = new Uint8Array(16);
|
||||
const headerView = new DataView(packetHeader.buffer);
|
||||
headerView.setUint32(0, secs, true); // 时间戳秒
|
||||
headerView.setUint32(4, usecs, true); // 时间戳微秒
|
||||
headerView.setUint32(8, bytes.length, true); // 捕获长度
|
||||
headerView.setUint32(12, bytes.length, true); // 原始长度
|
||||
|
||||
// 5. 合并所有数据
|
||||
const finalData = new Uint8Array(
|
||||
fileHeader.length + packetHeader.length + bytes.length
|
||||
);
|
||||
finalData.set(fileHeader);
|
||||
finalData.set(packetHeader, fileHeader.length);
|
||||
finalData.set(bytes, fileHeader.length + packetHeader.length);
|
||||
|
||||
// 6. 文件Blob对象
|
||||
return new Blob([finalData], { type: 'application/octet-stream' });
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
packetDevices().then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
state.devices = res.data;
|
||||
taskState.devices = res.data;
|
||||
if (res.data.length === 0) return;
|
||||
state.task.device = res.data[0].id;
|
||||
taskState.task.device = res.data[0].id;
|
||||
}
|
||||
});
|
||||
fnWK();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(state.keepTimer);
|
||||
state.keepTimer = null;
|
||||
if (ws.state() === WebSocket.OPEN) ws.close();
|
||||
clearInterval(taskState.keepTimer);
|
||||
wk.send({ type: 'close' }) && wk.close();
|
||||
if (ws.state() <= WebSocket.OPEN) ws.close();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '12px' }">
|
||||
<div class="toolbar">
|
||||
<a-space :size="8" class="toolbar-oper">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="end">
|
||||
<a-dropdown-button
|
||||
type="primary"
|
||||
:disabled="state.initialized"
|
||||
@click="fnStart"
|
||||
:disabled="!taskState.stop && taskState.task.taskNo !== ''"
|
||||
>
|
||||
<PlayCircleOutlined />
|
||||
Start Trace
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
@click="({ key }:any) => fnDevice(key)"
|
||||
:selectedKeys="[state.task.device]"
|
||||
:selectedKeys="[taskState.task.device]"
|
||||
>
|
||||
<a-menu-item v-for="v in state.devices" :key="v.id">
|
||||
<a-menu-item v-for="v in taskState.devices" :key="v.id">
|
||||
<a-popover placement="rightTop" trigger="hover">
|
||||
<template #content>
|
||||
<div v-for="c in v.children">{{ c.id }}</div>
|
||||
@@ -366,79 +606,130 @@ onBeforeUnmount(() => {
|
||||
<template #icon><DownOutlined /></template>
|
||||
</a-dropdown-button>
|
||||
|
||||
<a-button danger @click.prevent="fnStop()" v-if="state.initialized">
|
||||
<a-button
|
||||
danger
|
||||
@click.prevent="fnStop()"
|
||||
:disabled="taskState.stop || taskState.task.taskNo === ''"
|
||||
>
|
||||
<template #icon><CloseCircleOutlined /></template>
|
||||
Stop Trace
|
||||
</a-button>
|
||||
|
||||
<a-checkbox
|
||||
v-model:checked="taskState.task.outputPCAP"
|
||||
:disabled="!taskState.stop && taskState.task.taskNo !== ''"
|
||||
>
|
||||
Output PCAP
|
||||
</a-checkbox>
|
||||
|
||||
<a-tag color="processing" v-if="taskState.task.filter !== ''">
|
||||
{{ taskState.task.filter }}
|
||||
</a-tag>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="end">
|
||||
<a-button
|
||||
type="primary"
|
||||
:loading="downLoading"
|
||||
@click.prevent="fnDownloadPCAP()"
|
||||
v-if="state.task.outputPCAP"
|
||||
v-if="taskState.stop && taskState.stopOutputPCAP"
|
||||
>
|
||||
<template #icon><DownloadOutlined /></template>
|
||||
{{ t('common.downloadText') }}
|
||||
</a-button>
|
||||
<a-tag
|
||||
color="green"
|
||||
v-show="!!state.task.filter && state.initialized"
|
||||
>
|
||||
{{ state.task.filter }}
|
||||
</a-tag>
|
||||
</a-space>
|
||||
|
||||
<a-space :size="8" class="toolbar-info" v-show="state.initialized">
|
||||
<span>
|
||||
{{ t('views.traceManage.task.traceId') }}:
|
||||
<strong>{{ state.task.taskNo }}</strong>
|
||||
Packets:
|
||||
<strong>{{ tableState.total }}</strong>
|
||||
</span>
|
||||
<span>
|
||||
Task No:
|
||||
<strong>{{ taskState.task.taskNo }}</strong>
|
||||
</span>
|
||||
<span> Packets: {{ state.totalPackets }} </span>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 包数据表过滤 -->
|
||||
<a-input-group compact v-show="state.initialized">
|
||||
<a-input
|
||||
v-model:value="state.filter"
|
||||
placeholder="display filter, example: tcp"
|
||||
:allow-clear="true"
|
||||
<a-input-group compact>
|
||||
<a-auto-complete
|
||||
v-model:value="taskState.filter"
|
||||
:options="[
|
||||
{ value: 'tcp and port 33030 and greater 100' },
|
||||
{
|
||||
value:
|
||||
'(src 192.168.5.58 and dst port 33030) or (src 192.168.9.59 and dst port 33030)',
|
||||
},
|
||||
{ value: 'src host 192.168.5.58 and dst host 192.168.5.58' },
|
||||
{ value: 'host 192.168.5.58 and greater 100 and less 2500' },
|
||||
]"
|
||||
style="width: calc(100% - 100px)"
|
||||
@pressEnter="handleFilterFrames"
|
||||
:allow-clear="true"
|
||||
>
|
||||
<template #prefix>
|
||||
<FilterOutlined />
|
||||
</template>
|
||||
</a-input>
|
||||
<a-input
|
||||
placeholder="BPF Basic Filter, example: tcp"
|
||||
@pressEnter="handleFilterFrames"
|
||||
>
|
||||
<template #prefix>
|
||||
<FilterOutlined />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-auto-complete>
|
||||
<a-button
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
style="width: 100px"
|
||||
@click="handleFilterFrames"
|
||||
:disabled="taskState.task.taskNo === '' || taskState.stop"
|
||||
>
|
||||
Filter
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<a-alert
|
||||
:message="state.filterError"
|
||||
:message="taskState.filterError"
|
||||
type="error"
|
||||
v-if="state.filterError != null"
|
||||
v-if="taskState.filterError != null"
|
||||
/>
|
||||
|
||||
<!-- 包数据表 -->
|
||||
<PacketTable
|
||||
:columns="state.columns"
|
||||
:data="state.packetList"
|
||||
:selectedFrame="state.selectedFrame"
|
||||
:onSelectedFrame="handleSelectedFrame"
|
||||
:onScrollBottom="handleScrollBottom"
|
||||
></PacketTable>
|
||||
|
||||
<a-row>
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="number"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
size="small"
|
||||
:pagination="false"
|
||||
:row-class-name="(record:any) => {
|
||||
return `table-striped-${record.protocol}`
|
||||
}"
|
||||
:customRow="
|
||||
record => {
|
||||
return {
|
||||
onClick: () => fnVisible(record),
|
||||
};
|
||||
}
|
||||
"
|
||||
:scroll="{ x: tableColumns.length * 150, y: '400px' }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'protocol'">
|
||||
<!-- <DictTag :options="dict.traceMsgType" :value="record.msgType" /> -->
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<!-- 帧数据 -->
|
||||
<a-row
|
||||
:gutter="16"
|
||||
style="border: 2px rgb(217 217 217) solid; border-radius: 6px"
|
||||
v-show="state.selectedFrame == 1"
|
||||
>
|
||||
<a-col :lg="12" :md="12" :xs="24" class="tree">
|
||||
<!-- 帧数据 -->
|
||||
<DissectionTree
|
||||
id="root"
|
||||
:select="handleSelectedTreeEntry"
|
||||
:select="handleSelectedTree"
|
||||
:selected="state.selectedTree"
|
||||
:tree="state.packetFrame.tree"
|
||||
/>
|
||||
@@ -474,28 +765,6 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.toolbar-oper {
|
||||
flex: 1;
|
||||
}
|
||||
.toolbar-info {
|
||||
text-align: right;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.summary-item > span:first-child {
|
||||
font-weight: 600;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.tree {
|
||||
font-size: 0.8125rem;
|
||||
line-height: 1.5rem;
|
||||
|
||||
Reference in New Issue
Block a user