Compare commits
113 Commits
2.2402.5-2
...
2.2404.1-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6faa59f32 | ||
|
|
65fab4de1b | ||
| b044f01b1d | |||
|
|
f4ed68fc44 | ||
|
|
4de98923b7 | ||
|
|
1e0e3a89cf | ||
|
|
edabd9a104 | ||
|
|
c11893566a | ||
|
|
c88bef959a | ||
|
|
ffd1b02bf9 | ||
|
|
075ad76d19 | ||
|
|
fbbd0496de | ||
|
|
00e8b7acbb | ||
|
|
3d40e0856e | ||
|
|
32d7897818 | ||
|
|
fa14f50e2d | ||
|
|
4eff5d48cd | ||
|
|
5380c4fb3f | ||
|
|
e10a82ff35 | ||
|
|
f85535672e | ||
|
|
3c1c0620d1 | ||
|
|
9531230fc0 | ||
|
|
151175fb3f | ||
|
|
9faf02a1a6 | ||
|
|
c2e95f607b | ||
|
|
3a50300bf9 | ||
|
|
188a108d87 | ||
|
|
11e788cb8f | ||
|
|
74195bc88b | ||
|
|
2a622aa1d6 | ||
|
|
d15b75e8fd | ||
|
|
4ae5f64da3 | ||
|
|
16ab366136 | ||
|
|
85abd7bca4 | ||
|
|
cd66ff927d | ||
|
|
d242a87191 | ||
|
|
e1eaba0d68 | ||
|
|
5a7c161ed2 | ||
|
|
a0a4e65d5e | ||
|
|
a6751424a5 | ||
|
|
8abaff86e5 | ||
|
|
e7442bf750 | ||
|
|
dce068fcb1 | ||
| 249d89af88 | |||
|
|
0f3a689b04 | ||
|
|
569eea08cb | ||
|
|
3cb80bbd85 | ||
|
|
e0d590724b | ||
|
|
2cfab00e8f | ||
|
|
06170ba38f | ||
|
|
7e28df210d | ||
|
|
a69e8c89f8 | ||
|
|
f5544c66bd | ||
|
|
7339f45193 | ||
|
|
739025935a | ||
|
|
eaa0e2d70c | ||
|
|
2bffdaef3a | ||
|
|
79809b56e9 | ||
|
|
e5896c8513 | ||
|
|
104041eea5 | ||
|
|
0b576ed446 | ||
|
|
c1174fc273 | ||
|
|
90e1cd022b | ||
|
|
001e5e887b | ||
|
|
8230dbf257 | ||
|
|
49fa7bdec8 | ||
|
|
bd13b70a88 | ||
|
|
9b5b43c2bf | ||
|
|
dee4733ad0 | ||
|
|
c07c1dfde3 | ||
|
|
181da5836b | ||
|
|
7a787db45d | ||
|
|
49c98697e1 | ||
|
|
7dedbb927c | ||
|
|
c2cc81fccf | ||
|
|
92b2a4fb37 | ||
|
|
b7d02ff669 | ||
|
|
b4179b4c60 | ||
|
|
d7e08a1fff | ||
|
|
d52fdc115c | ||
|
|
e86ae12801 | ||
|
|
87b2fd965c | ||
|
|
8397847e66 | ||
|
|
84b0575ab4 | ||
|
|
f19e5e48f3 | ||
|
|
073f17b61f | ||
|
|
4cdfe6097d | ||
|
|
d55a4e3da7 | ||
|
|
dd6c38c57e | ||
|
|
f705ba3a2e | ||
|
|
8721aa3a49 | ||
|
|
d2507e4706 | ||
| 0937efc6e6 | |||
| 57445cd48f | |||
| a193655353 | |||
|
|
10b150f13c | ||
|
|
c9fdd1785a | ||
|
|
a821d49eed | ||
|
|
28122bbfae | ||
|
|
3b4b085499 | ||
|
|
51b3388cdc | ||
|
|
908d9341db | ||
|
|
82a2ab158f | ||
|
|
ce893f32e0 | ||
|
|
65696dee09 | ||
|
|
625b39d901 | ||
|
|
4c20e639d8 | ||
|
|
f28dfeb9b4 | ||
|
|
a75c89c451 | ||
|
|
883816c540 | ||
|
|
24ba825365 | ||
|
|
accb02963c | ||
|
|
abe5595fa7 |
@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network EMS"
|
||||
VITE_APP_CODE = "CN EMS"
|
||||
|
||||
# 应用版本
|
||||
VITE_APP_VERSION = "2.2402.5"
|
||||
VITE_APP_VERSION = "2.240330.1"
|
||||
|
||||
# 接口基础URL地址-不带/后缀
|
||||
VITE_API_BASE_URL = "/omc-api"
|
||||
|
||||
@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network EMS"
|
||||
VITE_APP_CODE = "CN EMS"
|
||||
|
||||
# 应用版本
|
||||
VITE_APP_VERSION = "2.240205.5"
|
||||
VITE_APP_VERSION = "2.240330.1"
|
||||
|
||||
# 接口基础URL地址-不带/后缀
|
||||
VITE_API_BASE_URL = "/omc-api"
|
||||
|
||||
61
CHANGELOG.md
61
CHANGELOG.md
@@ -1,5 +1,52 @@
|
||||
# 版本发布日志
|
||||
|
||||
## 2.2404.1-20240402
|
||||
|
||||
- 新增 网元安装流程相关页面与操作相关接口联调
|
||||
- 新增 终端远程连接组件添加消息监听和发送句柄
|
||||
- 优化 移除vue-codemirror库改为codemirror,编辑输入支持 yaml语法
|
||||
- 优化 终端-基站信息Radio Name列宽调整
|
||||
- 更新 依赖版本
|
||||
|
||||
## 2.2403.1-20240319
|
||||
|
||||
- 新增 ws 工具连接状态函数
|
||||
- 新增 终端主机配置页面
|
||||
- 新增 终端主机命令页面
|
||||
- 新增 网元信息关联主机页面,表格支持勾选删除记录并展开看网元资源 sn
|
||||
- 新增 数据级缓存工具,支持表格字段排序状态缓存记录,表格字段排序支持缓存变更状态
|
||||
- 新增 网元主机操作翻译提示,网元主机操作 Hooks 包
|
||||
- 新增 IMS-CDR 和 AMF-UE 页面,以及翻译
|
||||
- 新增 网元快速安装页面,网元安装步骤页面模块
|
||||
- 新增 MML 网元操作 UPF 支持区分 telnet 发送端
|
||||
- 新增 网元快速安装步骤选择安装包界面
|
||||
- 新增 网元版本接口/网元软件包接口
|
||||
- 新增 自定义指标界面
|
||||
- 修复 帮助文档中英标题翻译文件名称指定《5G 核心网网管操作手册.pdf》
|
||||
- 修复 ws 函数发送消息判断是否连接正常
|
||||
- 修复 网元管理表单主键 id 重置失效导致新增操作为更新操作
|
||||
- 修复 网元选择列表不进行可操作使用网元状态过滤
|
||||
- 修复 移除获取网元列表,取消 status 状态过滤
|
||||
- 修复 缓存信息页面标题翻译,图表 Echart 语言数据设置
|
||||
- 修复 性能门限提交失败异常
|
||||
- 修复 提交异常并去除对 smdata 的校验
|
||||
- 优化 全局缓存网元列表,接口改换查询网元列表全部无分页
|
||||
- 优化 网元默认可选常量值,删除无效变量声明
|
||||
- 优化 UE-PCF 页面提示信息、网元信息页面样式、网元主机表单校验和提示翻译
|
||||
- 优化 拖动框地脚 null 类型异常,不支持 footer 插槽
|
||||
- 优化 切片文件上传文件名去除非法字符和空格,开发服务端口改为 33020
|
||||
- 更新 包依赖版本
|
||||
|
||||
## 2.2402.6-20240222
|
||||
|
||||
- 修复 UDM 用户数据超时时间 180s
|
||||
- 修复 UDM 鉴权 ki 和 opc 显示禁用输入
|
||||
- 新增 MML 命令输入框 shift+回车进行换行,回车直接发送
|
||||
- 修复 文档查看下载文件名称?显示问题
|
||||
- 修复 看板栏目添加跳转事件
|
||||
- 修复 看板用户行为字典数据翻译
|
||||
- 优化 看板用户行为 CDR 短信号码显示区间样式
|
||||
|
||||
## 2.2402.5-20240205
|
||||
|
||||
- 修复 拓扑架构图调整缺失的节点非网元元素,看板拓扑非网元点击排除
|
||||
@@ -17,14 +64,14 @@
|
||||
- 优化 配置参数编辑页面记录类型多语言是否系统内置
|
||||
- 优化 看板用户行为小屏幕换行显示字内容,格子高度百分比充满
|
||||
- 优化 看板推送用户行为插入又弹出减少渲染数量
|
||||
- 修复 看班告警统计判断修复,超时30秒,图渲染逻辑优化
|
||||
- 修复 参数配置DNN List参数显示null问题
|
||||
- 优化 ws消息队列延迟处理
|
||||
- 新增 看板用户事件CDR短信类型,短信结果200显示成功
|
||||
- 优化 看板告警统计0数字不显示
|
||||
- 优化 看板用户事件CDR指定类型MOC/MTSM
|
||||
- 修复 看班告警统计判断修复,超时 30 秒,图渲染逻辑优化
|
||||
- 修复 参数配置 DNN List 参数显示 null 问题
|
||||
- 优化 ws 消息队列延迟处理
|
||||
- 新增 看板用户事件 CDR 短信类型,短信结果 200 显示成功
|
||||
- 优化 看板告警统计 0 数字不显示
|
||||
- 优化 看板用户事件 CDR 指定类型 MOC/MTSM
|
||||
- 新增 黄金指标项图表显示全部开关控制
|
||||
- 新增 黄金指标项随机颜色,图表实时5s动态显示
|
||||
- 新增 黄金指标项随机颜色,图表实时 5s 动态显示
|
||||
|
||||
## 2.2401.4-20240130
|
||||
|
||||
|
||||
25
package.json
25
package.json
@@ -15,38 +15,41 @@
|
||||
"@ant-design/icons-vue": "^7.0.1",
|
||||
"@antv/g6": "~4.8.24",
|
||||
"@codemirror/lang-javascript": "^6.2.1",
|
||||
"@codemirror/merge": "^6.5.0",
|
||||
"@codemirror/lang-yaml": "^6.0.0",
|
||||
"@codemirror/merge": "^6.6.1",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@tato30/vue-pdf": "^1.9.3",
|
||||
"@vueuse/core": "^10.7.0",
|
||||
"@tato30/vue-pdf": "^1.9.6",
|
||||
"@vueuse/core": "~10.9.0",
|
||||
"ant-design-vue": "^3.2.20",
|
||||
"antdv-pro-layout": "^3.2.6",
|
||||
"codemirror": "^6.0.1",
|
||||
"dayjs": "^1.11.10",
|
||||
"echarts": "~5.4.2",
|
||||
"echarts": "~5.5.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"js-base64": "^3.7.5",
|
||||
"js-base64": "^3.7.7",
|
||||
"js-cookie": "^3.0.5",
|
||||
"localforage": "^1.10.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"p-queue": "^8.0.1",
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "~3.3.13",
|
||||
"vue-codemirror": "^6.1.1",
|
||||
"vue-i18n": "~9.9.0",
|
||||
"vue-i18n": "~9.10.0",
|
||||
"vue-router": "^4.2.5",
|
||||
"vue3-smooth-dnd": "^0.0.6",
|
||||
"xlsx": "^0.18.5"
|
||||
"xlsx": "^0.18.5",
|
||||
"xterm": "^5.3.0",
|
||||
"xterm-addon-fit": "^0.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/node": "^18.0.0",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@vitejs/plugin-vue": "^5.0.2",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"less": "^4.2.0",
|
||||
"typescript": "^5.3.3",
|
||||
"typescript": "^5.4.3",
|
||||
"unplugin-vue-components": "^0.26.0",
|
||||
"vite": "^5.0.10",
|
||||
"vite": "^5.2.6",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vue-tsc": "^1.8.27"
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
*/
|
||||
(function () {
|
||||
// host = ip:port
|
||||
const host = '192.168.8.100:3030';
|
||||
const host = '192.168.8.100:33030';
|
||||
|
||||
// Service Address
|
||||
sessionStorage.setItem('baseUrl', `http://${host}`);
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
}
|
||||
</style>`;
|
||||
|
||||
const lang = localStorage.getItem('cache:local:i18n');
|
||||
const lang = localStorage.getItem('cache:local:i18n') || 'en_US';
|
||||
// 根据浏览器选择语言
|
||||
// if (!lang) {
|
||||
// let preferredLanguage = navigator.language;
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
import {
|
||||
RESULT_CODE_ERROR,
|
||||
RESULT_CODE_SUCCESS,
|
||||
RESULT_MSG_ERROR,
|
||||
} from '@/constants/result-constants';
|
||||
import { language, request } from '@/plugins/http-fetch';
|
||||
import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||
|
||||
/**
|
||||
@@ -8,9 +12,8 @@ import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||
* @returns object
|
||||
*/
|
||||
export async function listNeInfo(query: Record<string, any>) {
|
||||
let totalSQL =
|
||||
'select count(*) as total from ne_info where (status=0 or status=3)';
|
||||
let rowsSQL = 'select * from ne_info where (status=0 or status=3) ';
|
||||
let totalSQL = 'select count(*) as total from ne_info where 1=1 ';
|
||||
let rowsSQL = 'select * from ne_info where 1=1 ';
|
||||
|
||||
// 系统特定顺序
|
||||
const specificOrder = [
|
||||
@@ -92,15 +95,18 @@ export async function getNeInfo(id: string | number) {
|
||||
url: `/api/rest/databaseManagement/v1/select/omc_db/ne_info`,
|
||||
method: 'get',
|
||||
params: {
|
||||
SQL: `select * from ne_info where (status=0 or status=3) and id = ${id}`,
|
||||
SQL: `select * from ne_info where id = ${id}`,
|
||||
},
|
||||
});
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
|
||||
let data = result.data.data[0];
|
||||
return Object.assign(result, {
|
||||
data: parseObjLineToHump(data['ne_info'][0]),
|
||||
});
|
||||
let neInfo = result.data.data[0]['ne_info'];
|
||||
if (neInfo) {
|
||||
return Object.assign(result, {
|
||||
data: parseObjLineToHump(neInfo[0]),
|
||||
});
|
||||
}
|
||||
return { code: RESULT_CODE_ERROR, msg: RESULT_MSG_ERROR[language] };
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -148,54 +154,6 @@ export async function delNeInfo(data: Record<string, any>) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取网元网元列表
|
||||
* @returns object
|
||||
*/
|
||||
export async function getNelistAll() {
|
||||
// 系统特定顺序
|
||||
const specificOrder = [
|
||||
'OMC',
|
||||
'MME',
|
||||
'AMF',
|
||||
'AUSF',
|
||||
'UDM',
|
||||
'SMF',
|
||||
'PCF',
|
||||
'UPF',
|
||||
'NRF',
|
||||
'NSSF',
|
||||
'IMS',
|
||||
'N3IWF',
|
||||
'NEF',
|
||||
'LMF',
|
||||
];
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/elementType/omc_db/objectType/ne_info`,
|
||||
method: 'get',
|
||||
params: {
|
||||
SQL: `SELECT ne_type,ne_name,ne_id,ip FROM ne_info WHERE status in ('0','3')`,
|
||||
},
|
||||
});
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
|
||||
let data = result.data.data[0];
|
||||
//通过sort进行冒泡排序
|
||||
data['ne_info'].sort((a: any, b: any) => {
|
||||
const typeA = specificOrder.indexOf(a.ne_type);
|
||||
const typeB = specificOrder.indexOf(b.ne_type);
|
||||
if (typeA === -1) return 1;
|
||||
if (typeB === -1) return -1;
|
||||
return typeA - typeB;
|
||||
});
|
||||
return Object.assign(result, {
|
||||
data: parseObjLineToHump(data['ne_info']),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出网元配置文件
|
||||
* @param
|
||||
|
||||
@@ -59,12 +59,12 @@ export async function listMain() {
|
||||
ipAddress,
|
||||
serialNum,
|
||||
name: key.split('/').join('_'),
|
||||
expiryDate:'-',
|
||||
status: 'Abnormal',
|
||||
};
|
||||
}
|
||||
return mergedObj;
|
||||
});
|
||||
|
||||
//通过sort进行冒泡排序
|
||||
mergedData.sort((a: any, b: any) => {
|
||||
const typeA = specificOrder.indexOf(a.name.split('_')[0]);
|
||||
@@ -73,7 +73,6 @@ export async function listMain() {
|
||||
if (typeB === -1) return -1; // 如果不在特定顺序中,排到后面
|
||||
return typeA - typeB;
|
||||
});
|
||||
//console.log(mergedData);
|
||||
|
||||
return mergedData;
|
||||
}
|
||||
|
||||
@@ -30,17 +30,19 @@ export async function getMMLByNE(neType: string) {
|
||||
* 发送网元的mml命令
|
||||
* @param neType 网元类型
|
||||
* @param neId 网元ID
|
||||
* @param objectType 接口类型
|
||||
* @param cmdStr 命令串
|
||||
* @returns
|
||||
*/
|
||||
export async function sendMMlByNE(
|
||||
neType: string,
|
||||
neId: string,
|
||||
objectType: string,
|
||||
cmdArr: string[]
|
||||
) {
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/operationManagement/v1/elementType/${neType}/objectType/mml?ne_id=${neId}`,
|
||||
url: `/api/rest/operationManagement/v1/elementType/${neType}/objectType/${objectType}?ne_id=${neId}`,
|
||||
method: 'post',
|
||||
data: { mml: cmdArr },
|
||||
timeout: 180_000,
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 查询网元列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listNe(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ne/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询网元状态
|
||||
* @param neType 网元类型
|
||||
* @param neId 网元ID
|
||||
* @returns object
|
||||
*/
|
||||
export function stateNe(neType: string, neId: string) {
|
||||
return request({
|
||||
url: '/ne/state',
|
||||
method: 'get',
|
||||
params: { neType, neId },
|
||||
});
|
||||
}
|
||||
103
src/api/ne/neHost.ts
Normal file
103
src/api/ne/neHost.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 查询网元主机列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listNeHost(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ne/host/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询网元主机详细
|
||||
* @param hostId 网元主机ID
|
||||
* @returns object
|
||||
*/
|
||||
export function getNeHost(hostId: string | number) {
|
||||
return request({
|
||||
url: `/ne/host/${hostId}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增网元主机
|
||||
* @param data 网元主机对象
|
||||
* @returns object
|
||||
*/
|
||||
export function addNeHost(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ne/host',
|
||||
method: 'post',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改网元主机
|
||||
* @param data 网元主机对象
|
||||
* @returns object
|
||||
*/
|
||||
export function updateNeHost(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ne/host',
|
||||
method: 'put',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除网元主机
|
||||
* @param hostId 网元主机ID
|
||||
* @returns object
|
||||
*/
|
||||
export function delNeHost(hostId: string | number) {
|
||||
return request({
|
||||
url: `/ne/host/${hostId}`,
|
||||
method: 'delete',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试连接网元主机
|
||||
* @param data 网元主机对象
|
||||
* @returns object
|
||||
*/
|
||||
export function testNeHost(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ne/host/test',
|
||||
method: 'post',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元主机SSH方式检查服务器环境
|
||||
* @param data 网元主机对象
|
||||
* @returns object
|
||||
*/
|
||||
export function neHostCheckInfo(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ne/host/checkBySSH',
|
||||
method: 'post',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元主机SSH方式授权免密发送
|
||||
* @param data 网元主机对象
|
||||
* @returns object
|
||||
*/
|
||||
export function neHostAuthorizedRSA(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ne/host/authorizedBySSH',
|
||||
method: 'post',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
64
src/api/ne/neHostCmd.ts
Normal file
64
src/api/ne/neHostCmd.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 查询网元主机命令列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listNeHostCmd(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ne/hostCmd/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询网元主机命令详细
|
||||
* @param cmdId 网元主机命令ID
|
||||
* @returns object
|
||||
*/
|
||||
export function getNeHostCmd(cmdId: string | number) {
|
||||
return request({
|
||||
url: `/ne/hostCmd/${cmdId}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增网元主机命令
|
||||
* @param data 网元主机命令对象
|
||||
* @returns object
|
||||
*/
|
||||
export function addNeHostCmd(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ne/hostCmd',
|
||||
method: 'post',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改网元主机命令
|
||||
* @param data 网元主机命令对象
|
||||
* @returns object
|
||||
*/
|
||||
export function updateNeHostCmd(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ne/hostCmd',
|
||||
method: 'put',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除网元主机命令
|
||||
* @param cmdId 网元主机命令ID
|
||||
* @returns object
|
||||
*/
|
||||
export function delNeHostCmd(cmdId: string | number) {
|
||||
return request({
|
||||
url: `/ne/hostCmd/${cmdId}`,
|
||||
method: 'delete',
|
||||
});
|
||||
}
|
||||
136
src/api/ne/neInfo.ts
Normal file
136
src/api/ne/neInfo.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 查询网元列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listNeInfo(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ne/info/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询网元信息详细
|
||||
* @param infoId 信息ID
|
||||
* @returns object
|
||||
*/
|
||||
export function getNeInfo(infoId: string | number) {
|
||||
return request({
|
||||
url: `/ne/info/${infoId}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元信息新增
|
||||
* @param data 网元对象
|
||||
* @returns object
|
||||
*/
|
||||
export function addNeInfo(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ne/info`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元信息修改
|
||||
* @param data 网元对象
|
||||
* @returns object
|
||||
*/
|
||||
export function updateNeInfo(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ne/info`,
|
||||
method: 'put',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元信息删除
|
||||
* @param id 信息ID
|
||||
* @returns object
|
||||
*/
|
||||
export function delNeInfo(infoIds: string | number) {
|
||||
return request({
|
||||
url: `/ne/info/${infoIds}`,
|
||||
method: 'delete',
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询网元列表全部无分页
|
||||
* @param query 查询参数 neType neId bandStatus
|
||||
* @returns object
|
||||
*/
|
||||
export function listAllNeInfo(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ne/info/listAll',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询网元状态
|
||||
* @param neType 网元类型
|
||||
* @param neId 网元ID
|
||||
* @returns object
|
||||
*/
|
||||
export function stateNeInfo(neType: string, neId: string) {
|
||||
return request({
|
||||
url: '/ne/info/state',
|
||||
method: 'get',
|
||||
params: { neType, neId },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询网元信息
|
||||
* @param neType 网元类型
|
||||
* @param neId 网元ID
|
||||
* @returns object
|
||||
*/
|
||||
export function getTypeAndIDNeInfo(neType: string, neId: string) {
|
||||
return request({
|
||||
url: '/ne/info/byTypeAndID',
|
||||
method: 'get',
|
||||
params: { neType, neId },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元端配置文件读取
|
||||
* @param neType 网元类型
|
||||
* @param neId 网元ID
|
||||
* @param filePath 不带文件时重新覆盖返回目录列表
|
||||
* @returns object
|
||||
*/
|
||||
export function getConfigFile(neType: string, neId: string, filePath: string) {
|
||||
return request({
|
||||
url: '/ne/info/configFile',
|
||||
method: 'get',
|
||||
params: { neType, neId, filePath },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元端配置文件写入
|
||||
* @param data neType网元类型 neId网元ID filePath文件 content内容 sync同步到网元
|
||||
* @returns object
|
||||
*/
|
||||
export function saveConfigFile(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ne/info/configFile`,
|
||||
method: 'put',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
108
src/api/ne/neLicense.ts
Normal file
108
src/api/ne/neLicense.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 查询网元授权列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listNeLicense(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ne/license/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询网元授权详细
|
||||
* @param licenseId 信息ID
|
||||
* @returns object
|
||||
*/
|
||||
export function getNeLicense(licenseId: string | number) {
|
||||
return request({
|
||||
url: `/ne/license/${licenseId}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元授权新增
|
||||
* @param data 网元对象
|
||||
* @returns object
|
||||
*/
|
||||
export function addNeLicense(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ne/license`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元授权修改
|
||||
* @param data 网元对象
|
||||
* @returns object
|
||||
*/
|
||||
export function updateNeLicense(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ne/license`,
|
||||
method: 'put',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元授权删除
|
||||
* @param id 信息ID
|
||||
* @returns object
|
||||
*/
|
||||
export function delNeLicense(licenseIds: string | number) {
|
||||
return request({
|
||||
url: `/ne/license/${licenseIds}`,
|
||||
method: 'delete',
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元授权激活授权申请码
|
||||
* @param neType 网元类型
|
||||
* @param neId 网元id
|
||||
* @returns object
|
||||
*/
|
||||
export function codeNeLicense(neType: string, neId: string) {
|
||||
return request({
|
||||
url: `/ne/license/code`,
|
||||
method: 'get',
|
||||
params: { neType, neId },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元授权激活授权文件替换
|
||||
* @param data 网元对象
|
||||
* @returns object
|
||||
*/
|
||||
export function changeNeLicense(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ne/license/change`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元授权激活状态
|
||||
* @param neType 网元类型
|
||||
* @param neId 网元id
|
||||
* @returns object
|
||||
*/
|
||||
export function stateNeLicense(neType: string, neId: string) {
|
||||
return request({
|
||||
url: `/ne/license/state`,
|
||||
method: 'get',
|
||||
params: { neType, neId },
|
||||
});
|
||||
}
|
||||
80
src/api/ne/neSoftware.ts
Normal file
80
src/api/ne/neSoftware.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 查询网元版本列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listNeSoftware(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ne/software/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询网元软件包详细
|
||||
* @param softwareId 信息ID
|
||||
* @returns object
|
||||
*/
|
||||
export function getNeSoftware(softwareId: string | number) {
|
||||
return request({
|
||||
url: `/ne/software/${softwareId}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元软件包新增
|
||||
* @param data 网元对象
|
||||
* @returns object
|
||||
*/
|
||||
export function addNeSoftware(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ne/software`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元软件包修改
|
||||
* @param data 网元对象
|
||||
* @returns object
|
||||
*/
|
||||
export function updateNeSoftware(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ne/software`,
|
||||
method: 'put',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元软件包删除
|
||||
* @param id 信息ID
|
||||
* @returns object
|
||||
*/
|
||||
export function delNeSoftware(softwareIds: string | number) {
|
||||
return request({
|
||||
url: `/ne/software/${softwareIds}`,
|
||||
method: 'delete',
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元软件包安装检查
|
||||
* @param data 网元对象
|
||||
* @returns object
|
||||
*/
|
||||
export function checkInstallNeSoftware(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ne/software/checkInstall`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
66
src/api/ne/neVersion.ts
Normal file
66
src/api/ne/neVersion.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 查询网元版本列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listNeVersion(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ne/version/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询网元版本详细
|
||||
* @param versionId 信息ID
|
||||
* @returns object
|
||||
*/
|
||||
export function getNeVersion(versionId: string | number) {
|
||||
return request({
|
||||
url: `/ne/version/${versionId}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元版本新增
|
||||
* @param data 网元对象
|
||||
* @returns object
|
||||
*/
|
||||
export function addNeVersion(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ne/version`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元版本修改
|
||||
* @param data 网元对象
|
||||
* @returns object
|
||||
*/
|
||||
export function updateNeVersion(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ne/version`,
|
||||
method: 'put',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元版本删除
|
||||
* @param id 信息ID
|
||||
* @returns object
|
||||
*/
|
||||
export function delNeVersion(versionIds: string | number) {
|
||||
return request({
|
||||
url: `/ne/version/${versionIds}`,
|
||||
method: 'delete',
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
28
src/api/neData/amf.ts
Normal file
28
src/api/neData/amf.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 查询AMF-UE会话事件列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listAMFDataUE(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/amf/ue/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* AMF-UE会话删除
|
||||
* @param id 信息ID
|
||||
* @returns object
|
||||
*/
|
||||
export function delAMFDataUE(ueIds: string | number) {
|
||||
return request({
|
||||
url: `/neData/amf/ue/${ueIds}`,
|
||||
method: 'delete',
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
28
src/api/neData/ims.ts
Normal file
28
src/api/neData/ims.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 查询IMS-CDR会话事件
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listIMSDataCDR(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/ims/cdr/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* IMS-CDR会话删除
|
||||
* @param id 信息ID
|
||||
* @returns object
|
||||
*/
|
||||
export function delIMSDataCDR(cdrIds: string | number) {
|
||||
return request({
|
||||
url: `/neData/ims/cdr/${cdrIds}`,
|
||||
method: 'delete',
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ export function exportAuth(query: Record<string, any>) {
|
||||
method: 'post',
|
||||
data: query,
|
||||
responseType: 'blob',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -78,6 +79,7 @@ export function updateAuth(data: Record<string, any>) {
|
||||
url: `/ne/udm/auth/${data.neId}`,
|
||||
method: 'put',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -91,6 +93,7 @@ export function addAuth(data: Record<string, any>) {
|
||||
url: `/ne/udm/auth/${data.neId}`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -104,6 +107,7 @@ export function batchAuth(data: Record<string, any>) {
|
||||
url: `/ne/udm/auth/${data.neID}/${data.num}`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -129,5 +133,6 @@ export function batchDelAuth(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ne/udm/auth/${data.neID}/${data.imsi}/${data.num}`,
|
||||
method: 'delete',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ export async function listBase5G(query: Record<string, any>) {
|
||||
data.rows = rows;
|
||||
}
|
||||
// 模拟数据
|
||||
// data.rows =[{"address":"192.168.1.137:38412","id":"217","name":"","ueNum":0}]
|
||||
// data.rows =[{"address":"192.168.1.137:38412","id":"217","name":"attach-enb-100000-20","ueNum":0}]
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -138,12 +138,18 @@ export async function batchUpdateRule(data: Record<string, any>) {
|
||||
data: data,
|
||||
});
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS && result.data?.status) {
|
||||
return {
|
||||
code: RESULT_CODE_ERROR,
|
||||
msg: result.data?.cause,
|
||||
data: result.data,
|
||||
};
|
||||
if (result.code === RESULT_CODE_SUCCESS) {
|
||||
if (result.data?.status) {
|
||||
return {
|
||||
code: RESULT_CODE_ERROR,
|
||||
msg: result.data?.cause,
|
||||
data: result.data,
|
||||
};
|
||||
}
|
||||
if (result.data?.data) {
|
||||
result.data = result.data.data;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -182,12 +188,18 @@ export async function batchAddRule(data: Record<string, any>) {
|
||||
data: data,
|
||||
});
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS && result.data?.status) {
|
||||
return {
|
||||
code: RESULT_CODE_ERROR,
|
||||
msg: result.data?.cause,
|
||||
data: result.data,
|
||||
};
|
||||
if (result.code === RESULT_CODE_SUCCESS) {
|
||||
if (result.data?.status) {
|
||||
return {
|
||||
code: RESULT_CODE_ERROR,
|
||||
msg: result.data?.cause,
|
||||
data: result.data,
|
||||
};
|
||||
}
|
||||
if (result.data?.data) {
|
||||
result.data = result.data.data;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ export function updateSub(neId: string, data: Record<string, any>) {
|
||||
url: `/ne/udm/sub/${neId}`,
|
||||
method: 'put',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -93,6 +94,7 @@ export function addSub(neID: string, data: Record<string, any>) {
|
||||
url: `/ne/udm/sub/${neID}`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -106,6 +108,7 @@ export function batchAddSub(data: Record<string, any>) {
|
||||
url: `/ne/udm/sub/${data.neID}/${data.num}`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -131,5 +134,6 @@ export function batchDelSub(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ne/udm/sub/${data.neID}/${data.imsi}/${data.num}`,
|
||||
method: 'delete',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
146
src/api/perfManage/customTarget.ts
Normal file
146
src/api/perfManage/customTarget.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
|
||||
/**
|
||||
* 查询自定义指标
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export async function listCustom(query: Record<string, any>) {
|
||||
let totalSQL = 'select count(*) as total from pm_custom_title where 1=1 ';
|
||||
let rowsSQL = 'select * from pm_custom_title where 1=1 ';
|
||||
|
||||
// 查询
|
||||
let querySQL = '';
|
||||
if (query.neType) {
|
||||
querySQL += ` and ne_type like '%${query.neType}%' `;
|
||||
}
|
||||
|
||||
// 排序
|
||||
let sortSql = ' order by update_time ';
|
||||
if (query.sortOrder === 'asc') {
|
||||
sortSql += ' asc ';
|
||||
} else {
|
||||
sortSql += ' desc ';
|
||||
}
|
||||
// 分页
|
||||
const pageNum = (query.pageNum - 1) * query.pageSize;
|
||||
const limtSql = ` limit ${pageNum},${query.pageSize} `;
|
||||
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/select/omc_db/pm_custom_title`,
|
||||
method: 'get',
|
||||
params: {
|
||||
totalSQL: totalSQL + querySQL,
|
||||
rowsSQL: rowsSQL + querySQL + sortSql + limtSql,
|
||||
},
|
||||
});
|
||||
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS) {
|
||||
const data: DataList = {
|
||||
total: 0,
|
||||
rows: [],
|
||||
code: result.code,
|
||||
msg: result.msg,
|
||||
};
|
||||
result.data.data.forEach((item: any) => {
|
||||
const itemData = item['pm_custom_title'];
|
||||
if (Array.isArray(itemData)) {
|
||||
if (itemData.length === 1 && itemData[0]['total'] >= 0) {
|
||||
data.total = itemData[0]['total'];
|
||||
} else {
|
||||
data.rows = itemData.map(v => parseObjLineToHump(v));
|
||||
}
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询自定义指标详细
|
||||
* @param id 网元ID
|
||||
* @returns object
|
||||
*/
|
||||
export async function getCustom(id: string | number) {
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/select/omc_db/pm_custom_title`,
|
||||
method: 'get',
|
||||
params: {
|
||||
SQL: `select * from pm_custom_title where id = ${id}`,
|
||||
},
|
||||
});
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
|
||||
let data = result.data.data[0];
|
||||
return Object.assign(result, {
|
||||
data: parseObjLineToHump(data['pm_custom_title'][0]),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增自定义指标
|
||||
* @param data 网元对象
|
||||
* @returns object
|
||||
*/
|
||||
export function addCustom(data: Record<string, any>) {
|
||||
let obj: any = {
|
||||
title: data.title,
|
||||
ne_type: data.neType,
|
||||
kpi_id: data.kpiId,
|
||||
object_type: data.objectType,
|
||||
expression: data.expression,
|
||||
period: data.period,
|
||||
description: data.description,
|
||||
kpi_set: data.kpiSet,
|
||||
};
|
||||
|
||||
return request({
|
||||
url: `/api/rest/databaseManagement/v1/omc_db/pm_custom_title`,
|
||||
method: 'post',
|
||||
data: { 'data': [obj] },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改自定义指标
|
||||
* @param data 网元对象
|
||||
* @returns object
|
||||
*/
|
||||
export function updateCustom(data: Record<string, any>) {
|
||||
let obj: any = {
|
||||
title: data.title,
|
||||
ne_type: data.neType,
|
||||
kpi_id: data.kpiId,
|
||||
object_type: data.objectType,
|
||||
expression: data.expression,
|
||||
period: data.period,
|
||||
description: data.description,
|
||||
kpi_set: data.kpiSet,
|
||||
};
|
||||
return request({
|
||||
url: `/api/rest/databaseManagement/v1/omc_db/pm_custom_title?WHERE=id=${data.id}`,
|
||||
method: 'put',
|
||||
data: { data: obj },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除自定义指标
|
||||
* @returns object
|
||||
*/
|
||||
export async function delCustom(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/api/rest/databaseManagement/v1/omc_db/pm_custom_title?WHERE=id=${data.id}`,
|
||||
method: 'delete',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||
|
||||
/**
|
||||
* Todo 废弃
|
||||
* 查询黄金指标数据
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
@@ -110,6 +110,7 @@ export async function getKPITitle(neType: string) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Todo 废弃
|
||||
* 查询UPF上下行速率数据
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
@@ -126,9 +127,7 @@ export async function listUPFData(timeArr: any) {
|
||||
url: `/api/rest/databaseManagement/v1/select/omc_db/gold_kpi`,
|
||||
method: 'get',
|
||||
params: {
|
||||
SQL: `SELECT gold_kpi.*,kpi_title.en_title FROM gold_kpi LEFT JOIN kpi_title on gold_kpi.kpi_id=kpi_title.kpi_id where 1=1 and gold_kpi.kpi_id ='UPF.03' and timestamp BETWEEN '${parseDateToStr(
|
||||
twentyFourHoursAgo
|
||||
)}' AND '${parseDateToStr(initTime)}' `,
|
||||
SQL: `SELECT gold_kpi.*,kpi_title.en_title FROM gold_kpi LEFT JOIN kpi_title on gold_kpi.kpi_id=kpi_title.kpi_id where 1=1 and gold_kpi.kpi_id ='UPF.03' AND timestamp BETWEEN DATE_SUB(NOW(), INTERVAL 10 MINUTE) AND NOW()`,
|
||||
},
|
||||
timeout: 60_000,
|
||||
}),
|
||||
@@ -137,9 +136,7 @@ export async function listUPFData(timeArr: any) {
|
||||
url: `/api/rest/databaseManagement/v1/select/omc_db/gold_kpi`,
|
||||
method: 'get',
|
||||
params: {
|
||||
SQL: `SELECT gold_kpi.*,kpi_title.en_title FROM gold_kpi LEFT JOIN kpi_title on gold_kpi.kpi_id=kpi_title.kpi_id where 1=1 and gold_kpi.kpi_id ='UPF.06' and timestamp BETWEEN '${parseDateToStr(
|
||||
twentyFourHoursAgo
|
||||
)}' AND '${parseDateToStr(initTime)}' `,
|
||||
SQL: `SELECT gold_kpi.*,kpi_title.en_title FROM gold_kpi LEFT JOIN kpi_title on gold_kpi.kpi_id=kpi_title.kpi_id where 1=1 and gold_kpi.kpi_id ='UPF.06' AND timestamp BETWEEN DATE_SUB(NOW(), INTERVAL 10 MINUTE) AND NOW()`,
|
||||
},
|
||||
timeout: 60_000,
|
||||
}),
|
||||
|
||||
@@ -103,7 +103,7 @@ export function addPerfThre(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/api/rest/databaseManagement/v1/omc_db/measure_threshold`,
|
||||
method: 'post',
|
||||
data: { measure_task: [obj] },
|
||||
data: { measure_threshold: [obj] },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -115,15 +115,15 @@ export function addPerfThre(data: Record<string, any>) {
|
||||
export function updatePerfThre(data: Record<string, any>) {
|
||||
let obj: any = {
|
||||
ne_type: data.neType,
|
||||
kpi_set: data.performanceArr,
|
||||
kpi_set: data.kpiSet,
|
||||
status: 'Inactive',
|
||||
orig_severity: data.origSeverity,
|
||||
threshold: data.threshold,
|
||||
threshold: ''+data.threshold,
|
||||
};
|
||||
return request({
|
||||
url: `/api/rest/databaseManagement/v1/omc_db/measure_task?WHERE=id=${data.id}`,
|
||||
url: `/api/rest/databaseManagement/v1/omc_db/measure_threshold?WHERE=id=${data.id}`,
|
||||
method: 'put',
|
||||
data: { data: obj },
|
||||
data: { measure_threshold: obj },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,12 @@ export async function uploadFileChunk(
|
||||
chunkSize: number = 1,
|
||||
subPath: string = 'default'
|
||||
) {
|
||||
const { name, size } = fileData;
|
||||
let { name, size } = fileData;
|
||||
// 去除非法字符
|
||||
const cleanedFilename = name.replace(/[\\/:*?"<>|]/g, '');
|
||||
// 去除空格
|
||||
name = cleanedFilename.replace(/\s/g, '_');
|
||||
// 数据块大小
|
||||
const chunkSizeInBytes = chunkSize * 1024 * 1024;
|
||||
// 文件标识使用唯一编码 MD5(文件名+文件大小)
|
||||
const fileIdentifier = `${name}-${size}`;
|
||||
@@ -125,6 +130,7 @@ export async function uploadFileChunk(
|
||||
const chunksIndex = `${index}`;
|
||||
// 跳过已上传块
|
||||
if (resCheck.data.includes(chunksIndex)) {
|
||||
uploadedSize += chunk.size;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,70 +1,117 @@
|
||||
<template>
|
||||
<codemirror
|
||||
:model-value="modelValue"
|
||||
:placeholder="props.placeholder"
|
||||
:style="props.editorStyle"
|
||||
:disabled="props.disabled"
|
||||
:autofocus="false"
|
||||
:indent-with-tab="true"
|
||||
:tab-size="props.tabSize"
|
||||
:extensions="[javascript(), oneDark]"
|
||||
@ready="fnReady"
|
||||
@change="fnChange"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Codemirror } from 'vue-codemirror';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { yaml } from '@codemirror/lang-yaml';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const emit = defineEmits(['update:value']);
|
||||
import { basicSetup, EditorView } from 'codemirror';
|
||||
import { indentWithTab } from '@codemirror/commands';
|
||||
import { keymap } from '@codemirror/view';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
import { ref, watch, onMounted, onBeforeUnmount } from 'vue';
|
||||
const emit = defineEmits(['update:value', 'change']);
|
||||
const props = defineProps({
|
||||
/**禁用输入使用v-model:value时不生效 */
|
||||
value: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
placeholder: {
|
||||
/**编辑框高度 */
|
||||
height: {
|
||||
type: String,
|
||||
default: 'input context here...',
|
||||
},
|
||||
/**是否禁止输入 */
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
editorStyle: {
|
||||
type: Object,
|
||||
default: () => ({ height: '400px !important' }),
|
||||
default: '400px',
|
||||
},
|
||||
/**缩进2空格 */
|
||||
tabSize: {
|
||||
type: Number,
|
||||
default: 2,
|
||||
},
|
||||
/**是否禁止输入 */
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/**高亮语言 支持javascript、yaml */
|
||||
lang: {
|
||||
type: String,
|
||||
default: 'javascript',
|
||||
},
|
||||
});
|
||||
|
||||
// 绑定值
|
||||
const modelValue = ref<string>('');
|
||||
/**视图容器 */
|
||||
const viewContainerDom = ref<HTMLElement | undefined>(undefined);
|
||||
let viewContainer: EditorView | null = null;
|
||||
|
||||
/**变更时更新绑定值 */
|
||||
function fnChange(value: string, viewUpdate: any) {
|
||||
if (props.disabled) return;
|
||||
emit('update:value', value);
|
||||
/**高亮语言拓展 */
|
||||
function fnLangExtension() {
|
||||
if (props.lang === 'yaml') {
|
||||
return yaml();
|
||||
}
|
||||
return javascript();
|
||||
}
|
||||
|
||||
/**组件渲染后 */
|
||||
function fnReady(payload: any) {
|
||||
modelValue.value = props.value;
|
||||
/**初始化渲染视图 */
|
||||
function handleRanderView(container: HTMLElement | undefined) {
|
||||
if (!container) return;
|
||||
viewContainer = new EditorView({
|
||||
doc: props.value,
|
||||
extensions: [
|
||||
oneDark,
|
||||
basicSetup,
|
||||
keymap.of([indentWithTab]),
|
||||
fnLangExtension(),
|
||||
EditorView.editable.of(!props.disabled),
|
||||
EditorState.readOnly.of(props.disabled),
|
||||
EditorState.tabSize.of(props.tabSize),
|
||||
EditorView.updateListener.of(v => {
|
||||
if (v.docChanged) {
|
||||
const docStr = v.state.doc.toString();
|
||||
emit('change', docStr, v.state.doc);
|
||||
// 禁用时不双向绑定,防止监听重复变化数值
|
||||
if (!props.disabled) {
|
||||
emit('update:value', docStr);
|
||||
}
|
||||
}
|
||||
}),
|
||||
],
|
||||
parent: container,
|
||||
});
|
||||
}
|
||||
|
||||
/**监听是否value改变 */
|
||||
watch(
|
||||
() => props.value,
|
||||
val => {
|
||||
modelValue.value = val;
|
||||
// 禁用时无输入靠外部值变化改变数值
|
||||
if (props.disabled && viewContainer) {
|
||||
const docLine = viewContainer.state.doc.length;
|
||||
viewContainer.dispatch({
|
||||
changes: { from: 0, to: docLine, insert: val },
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
handleRanderView(viewContainerDom.value);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
viewContainer?.destroy();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<template>
|
||||
<div
|
||||
ref="viewContainerDom"
|
||||
class="container"
|
||||
:style="{ '--editor-height': height }"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.container {
|
||||
--editor-height: 400px;
|
||||
}
|
||||
.container :deep(.cm-editor) {
|
||||
height: var(--editor-height);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,116 +1,131 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:title="props.title"
|
||||
width="80%"
|
||||
:visible="props.visible"
|
||||
:body-style="{ padding: '0 24px' }"
|
||||
:destroy-on-close="true"
|
||||
@cancel="fnCronModal(false)"
|
||||
@ok="fnCronModal(true)"
|
||||
>
|
||||
<div ref="mergeViewContainer" class="mergeViewContainer"></div>
|
||||
</a-modal>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { yaml } from '@codemirror/lang-yaml';
|
||||
import { oneDark } from '@codemirror/theme-one-dark';
|
||||
import { MergeView } from '@codemirror/merge';
|
||||
import { EditorView, basicSetup } from 'codemirror';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
|
||||
import { watch, ref } from 'vue';
|
||||
import { nextTick } from 'vue';
|
||||
|
||||
const emit = defineEmits(['cancel', 'ok', 'update:visible']);
|
||||
import { ref, onMounted, onBeforeUnmount, watch } from 'vue';
|
||||
const emit = defineEmits(['update:newArea', 'change']);
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '内容比较',
|
||||
},
|
||||
newArea: {
|
||||
type: String,
|
||||
default: '当前内容',
|
||||
},
|
||||
oldArea: {
|
||||
type: String,
|
||||
default: '原始内容',
|
||||
},
|
||||
/**当前变更内容 */
|
||||
newArea: {
|
||||
type: String,
|
||||
default: '当前内容',
|
||||
},
|
||||
/**编辑框高度 */
|
||||
height: {
|
||||
type: String,
|
||||
default: '400px !important',
|
||||
default: '400px',
|
||||
},
|
||||
/**缩进2空格 */
|
||||
tabSize: {
|
||||
type: Number,
|
||||
default: 2,
|
||||
},
|
||||
/**是否禁止输入 */
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
/**高亮语言 */
|
||||
lang: {
|
||||
type: String,
|
||||
default: 'javascript',
|
||||
},
|
||||
});
|
||||
|
||||
const mergeViewContainer = ref();
|
||||
/**视图容器 */
|
||||
const viewContainerDom = ref<HTMLElement | undefined>(undefined);
|
||||
let viewContainer: MergeView | null = null;
|
||||
|
||||
/**监听是否显示,初始cron属性 */
|
||||
/**高亮语言拓展 */
|
||||
function fnLangExtension() {
|
||||
if (props.lang === 'yaml') {
|
||||
return yaml();
|
||||
}
|
||||
return javascript();
|
||||
}
|
||||
|
||||
/**初始化渲染视图 */
|
||||
function handleRanderView(container: HTMLElement | undefined) {
|
||||
if (!container) return;
|
||||
viewContainer = new MergeView({
|
||||
a: {
|
||||
doc: props.oldArea,
|
||||
extensions: [
|
||||
fnLangExtension(),
|
||||
oneDark,
|
||||
basicSetup,
|
||||
EditorView.editable.of(false),
|
||||
EditorState.readOnly.of(true),
|
||||
],
|
||||
},
|
||||
b: {
|
||||
doc: props.newArea,
|
||||
extensions: [
|
||||
fnLangExtension(),
|
||||
oneDark,
|
||||
basicSetup,
|
||||
EditorView.editable.of(!props.disabled),
|
||||
EditorState.readOnly.of(props.disabled),
|
||||
EditorState.tabSize.of(props.tabSize),
|
||||
EditorView.updateListener.of(v => {
|
||||
if (v.docChanged) {
|
||||
const docStr = v.state.doc.toString();
|
||||
emit('change', docStr, v.state.doc);
|
||||
// 禁用时不双向绑定,防止监听重复变化数值
|
||||
if (!props.disabled) {
|
||||
emit('update:newArea', docStr);
|
||||
}
|
||||
}
|
||||
}),
|
||||
],
|
||||
},
|
||||
parent: container,
|
||||
});
|
||||
}
|
||||
|
||||
/**监听是否value改变 */
|
||||
watch(
|
||||
() => props.visible,
|
||||
() => props.newArea,
|
||||
val => {
|
||||
if (val) {
|
||||
// 开启时等待dom完成
|
||||
|
||||
nextTick(() => {
|
||||
// 设置高度
|
||||
mergeViewContainer.value.style.height = props.height;
|
||||
// 实例到dom
|
||||
new MergeView({
|
||||
a: {
|
||||
doc: props.oldArea,
|
||||
extensions: [
|
||||
javascript(),
|
||||
oneDark,
|
||||
basicSetup,
|
||||
EditorView.editable.of(false),
|
||||
EditorState.readOnly.of(true),
|
||||
],
|
||||
},
|
||||
b: {
|
||||
doc: props.newArea,
|
||||
extensions: [
|
||||
javascript(),
|
||||
oneDark,
|
||||
basicSetup,
|
||||
EditorView.editable.of(!props.disabled),
|
||||
EditorState.readOnly.of(props.disabled),
|
||||
],
|
||||
},
|
||||
parent: mergeViewContainer.value,
|
||||
});
|
||||
// 禁用时无输入靠外部值变化改变数值
|
||||
if (props.disabled && viewContainer) {
|
||||
const docLine = viewContainer.b.state.doc.length;
|
||||
viewContainer.b.dispatch({
|
||||
changes: { from: 0, to: docLine, insert: val },
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 窗口事件
|
||||
* @param val modal触发事件
|
||||
*/
|
||||
function fnCronModal(val: boolean) {
|
||||
emit('update:visible', false);
|
||||
if (val) {
|
||||
emit('ok', true);
|
||||
} else {
|
||||
emit('cancel');
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
handleRanderView(viewContainerDom.value);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
viewContainer?.destroy();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
ref="viewContainerDom"
|
||||
class="container"
|
||||
:style="{ '--editor-height': height }"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.mergeViewContainer {
|
||||
height: 400px;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
color: #abb2bf;
|
||||
background-color: #282c34;
|
||||
.container {
|
||||
--editor-height: 400px;
|
||||
}
|
||||
.container :deep(.cm-editor) {
|
||||
height: var(--editor-height);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -15,14 +15,24 @@ const props = defineProps({
|
||||
type: [Number, String],
|
||||
default: '',
|
||||
},
|
||||
/**数据默认值,当前值不存在时 */
|
||||
valueOption: {
|
||||
type: [Number, String],
|
||||
},
|
||||
});
|
||||
|
||||
/**遍历找到对应值数据项 */
|
||||
const item = computed(() => {
|
||||
if (Array.isArray(props.options) && props.options.length > 0) {
|
||||
const option = (props.options as any[]).find(
|
||||
let option = (props.options as any[]).find(
|
||||
item => `${item[props.valueField]}` === `${props.value}`
|
||||
);
|
||||
// 数据默认值
|
||||
if (props.valueOption != undefined && !option) {
|
||||
option = (props.options as any[]).find(
|
||||
item => `${item[props.valueField]}` === `${props.valueOption }`
|
||||
);
|
||||
}
|
||||
return option;
|
||||
}
|
||||
return undefined;
|
||||
|
||||
@@ -48,7 +48,7 @@ const props = defineProps({
|
||||
default: false,
|
||||
},
|
||||
footer: {
|
||||
type: Object,
|
||||
type: Object as any,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, watch, onMounted, PropType } from 'vue';
|
||||
import { reactive, watch, onMounted, PropType, nextTick } from 'vue';
|
||||
import { Container, Draggable } from 'vue3-smooth-dnd';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { type ColumnsType } from 'ant-design-vue/lib/table';
|
||||
const { t } = useI18n();
|
||||
import { dbGetJSON, dbSetJSON } from '@/utils/cache-db-utils';
|
||||
import { CACHE_DB_TABLE_DND } from '@/constants/cache-keys-constants';
|
||||
const { t, currentLocale } = useI18n();
|
||||
|
||||
const emit = defineEmits(['update:columns-dnd']);
|
||||
const props = defineProps({
|
||||
@@ -16,7 +18,8 @@ const props = defineProps({
|
||||
type: Array<any>,
|
||||
required: true,
|
||||
},
|
||||
/**按钮类型
|
||||
/**
|
||||
* 按钮类型
|
||||
* text 图标
|
||||
* ghost 图标按钮带文字
|
||||
*/
|
||||
@@ -24,16 +27,29 @@ const props = defineProps({
|
||||
type: String as PropType<'text' | 'ghost'>,
|
||||
default: 'text',
|
||||
},
|
||||
/**
|
||||
* 缓存列变更数据标识,不传则不缓存
|
||||
*/
|
||||
cacheId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
const tableColumns = reactive<ColumnsType>(props.columns);
|
||||
|
||||
/**表格字段列勾选状态 */
|
||||
const state = reactive({
|
||||
const state = reactive<{
|
||||
indeterminate: boolean;
|
||||
/**是否全选 */
|
||||
checkAll: boolean;
|
||||
/**字段标题列表 */
|
||||
columnsTitleList: string[];
|
||||
}>({
|
||||
indeterminate: false,
|
||||
checkAll: true,
|
||||
columnsTitleList: tableColumns.map(s => `${s.title}`),
|
||||
columnsTitleList: [],
|
||||
});
|
||||
|
||||
/**表格字段列全选操作 */
|
||||
@@ -46,12 +62,13 @@ function fnTableColumnsCheckAllChange(e: any) {
|
||||
/**表格字段列拖拽操作 */
|
||||
function fnTableColumnsDrop(dropResult: any) {
|
||||
const { removedIndex, addedIndex, payload } = dropResult;
|
||||
if (removedIndex !== null && addedIndex !== null) {
|
||||
let itemToAdd = payload;
|
||||
itemToAdd = tableColumns.splice(removedIndex, 1)[0];
|
||||
tableColumns.splice(addedIndex, 0, itemToAdd);
|
||||
fnUpdateColumns();
|
||||
if (!removedIndex || !addedIndex) {
|
||||
return;
|
||||
}
|
||||
let itemToAdd = payload;
|
||||
itemToAdd = tableColumns.splice(removedIndex, 1)[0];
|
||||
tableColumns.splice(addedIndex, 0, itemToAdd);
|
||||
fnUpdateColumns();
|
||||
}
|
||||
|
||||
/**表格字段列固定操作 */
|
||||
@@ -66,6 +83,7 @@ function fnTableColumnsFixed(row: Record<string, any>) {
|
||||
} else {
|
||||
row.fixed = !row.fixed;
|
||||
}
|
||||
fnUpdateColumns();
|
||||
}
|
||||
|
||||
/**表格字段列勾选字段变化 */
|
||||
@@ -77,7 +95,18 @@ function fnUpdateColumns() {
|
||||
if (list.length === 0) {
|
||||
list = [tableColumns[0]];
|
||||
}
|
||||
emit('update:columns-dnd', list);
|
||||
|
||||
if (props.cacheId) {
|
||||
// 存入数据
|
||||
dbSetJSON(
|
||||
CACHE_DB_TABLE_DND,
|
||||
`${props.cacheId}#${currentLocale.value}`,
|
||||
list
|
||||
);
|
||||
}
|
||||
nextTick(() => {
|
||||
emit('update:columns-dnd', list);
|
||||
});
|
||||
}
|
||||
|
||||
/**表格字段列勾选监听 */
|
||||
@@ -92,7 +121,34 @@ watch(
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
fnUpdateColumns();
|
||||
if (props.cacheId) {
|
||||
// 读取数据后响应
|
||||
dbGetJSON(CACHE_DB_TABLE_DND, `${props.cacheId}#${currentLocale.value}`)
|
||||
.then(data => {
|
||||
if (data) {
|
||||
const titleList: string[] = [];
|
||||
for (const item of data) {
|
||||
titleList.push(`${item.title}`);
|
||||
// 固定标记还原
|
||||
if (item.fixed !== undefined) {
|
||||
const fixedItem = tableColumns.find(s => s.title === item.title);
|
||||
if (fixedItem) {
|
||||
fixedItem.fixed = item.fixed;
|
||||
}
|
||||
}
|
||||
}
|
||||
state.columnsTitleList = titleList;
|
||||
} else {
|
||||
state.columnsTitleList = tableColumns.map(s => `${s.title}`);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
fnUpdateColumns();
|
||||
});
|
||||
} else {
|
||||
state.columnsTitleList = tableColumns.map(s => `${s.title}`);
|
||||
fnUpdateColumns();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
258
src/components/TerminalSSH/index.vue
Normal file
258
src/components/TerminalSSH/index.vue
Normal file
@@ -0,0 +1,258 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||
import { FitAddon } from 'xterm-addon-fit';
|
||||
import { Terminal } from 'xterm';
|
||||
import 'xterm/css/xterm.css';
|
||||
import { RESULT_CODE_ERROR } from '@/constants/result-constants';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
const ws = new WS();
|
||||
const emit = defineEmits(['connect', 'close', 'message']);
|
||||
const props = defineProps({
|
||||
/**终端ID,必传 */
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
/**连接主机ID,必传 */
|
||||
hostId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
/**窗口单行字符数 */
|
||||
cols: {
|
||||
type: Number,
|
||||
default: 80,
|
||||
},
|
||||
/**窗口行数 */
|
||||
rows: {
|
||||
type: Number,
|
||||
default: 40,
|
||||
},
|
||||
/**禁止输入 */
|
||||
disable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/**初始发送命令 */
|
||||
initCmd: {
|
||||
type: [String, Boolean],
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
/**终端输入DOM节点实例对象 */
|
||||
const terminalDom = ref<HTMLElement | undefined>(undefined);
|
||||
|
||||
/**终端输入实例对象 */
|
||||
const terminal = ref<any>(null);
|
||||
|
||||
/**终端输入渲染 */
|
||||
function handleRanderXterm(container: HTMLElement | undefined) {
|
||||
if (!container) return;
|
||||
const xterm = new Terminal({
|
||||
cols: props.cols,
|
||||
rows: props.rows,
|
||||
lineHeight: 1.2,
|
||||
fontSize: 12,
|
||||
fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace",
|
||||
theme: {
|
||||
background: '#000000',
|
||||
},
|
||||
cursorBlink: true, // 光标闪烁
|
||||
cursorStyle: 'block',
|
||||
scrollback: 1000,
|
||||
scrollSensitivity: 15,
|
||||
tabStopWidth: 4,
|
||||
disableStdin: props.disable, // 禁止输入
|
||||
});
|
||||
// 挂载
|
||||
xterm.open(container);
|
||||
// 自适应尺寸
|
||||
const fitAddon = new FitAddon();
|
||||
xterm.loadAddon(fitAddon);
|
||||
// 终端输入字符按键监听
|
||||
xterm.onData(char => {
|
||||
ws.send({
|
||||
requestId: `ssh_${props.hostId}`,
|
||||
type: 'ssh',
|
||||
data: char,
|
||||
});
|
||||
// const printable = char.match(/[\x20-\x7E]/); // 匹配可打印字符的正则表达式
|
||||
// if (char === '\r' || char === '\x0D') {
|
||||
// // 处理回车键,添加换行
|
||||
// xterm.writeln('');
|
||||
// } else if (char === '\x08' || char === '\x7F') {
|
||||
// // 处理退格键,删除最后一个字符
|
||||
// xterm.write('\b \b');
|
||||
// } else if (printable) {
|
||||
// // 处理可打印字符
|
||||
// xterm.write(char);
|
||||
// }
|
||||
});
|
||||
// 终端输入按键监听
|
||||
// xterm.onKey(({ key, domEvent }) => {
|
||||
// // console.log(key, domEvent);
|
||||
// // 单键输入
|
||||
// // switch (domEvent.key) {
|
||||
// // case 'ArrowUp':
|
||||
// // // 按“↑”方向键时要做的事。
|
||||
// // break;
|
||||
// // case 'ArrowDown':
|
||||
// // // 按“↓”方向键时要做的事。
|
||||
// // break;
|
||||
// // case 'ArrowLeft':
|
||||
// // // 按“←”方向键时要做的事。
|
||||
// // break;
|
||||
// // case 'ArrowRight':
|
||||
// // // 按“→”方向键时要做的事。
|
||||
// // break;
|
||||
// // case 'Enter':
|
||||
// // // 处理回车键,添加换行
|
||||
// // term.writeln('');
|
||||
// // break;
|
||||
// // case 'Backspace':
|
||||
// // // 处理退格键,删除最后一个字符
|
||||
// // term.write('\b \b');
|
||||
// // break;
|
||||
// // case 'Escape':
|
||||
// // // 按“ESC”键时要做的事。
|
||||
// // break;
|
||||
// // default:
|
||||
// // return; // 什么都没按就退出吧。
|
||||
// // }
|
||||
// });
|
||||
// 终端尺寸变化触发
|
||||
xterm.onResize(({ cols, rows }) => {
|
||||
// console.log('尺寸', cols, rows);
|
||||
ws.send({
|
||||
requestId: `ssh_resize_${props.hostId}`,
|
||||
type: 'ssh_resize',
|
||||
data: { cols, rows },
|
||||
});
|
||||
});
|
||||
|
||||
// 创建 ResizeObserver 实例
|
||||
var observer = new ResizeObserver(entries => {
|
||||
fitAddon.fit();
|
||||
});
|
||||
// 监听元素大小变化
|
||||
observer.observe(container);
|
||||
|
||||
terminal.value = xterm;
|
||||
}
|
||||
|
||||
/**连接打开后回调 */
|
||||
function wsOpen(ev: any) {
|
||||
// console.info('wsOpen', ev);
|
||||
nextTick(() => {
|
||||
handleRanderXterm(terminalDom.value);
|
||||
// 连接事件
|
||||
emit('connect', {
|
||||
timeStamp: ev.timeStamp,
|
||||
cols: terminal.value.cols,
|
||||
rows: terminal.value.rows,
|
||||
hostId: props.hostId,
|
||||
id: props.id,
|
||||
});
|
||||
// 初始发送命令
|
||||
if (typeof props.initCmd === 'string') {
|
||||
ws.send({
|
||||
requestId: `ssh_${props.hostId}`,
|
||||
type: 'ssh',
|
||||
data: `${props.initCmd}\n`,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**连接错误后回调 */
|
||||
function wsError(ev: any) {
|
||||
// console.error('wsError', ev);
|
||||
if (terminal.value != null) {
|
||||
let message = 'disconnected';
|
||||
terminal.value.write(`\x1b[31m${message}\x1b[m\r\n`);
|
||||
} else if (terminalDom.value) {
|
||||
terminalDom.value.style.background = '#000';
|
||||
terminalDom.value.style.color = '#ff4d4f';
|
||||
terminalDom.value.style.height = '60%';
|
||||
terminalDom.value.innerText = 'disconnected';
|
||||
}
|
||||
}
|
||||
|
||||
/**连接关闭后回调 */
|
||||
function wsClose(code: number) {
|
||||
// console.warn('wsClose', code);
|
||||
if (terminal.value != null) {
|
||||
let message = 'disconnected';
|
||||
terminal.value.write(`\x1b[31m${message}\x1b[m\r\n`);
|
||||
}
|
||||
// 关闭事件
|
||||
emit('close', {
|
||||
code: code,
|
||||
hostId: props.hostId,
|
||||
id: props.id,
|
||||
});
|
||||
}
|
||||
|
||||
/**接收消息后回调 */
|
||||
function wsMessage(res: Record<string, any>) {
|
||||
emit('message', res);
|
||||
// console.log('wsMessage', res);
|
||||
const { code, requestId, data } = res;
|
||||
if (code === RESULT_CODE_ERROR) {
|
||||
console.warn(res.msg);
|
||||
return;
|
||||
}
|
||||
if (!requestId) return;
|
||||
if (terminal.value != null) {
|
||||
terminal.value.write(data);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.hostId) {
|
||||
// 建立链接
|
||||
const options: OptionsType = {
|
||||
url: '/ws/ssh',
|
||||
params: {
|
||||
hostId: props.hostId,
|
||||
cols: props.cols,
|
||||
rows: props.rows,
|
||||
},
|
||||
onmessage: wsMessage,
|
||||
onerror: wsError,
|
||||
onopen: wsOpen,
|
||||
onclose: wsClose,
|
||||
};
|
||||
ws.connect(options);
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
ws.close();
|
||||
});
|
||||
|
||||
// 给组件设置属性 ref="xxxTerminal"
|
||||
// setup内使用 const xxxTerminal = ref();
|
||||
defineExpose({
|
||||
/**发送方法 */
|
||||
send: (data: string) => {
|
||||
ws.send({
|
||||
requestId: `ssh_${props.hostId}`,
|
||||
type: 'ssh',
|
||||
data: `${data}\n`,
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="terminalDom" :id="id" class="terminal"></div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.terminal {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
293
src/components/TerminalTelnet/index.vue
Normal file
293
src/components/TerminalTelnet/index.vue
Normal file
@@ -0,0 +1,293 @@
|
||||
<script lang="ts" setup>
|
||||
import { message } from 'ant-design-vue/lib';
|
||||
import { ref, reactive, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||
import { FitAddon } from 'xterm-addon-fit';
|
||||
import { Terminal } from 'xterm';
|
||||
import 'xterm/css/xterm.css';
|
||||
import { RESULT_CODE_ERROR } from '@/constants/result-constants';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
const ws = new WS();
|
||||
const emit = defineEmits(['connect', 'close', 'message']);
|
||||
const props = defineProps({
|
||||
/**终端ID,必传 */
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
/**连接主机ID,必传 */
|
||||
hostId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
/**禁止输入 */
|
||||
disable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/**初始发送命令 */
|
||||
initCmd: {
|
||||
type: [String, Boolean],
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
/**终端输入DOM节点实例对象 */
|
||||
const terminalDom = ref<HTMLElement | undefined>(undefined);
|
||||
|
||||
/**终端输入实例对象 */
|
||||
const terminal = ref<any>(null);
|
||||
|
||||
/**终端输入文字状态 */
|
||||
const terminalState = reactive<{
|
||||
/**输入值 */
|
||||
text: string;
|
||||
/**历史 */
|
||||
history: {
|
||||
label?: string;
|
||||
value: string;
|
||||
}[];
|
||||
}>({
|
||||
text: '',
|
||||
history: [
|
||||
{
|
||||
value: 'help',
|
||||
},
|
||||
{
|
||||
value: 'quit',
|
||||
},
|
||||
{
|
||||
value: 'list ver',
|
||||
},
|
||||
{
|
||||
value: 'list lic',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
/**自动完成根据输入项进行筛选 */
|
||||
function fnAutoCompleteFilter(input: string, option: any) {
|
||||
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
}
|
||||
|
||||
/**自动完成按键触发 */
|
||||
function fnAutoCompleteKeydown(evt: any) {
|
||||
if (evt.key === 'Enter') {
|
||||
// 阻止默认的换行行为
|
||||
evt.preventDefault();
|
||||
// 按下 Shift + Enter 键时换行
|
||||
if (evt.shiftKey) {
|
||||
// 插入换行符
|
||||
const textarea = evt.target;
|
||||
const start = textarea.selectionStart;
|
||||
const end = textarea.selectionEnd;
|
||||
const text = textarea.value;
|
||||
textarea.value = text.substring(0, start) + '\n' + text.substring(end);
|
||||
terminalState.text = textarea.value;
|
||||
// 更新光标位置
|
||||
textarea.selectionStart = textarea.selectionEnd = start + 1;
|
||||
} else {
|
||||
// ws未连接
|
||||
if (ws.state() !== WebSocket.OPEN) {
|
||||
message.error('disconnected');
|
||||
return;
|
||||
}
|
||||
|
||||
// 输入历史
|
||||
const cmdStr = terminalState.text.trim().replace(/\n/g, '\r\n');
|
||||
const hisIndex = terminalState.history.findIndex(
|
||||
item => item.value === cmdStr
|
||||
);
|
||||
if (hisIndex === -1) {
|
||||
terminalState.history.push({
|
||||
value: cmdStr,
|
||||
});
|
||||
}
|
||||
|
||||
// 发送文本
|
||||
terminal.value.scrollToBottom();
|
||||
terminal.value.writeln(cmdStr);
|
||||
ws.send({
|
||||
requestId: `telnet_${props.hostId}`,
|
||||
type: 'telnet',
|
||||
data: `${cmdStr}\r\n'`,
|
||||
});
|
||||
terminalState.text = ' ';
|
||||
|
||||
// 退出登录
|
||||
if (['q', 'quit', 'exit'].includes(cmdStr)) {
|
||||
setTimeout(() => {
|
||||
ws.close();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**终端输入渲染 */
|
||||
function handleRanderXterm(container: HTMLElement | undefined) {
|
||||
if (!container) return;
|
||||
const xterm = new Terminal({
|
||||
lineHeight: 1.2,
|
||||
fontSize: 12,
|
||||
fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace",
|
||||
theme: {
|
||||
background: '#000000',
|
||||
},
|
||||
cursorBlink: true, // 光标闪烁
|
||||
cursorStyle: 'block',
|
||||
scrollback: 1000,
|
||||
scrollSensitivity: 15,
|
||||
tabStopWidth: 4,
|
||||
disableStdin: props.disable, // 禁止输入
|
||||
});
|
||||
// 挂载
|
||||
xterm.open(container);
|
||||
// 自适应尺寸
|
||||
const fitAddon = new FitAddon();
|
||||
xterm.loadAddon(fitAddon);
|
||||
|
||||
// 创建 ResizeObserver 实例
|
||||
var observer = new ResizeObserver(entries => {
|
||||
fitAddon.fit();
|
||||
});
|
||||
// 监听元素大小变化
|
||||
observer.observe(container);
|
||||
|
||||
terminal.value = xterm;
|
||||
}
|
||||
|
||||
/**连接打开后回调 */
|
||||
function wsOpen(ev: any) {
|
||||
// console.info('wsOpen', ev);
|
||||
nextTick(() => {
|
||||
handleRanderXterm(terminalDom.value);
|
||||
|
||||
// 连接事件
|
||||
emit('connect', {
|
||||
timeStamp: ev.timeStamp,
|
||||
cols: terminal.value.cols,
|
||||
rows: terminal.value.rows,
|
||||
hostId: props.hostId,
|
||||
id: props.id,
|
||||
});
|
||||
// 初始发送命令
|
||||
if (typeof props.initCmd === 'string') {
|
||||
ws.send({
|
||||
requestId: `telnet_${props.hostId}`,
|
||||
type: 'telnet',
|
||||
data: `${props.initCmd}\r\n`,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**连接错误后回调 */
|
||||
function wsError(ev: any) {
|
||||
console.error('wsError', ev);
|
||||
if (terminal.value != null) {
|
||||
let message = 'disconnected';
|
||||
terminal.value.write(`\x1b[31m${message}\x1b[m\r\n`);
|
||||
} else if (terminalDom.value) {
|
||||
terminalDom.value.style.background = '#000';
|
||||
terminalDom.value.style.color = '#ff4d4f';
|
||||
terminalDom.value.style.height = '60%';
|
||||
terminalDom.value.innerText = 'disconnected';
|
||||
}
|
||||
}
|
||||
|
||||
/**连接关闭后回调 */
|
||||
function wsClose(code: number) {
|
||||
// console.warn('wsClose', code);
|
||||
if (terminal.value != null) {
|
||||
let message = 'disconnected';
|
||||
terminal.value.write(`\x1b[31m${message}\x1b[m\r\n`);
|
||||
}
|
||||
// 关闭事件
|
||||
emit('close', {
|
||||
code: code,
|
||||
hostId: props.hostId,
|
||||
id: props.id,
|
||||
});
|
||||
}
|
||||
|
||||
/**接收消息后回调 */
|
||||
function wsMessage(res: Record<string, any>) {
|
||||
emit('message', res);
|
||||
// console.log('wsMessage', res);
|
||||
const { code, requestId, data } = res;
|
||||
if (code === RESULT_CODE_ERROR) {
|
||||
console.warn(res.msg);
|
||||
return;
|
||||
}
|
||||
if (!requestId) return;
|
||||
if (terminal.value != null) {
|
||||
// terminal.value.write(data.trim().replace(/\n/g, "\r\n"));
|
||||
// 是否n结尾
|
||||
if (/[\r\n]$/.test(data)) {
|
||||
terminal.value.writeln(data.trim().replace(/\n/g, '\r\n'));
|
||||
} else {
|
||||
terminal.value.write(data.replace(/\n/g, '\r\n'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.hostId) {
|
||||
// 建立链接
|
||||
const options: OptionsType = {
|
||||
url: '/ws/telnet',
|
||||
params: {
|
||||
hostId: props.hostId,
|
||||
},
|
||||
onmessage: wsMessage,
|
||||
onerror: wsError,
|
||||
onopen: wsOpen,
|
||||
onclose: wsClose,
|
||||
};
|
||||
ws.connect(options);
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
ws.close();
|
||||
});
|
||||
|
||||
// 给组件设置属性 ref="xxxTerminal"
|
||||
// setup内使用 const xxxTerminal = ref();
|
||||
defineExpose({
|
||||
/**发送方法 */
|
||||
send: (data: string) => {
|
||||
ws.send({
|
||||
requestId: `telnet_${props.hostId}`,
|
||||
type: 'telnet',
|
||||
data: `${data}\r\n`,
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="terminal">
|
||||
<div ref="terminalDom" style="height: 78%" :id="id"></div>
|
||||
<a-auto-complete
|
||||
v-model:value="terminalState.text"
|
||||
:dropdown-match-select-width="500"
|
||||
style="width: 100%"
|
||||
:options="terminalState.history"
|
||||
:filter-option="fnAutoCompleteFilter"
|
||||
@keydown="fnAutoCompleteKeydown"
|
||||
>
|
||||
<a-textarea
|
||||
:auto-size="{ minRows: 1, maxRows: 6 }"
|
||||
placeholder="Execute command. Shift+Enter to line feed, Enter to send"
|
||||
/>
|
||||
</a-auto-complete>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.terminal {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -12,3 +12,6 @@ export const CACHE_LOCAL_I18N = 'cache:local:i18n';
|
||||
|
||||
/**本地缓存-锁屏设置 */
|
||||
export const CACHE_LOCAL_LOCK = 'cache:local:Lock';
|
||||
|
||||
/**数据缓存表-表格排序 */
|
||||
export const CACHE_DB_TABLE_DND = 'tbl_dnd';
|
||||
|
||||
18
src/constants/ne-constants.ts
Normal file
18
src/constants/ne-constants.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/**网元列表,默认顺序 */
|
||||
export const NE_TYPE_LIST = [
|
||||
'OMC',
|
||||
'MME',
|
||||
'AMF',
|
||||
'AUSF',
|
||||
'UDM',
|
||||
'SMF',
|
||||
'PCF',
|
||||
'UPF',
|
||||
'NRF',
|
||||
'NSSF',
|
||||
'IMS',
|
||||
'N3IWF',
|
||||
'NEF',
|
||||
'LMF',
|
||||
'MOCNGW',
|
||||
];
|
||||
@@ -54,6 +54,8 @@ export default {
|
||||
},
|
||||
rowId: 'ID',
|
||||
operate: 'Operation',
|
||||
operateOk: 'Operation Successful!',
|
||||
operateErr: 'Operation Failed!',
|
||||
units: {
|
||||
second: 'Second',
|
||||
minute: 'Minute',
|
||||
@@ -362,6 +364,7 @@ export default {
|
||||
totalSure:'Confirm the network element with {operator} network element name {msg}',
|
||||
stop: 'Stop',
|
||||
start: 'Start',
|
||||
log: 'Logs',
|
||||
export: 'Export',
|
||||
import: 'Import',
|
||||
fileForm:'File Source',
|
||||
@@ -492,20 +495,25 @@ export default {
|
||||
overview:{
|
||||
title: "Core Network Dashboard",
|
||||
fullscreen: "Click on the full-screen display",
|
||||
toRouter: "Click to jump to the detail page",
|
||||
skim: {
|
||||
users: "Users",
|
||||
userTitle:'User Information',
|
||||
session: "Sessions",
|
||||
base: "Base Stations",
|
||||
baseTitle:'Radios Information',
|
||||
imsUeNum: "VoNR/VoLTE",
|
||||
smfUeNum: "Data Sessions",
|
||||
gnbBase: "Online gNodeB",
|
||||
enbBase: "Online eNodeB",
|
||||
gnbUeNum:'5G Active Users',
|
||||
enbUeNum:'4G Active Users',
|
||||
baseTitle:'Online Information',
|
||||
},
|
||||
upfFlow:{
|
||||
title: "User Plane Throughput",
|
||||
title: "UPF Throughput",
|
||||
up:'Uplink',
|
||||
down:'Downlink'
|
||||
},
|
||||
upfFlowTotal:{
|
||||
title:'Traffic Information',
|
||||
title:'Traffic Summary',
|
||||
up:'Uplink',
|
||||
down:'Downlink'
|
||||
},
|
||||
@@ -514,14 +522,14 @@ export default {
|
||||
topTitle:"TOP 3",
|
||||
},
|
||||
resources: {
|
||||
title: "Resource Summary",
|
||||
title: "Resource Usage",
|
||||
sysMem: "SYS Mem",
|
||||
sysCpu: "SYS CPU",
|
||||
sysDisk: "SYS Store",
|
||||
neCpu: "NE CPU",
|
||||
},
|
||||
topology: {
|
||||
title: "Network Topology",
|
||||
title: "Network Status",
|
||||
normal: "Normal",
|
||||
abnormal: "Abnormal",
|
||||
},
|
||||
@@ -536,6 +544,93 @@ export default {
|
||||
resultOK: "Success",
|
||||
},
|
||||
},
|
||||
cdr: {
|
||||
recordType: "Recording Behavior",
|
||||
realTimeDataStart: "Turn on real-time data",
|
||||
realTimeDataStop: "Turn off real-time data",
|
||||
cdrInfo: "CDR Info",
|
||||
neName: "NE name",
|
||||
rmUID: "UID",
|
||||
time: "Time",
|
||||
rowInfo: "Info",
|
||||
type: "Type",
|
||||
duration: "Duration",
|
||||
caller: "Caller",
|
||||
called: "Called",
|
||||
result: "Result",
|
||||
delTip: "Confirm deletion of the data item numbered [{msg}]?",
|
||||
},
|
||||
ue: {
|
||||
eventType: "Event Type",
|
||||
realTimeDataStart: "Turn on real-time data",
|
||||
realTimeDataStop: "Turn off real-time data",
|
||||
ueInfo: "UE Info",
|
||||
neName: "NE name",
|
||||
rmUID: "UID",
|
||||
time: "Time",
|
||||
rowInfo: "Info",
|
||||
result: "Result",
|
||||
resultOk: "Successes",
|
||||
delTip: "Confirm deletion of the data item numbered [{msg}]?",
|
||||
},
|
||||
},
|
||||
ne: {
|
||||
neInfo: {
|
||||
version: "Version",
|
||||
serialNum: 'Serial Number',
|
||||
expiryDate: 'Expiry Date',
|
||||
state: "State",
|
||||
serviceState: "Service Status",
|
||||
normalcy: "Normal",
|
||||
exceptions: "Abnormal",
|
||||
info: 'Status Message',
|
||||
resourceInfo: 'Resource Situation',
|
||||
sysMem: "SYS Mem",
|
||||
sysCpu: "SYS CPU",
|
||||
sysDisk: "SYS Store",
|
||||
neCpu: "NE CPU",
|
||||
hostConfig: "Connection Configuration",
|
||||
rmUID: "Data resource location identifiers are used for data tagging such as logging, alarm reporting, etc.",
|
||||
ipAddr: "Support IPV4/IPV6, synchronize the change of the configuration address of the following configuration",
|
||||
},
|
||||
neHost: {
|
||||
hostType: "Type",
|
||||
groupId: "Group",
|
||||
title: "Host Name",
|
||||
titlePlease: "Please fill in the host name correctly",
|
||||
addr: "IP Addr",
|
||||
addrPlease: "Please fill in the host IP address correctly",
|
||||
port: "Port",
|
||||
portPlease: "Please fill in the host port number correctly",
|
||||
user: "Login User",
|
||||
userPlease: "Please fill in the host login user correctly",
|
||||
authMode: "Auth Mode",
|
||||
password: "Password",
|
||||
passwordPlease: "Please fill in the host login password correctly",
|
||||
privateKey: "Private Key",
|
||||
privateKeyPlease: "Please fill in the private key characters correctly ~/.ssh/id_rsa",
|
||||
passPhrase: "Private Key Cipher",
|
||||
remark: "Remark",
|
||||
createTime: "Time",
|
||||
delTip: "Confirm that you want to delete the host number [{num}]?",
|
||||
addTitle: "Add Host Connection",
|
||||
editTitle: "Edit Host Connection",
|
||||
test: "Test Connection To Host",
|
||||
testOk: "Test Connection Successful",
|
||||
},
|
||||
neHostCmd: {
|
||||
cmdType: "Type",
|
||||
groupId: "Group",
|
||||
title: "Title",
|
||||
titlePlease: "Please fill in the command name correctly",
|
||||
command: "Command",
|
||||
commandPlease: "Please enter a valid command string correctly",
|
||||
remark: "Remark",
|
||||
createTime: "Time",
|
||||
delTip: "Are you sure you want to delete the message with command number [{num}]?",
|
||||
addTitle: "New Host Commands",
|
||||
editTitle: "Edit Host Commands",
|
||||
},
|
||||
},
|
||||
neUser: {
|
||||
auth: {
|
||||
@@ -603,7 +698,8 @@ export default {
|
||||
epsOdbTip: 'ODB (Operator-Determined Barring) Operator-determined blocking, i.e. the ability of a subscriber to access the EPS network is determined by the operator.',
|
||||
hplmnOdbTip: 'HPLMN-ODB homing operator-determined blocking, i.e., the ability of a subscriber to access services in the EPS network is determined by the subscriber is homing operator',
|
||||
ardTip:'Access-Restriction-Data (Access-Restriction-Data), can be used to distinguish between 2G/3G/LTE users, to facilitate the coexistence of 2G/3G/LTE network for different types of users to distinguish between the service',
|
||||
smDataTip:'The IP in sm_data=1-000001&internet-1.2.3.4&ims-1.2.3.5: 1.2.3.4 is the static IP assigned to the APN of 5G user internet, and 1.2.3.5 is the static IP assigned to the APN of 5G user ims. If it is dynamic allocation, just remove the IP and the previous connector. Need to support multiple dnn uses & connections'
|
||||
smDataTip:'The IP in sm_data=1-000001&internet-1.2.3.4&ims-1.2.3.5: 1.2.3.4 is the static IP assigned to the APN of 5G user internet, and 1.2.3.5 is the static IP assigned to the APN of 5G user ims. If it is dynamic allocation, just remove the IP and the previous connector. Need to support multiple dnn uses & connections',
|
||||
smDataArrTip:'SST,DNN/APN is required',
|
||||
},
|
||||
pcf: {
|
||||
neType: 'PCF Object',
|
||||
@@ -720,6 +816,20 @@ export default {
|
||||
exportEmpty: "Export data is empty",
|
||||
showChartSelected: "Show All",
|
||||
realTimeData: "Real Time 5s Data",
|
||||
},
|
||||
customTarget:{
|
||||
kpiId:' Custom Indicator',
|
||||
period:' Granularity',
|
||||
title:' Custom Indicator Title',
|
||||
objectType:' Object type',
|
||||
expression:'Expression',
|
||||
description:' Description',
|
||||
kpiSet:' Statistical Settings',
|
||||
delCustomTip:'Confirm deletion of data item with record number {num}?',
|
||||
delCustom:' Successfully delete record number {num} custom indicator',
|
||||
addCustom:' Add custom indicator',
|
||||
editCustom:' Edit Custom indicator',
|
||||
errorCustomInfo: 'Failed to get information',
|
||||
}
|
||||
},
|
||||
traceManage: {
|
||||
@@ -875,7 +985,7 @@ export default {
|
||||
alarmId:'Alarm ID',
|
||||
alarmSeq:'Sequece Number',
|
||||
alarmCode:'Alarm Code',
|
||||
alarmStatus:'Severity',
|
||||
alarmStatus:'Status',
|
||||
eventTime:'Event Time',
|
||||
logTime:'Recording Time',
|
||||
status:'Status',
|
||||
@@ -1079,6 +1189,7 @@ export default {
|
||||
keyContent: "Cached content",
|
||||
},
|
||||
cacheInfo: {
|
||||
baseInfo: "Basic Info",
|
||||
version: "Service Versions",
|
||||
mode: "Perating Mode",
|
||||
modeStandalone: "stand-alone",
|
||||
@@ -1111,7 +1222,6 @@ export default {
|
||||
serialNum: 'Serial Number',
|
||||
expiryDate: 'Expiry Date',
|
||||
switchLayout: "Switch Layout",
|
||||
viewLogFile: "Viewing Log Files",
|
||||
noData: "Can't find the corresponding plot data",
|
||||
},
|
||||
topologyBuild: {
|
||||
@@ -1289,7 +1399,7 @@ export default {
|
||||
loginTime: 'Login Time',
|
||||
status: 'Status',
|
||||
userNameTip:'The account cannot start with a number and can contain uppercase and lowercase letters, numbers, and no less than 5 digits',
|
||||
pwdTip:'The password should contain at least uppercase and lowercase letters, numbers, special symbols, and no less than 6 digits',
|
||||
passwdTip:'The password should contain at least uppercase and lowercase letters, numbers, special symbols, and no less than 6 digits',
|
||||
nickNameTip:'Nicknames can only contain letters, numbers, Chinese characters, and underscores, with no less than 2 digits',
|
||||
emailTip:'Please enter the correct email address',
|
||||
phoneTip:'Please enter the correct phone number',
|
||||
@@ -1607,15 +1717,20 @@ export default {
|
||||
requireIpv6: "{display} Not a legitimate IPV6 address.",
|
||||
requireEnum: "{display} is not a reasonable enumeration value.",
|
||||
requireBool: "{display} is not a reasonable boolean value.",
|
||||
requireFile: "{display} is not a value that matches the parameter file type.",
|
||||
cmdQuickEntry: "Command Quick Entry",
|
||||
cmdQuickEntryHelp: "Line feed (Shift + Enter), Execute (Enter)",
|
||||
cmdParamPanel: "Parameter Panel",
|
||||
clearForm: "Reset",
|
||||
clearLog: "Clear Logs",
|
||||
exec: "Execute",
|
||||
cmdAwait: "Waiting for a command to be sent",
|
||||
uploadFileTip: 'Are you sure you want to upload the file?',
|
||||
uploadFileOk: 'File Upload Successful',
|
||||
uploadFileErr: 'File Upload Failed',
|
||||
neOperate:{
|
||||
mml: "General",
|
||||
mml2: "Standard",
|
||||
},
|
||||
omcOperate:{
|
||||
noOMC: "No OMC network elements",
|
||||
},
|
||||
@@ -1635,10 +1750,28 @@ export default {
|
||||
},
|
||||
tool: {
|
||||
help: {
|
||||
fileName: "5G Core Network Network Management Operations Manual.pdf",
|
||||
download: "Download",
|
||||
pdfViewer: "In-browser preview",
|
||||
pdfViewerErr: "Sorry, your browser does not support PDF preview!",
|
||||
}
|
||||
},
|
||||
terminal: {
|
||||
start: "Start Page",
|
||||
new: "To Create",
|
||||
more: "More",
|
||||
reload: "Reload Current Tab",
|
||||
reloadTip: "Are you sure you want to refresh and reconnect the current [{num}] terminal link?",
|
||||
current: "Close Current Tab",
|
||||
other: "Close Other Tabs",
|
||||
otherTip: "Confirm that you want to close other terminal links?",
|
||||
all: "Close All Tabs",
|
||||
allTip: "Confirmed to close all terminal links?",
|
||||
closeTip: "Confirm that you want to close the [{num}] terminal link?",
|
||||
hostSelectTitle: "Select the created host to connect to",
|
||||
hostSelectShow: "Open Selection",
|
||||
hostSelectMore: "Load More {num}",
|
||||
hostSelectHeader: "Host List",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -54,6 +54,8 @@ export default {
|
||||
},
|
||||
rowId: '编号',
|
||||
operate: '操作',
|
||||
operateOk: '操作成功!',
|
||||
operateErr: '操作失败!',
|
||||
units: {
|
||||
second: '秒',
|
||||
minute: '分钟',
|
||||
@@ -362,6 +364,7 @@ export default {
|
||||
totalSure:'确认{oper}网元名称为 {msg} 的网元',
|
||||
stop: '停止',
|
||||
start: '启动',
|
||||
log: '日志',
|
||||
export: '导出',
|
||||
import: '导入',
|
||||
fileForm:'文件来源',
|
||||
@@ -492,12 +495,17 @@ export default {
|
||||
overview:{
|
||||
title: "核心网系统看板",
|
||||
fullscreen: "点击全屏显示",
|
||||
toRouter: "点击跳转详情页面",
|
||||
skim: {
|
||||
users: "用户数",
|
||||
userTitle:'用户信息',
|
||||
session: "会话数",
|
||||
base: "基站数",
|
||||
baseTitle:'基站信息',
|
||||
imsUeNum: "IMS 会话数",
|
||||
smfUeNum: "Data 会话数",
|
||||
gnbBase: "5G 基站数",
|
||||
gnbUeNum:'5G 用户数',
|
||||
enbBase: "4G 基站数",
|
||||
enbUeNum:'4G 用户数',
|
||||
baseTitle:'在线信息',
|
||||
},
|
||||
upfFlow:{
|
||||
title: "用户面吞吐量",
|
||||
@@ -535,7 +543,94 @@ export default {
|
||||
time: "时间",
|
||||
resultOK: "成功",
|
||||
},
|
||||
}
|
||||
},
|
||||
cdr: {
|
||||
recordType: "记录行为",
|
||||
realTimeDataStart: "开启实时数据",
|
||||
realTimeDataStop: "关闭实时数据",
|
||||
cdrInfo: "CDR信息",
|
||||
neName: "网元名称",
|
||||
rmUID: "资源标识",
|
||||
time: "记录时间",
|
||||
rowInfo: "记录信息",
|
||||
type: "记录类型",
|
||||
duration: "通话时长",
|
||||
caller: "主叫",
|
||||
called: "被叫",
|
||||
result: "结果",
|
||||
delTip: "确认删除编号为【{msg}】的数据项?",
|
||||
},
|
||||
ue: {
|
||||
eventType: "事件类型",
|
||||
realTimeDataStart: "开启实时数据",
|
||||
realTimeDataStop: "关闭实时数据",
|
||||
ueInfo: "UE信息",
|
||||
neName: "网元名称",
|
||||
rmUID: "资源标识",
|
||||
rowInfo: "记录信息",
|
||||
time: "记录时间",
|
||||
result: "结果",
|
||||
resultOk: "成功",
|
||||
delTip: "确认删除编号为【{msg}】的数据项?",
|
||||
},
|
||||
},
|
||||
ne: {
|
||||
neInfo: {
|
||||
version: "网元版本",
|
||||
serialNum: '序列号',
|
||||
expiryDate: '许可证到期日期',
|
||||
state: "网元状态",
|
||||
serviceState: "服务状态",
|
||||
normalcy: "正常",
|
||||
exceptions: "异常",
|
||||
info: '状态信息',
|
||||
resourceInfo: '资源情况',
|
||||
sysMem: "系统内存",
|
||||
sysCpu: "系统CPU",
|
||||
sysDisk: "系统存储",
|
||||
neCpu: "网元CPU",
|
||||
hostConfig: "终端连接配置",
|
||||
rmUID: "数据资源定位标识符用于日志、告警上报等数据标记",
|
||||
ipAddr: "支持IPV4/IPV6,同步变更下方配置的配置地址",
|
||||
},
|
||||
neHost: {
|
||||
hostType: "主机类型",
|
||||
groupId: "分组",
|
||||
title: "主机名称",
|
||||
titlePlease: "请正确填写主机名称",
|
||||
addr: "IP地址",
|
||||
addrPlease: "请正确填写主机IP地址",
|
||||
port: "端口",
|
||||
portPlease: "请正确填写主机端口号",
|
||||
user: "用户名",
|
||||
userPlease: "请正确填写主机登录用户",
|
||||
authMode: "认证模式",
|
||||
password: "密码",
|
||||
passwordPlease: "请正确填写主机登录密码",
|
||||
privateKey: "私钥",
|
||||
privateKeyPlease: "请正确填写私钥字符内容 ~/.ssh/id_rsa",
|
||||
passPhrase: "私钥密码",
|
||||
remark: "备注信息",
|
||||
createTime: "创建时间",
|
||||
delTip: "确认要删除主机编号为【{num}】的信息吗?",
|
||||
addTitle: "新增主机连接",
|
||||
editTitle: "编辑主机连接",
|
||||
test: "测试连接",
|
||||
testOk: "测试连接成功",
|
||||
},
|
||||
neHostCmd: {
|
||||
cmdType: "命令类型",
|
||||
groupId: "分组",
|
||||
title: "名称",
|
||||
titlePlease: "请正确填写命令名称",
|
||||
command: "命令",
|
||||
commandPlease: "请正确输入有效命令字符串",
|
||||
remark: "备注",
|
||||
createTime: "创建时间",
|
||||
delTip: "确认要删除命令编号为【{num}】的信息吗?",
|
||||
addTitle: "新增主机命令",
|
||||
editTitle: "编辑主机命令",
|
||||
},
|
||||
},
|
||||
neUser: {
|
||||
auth: {
|
||||
@@ -603,7 +698,8 @@ export default {
|
||||
epsOdbTip: 'ODB(Operator-Determined Barring)运营商决定的闭锁,即用户接入EPS网络的业务能力由运营商决定.选中 ---对应服务被允许 未选 --- 对应服务被禁止',
|
||||
hplmnOdbTip: 'HPLMN-ODB归属运营商决定的闭锁,即用户接入EPS网络的业务能力由用户归宿运营商决定.选中 --- 对应服务被允许 未选 -- 对应服务被禁止',
|
||||
ardTip:'接入控制标志(Access-Restriction-Data),可用于区分2G/3G/LTE用户,便于为2G/3G/LTE网络共存时,对不同类型用户进行区分服务',
|
||||
smDataTip:'sm_data=1-000001&internet-1.2.3.4&ims-1.2.3.5中的IP:1.2.3.4为5G用户internet这个APN分配的静态IP,1.2.3.5为5G用户ims这个APN分配的静态IP。如果是动态分配,把IP以及前面一个连接符去掉即可。需支持多个dnn用&连接'
|
||||
smDataTip:'sm_data=1-000001&internet-1.2.3.4&ims-1.2.3.5中的IP:1.2.3.4为5G用户internet这个APN分配的静态IP,1.2.3.5为5G用户ims这个APN分配的静态IP。如果是动态分配,把IP以及前面一个连接符去掉即可。需支持多个dnn用&连接',
|
||||
smDataArrTip:'SST,DNN/APN为必填项',
|
||||
},
|
||||
pcf: {
|
||||
neType: 'PCF网元对象',
|
||||
@@ -720,7 +816,21 @@ export default {
|
||||
exportEmpty: "导出数据为空",
|
||||
showChartSelected: "显示全部",
|
||||
realTimeData: "实时5s数据",
|
||||
}
|
||||
},
|
||||
customTarget:{
|
||||
kpiId:'自定义指标项',
|
||||
period:'颗粒度',
|
||||
title:'自定义指标项标题',
|
||||
objectType:'对象类型',
|
||||
expression:'计算公式',
|
||||
description:'描述',
|
||||
kpiSet:'统计设置',
|
||||
delCustomTip:'确认删除记录编号为 {num} 的数据项?',
|
||||
delCustom:'成功删除记录编号为 {num} 自定义指标',
|
||||
addCustom:'添加自定义指标',
|
||||
editCustom:'编辑自定义指标',
|
||||
errorCustomInfo: '获取信息失败',
|
||||
}
|
||||
},
|
||||
traceManage: {
|
||||
analysis: {
|
||||
@@ -875,7 +985,7 @@ export default {
|
||||
alarmId:'告警唯一标识',
|
||||
alarmSeq:'告警流水号',
|
||||
alarmCode:'告警编号',
|
||||
alarmStatus:'原始告警级别',
|
||||
alarmStatus:'告警状态',
|
||||
eventTime:'告警产生时间',
|
||||
logTime:'记录时间',
|
||||
status:'告警状态'
|
||||
@@ -1079,6 +1189,7 @@ export default {
|
||||
keyContent: "缓存内容",
|
||||
},
|
||||
cacheInfo: {
|
||||
baseInfo: "基本信息",
|
||||
version: "服务版本",
|
||||
mode: "运行模式",
|
||||
modeStandalone: "单机",
|
||||
@@ -1111,7 +1222,6 @@ export default {
|
||||
serialNum: '序列号',
|
||||
expiryDate: '许可证到期日期',
|
||||
switchLayout: "切换布局",
|
||||
viewLogFile: "查看日志文件",
|
||||
noData: "找不到对应的图组数据",
|
||||
},
|
||||
topologyBuild: {
|
||||
@@ -1289,7 +1399,7 @@ export default {
|
||||
loginTime: '登录时间',
|
||||
status: '用户状态',
|
||||
userNameTip:'账号不能以数字开头,可包含大写小写字母,数字,且不少于5位',
|
||||
pwdTip:'密码至少包含大小写字母、数字、特殊符号,且不少于6位',
|
||||
passwdTip:'密码至少包含大小写字母、数字、特殊符号,且不少于6位',
|
||||
nickNameTip:'昵称只能包含字母、数字、中文和下划线,且不少于2位',
|
||||
emailTip:'请输入正确的邮箱地址',
|
||||
phoneTip:'请输入正确的手机号码',
|
||||
@@ -1607,15 +1717,20 @@ export default {
|
||||
requireIpv6: "{display} 不是合法的IPV6地址",
|
||||
requireEnum: "{display} 不是合理的枚举值",
|
||||
requireBool: "{display} 不是合理的布尔类型的值",
|
||||
requireFile: "{display} 不是符合参数文件类型的值",
|
||||
cmdQuickEntry: "命令快速输入",
|
||||
cmdQuickEntryHelp: "换行(Shift + Enter) 执行发送(Enter)",
|
||||
cmdParamPanel: "参数面板",
|
||||
clearForm: "重置",
|
||||
clearLog: "清除日志",
|
||||
exec: "执行",
|
||||
cmdAwait: "等待发送命令",
|
||||
uploadFileTip: '确认要上传文件吗?',
|
||||
uploadFileOk: '文件上传成功',
|
||||
uploadFileErr: '文件上传失败',
|
||||
neOperate:{
|
||||
mml: "通用",
|
||||
mml2: "标准版",
|
||||
},
|
||||
omcOperate:{
|
||||
noOMC: "暂无OMC网元",
|
||||
},
|
||||
@@ -1635,10 +1750,28 @@ export default {
|
||||
},
|
||||
tool: {
|
||||
help: {
|
||||
fileName: "5G核心网网管操作手册.pdf",
|
||||
download: "下载",
|
||||
pdfViewer: "浏览器内预览",
|
||||
pdfViewerErr: "很抱歉,您的浏览器不支持 PDF 预览!",
|
||||
}
|
||||
},
|
||||
terminal: {
|
||||
start: "开始页",
|
||||
new: "去创建",
|
||||
more: "更多",
|
||||
reload: "断开重连",
|
||||
reloadTip: "确认要刷新重连当前 【{num}】 终端链接?",
|
||||
current: "关闭当前",
|
||||
other: "关闭其他",
|
||||
otherTip: "确认要关闭其他终端链接?",
|
||||
all: "关闭全部",
|
||||
allTip: "确认要关闭全部终端链接?",
|
||||
closeTip: "确认要关闭 【{num}】 终端链接?",
|
||||
hostSelectTitle: "选择已创建的主机进行连接",
|
||||
hostSelectShow: "打开选择",
|
||||
hostSelectMore: "加载更多 {num}",
|
||||
hostSelectHeader: "主机列表",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import RightContent from './components/RightContent.vue';
|
||||
import Tabs from './components/Tabs.vue';
|
||||
import { scriptUrl } from '@/assets/js/icon_font_8d5l8fzk5b87iudi';
|
||||
import { computed, reactive, watch } from 'vue';
|
||||
import { computed, reactive, watch, onMounted, onUnmounted } from 'vue';
|
||||
import useLayoutStore from '@/store/modules/layout';
|
||||
import useRouterStore from '@/store/modules/router';
|
||||
import useTabsStore from '@/store/modules/tabs';
|
||||
@@ -21,7 +21,6 @@ const { proConfig, waterMarkContent } = useLayoutStore();
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { getServerTime } from '@/api';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { onMounted } from 'vue';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { parseUrlPath } from '@/plugins/file-static-url';
|
||||
const { t, currentLocale } = useI18n();
|
||||
@@ -170,7 +169,7 @@ onMounted(() => {
|
||||
let serverTime = reactive({
|
||||
timestamp: 0,
|
||||
zone: 'UTC', // 时区 UTC
|
||||
interval: 0 as any, // 定时器
|
||||
interval: null as any, // 定时器
|
||||
});
|
||||
|
||||
// 获取服务器时间
|
||||
@@ -178,6 +177,9 @@ function fnGetServerTime() {
|
||||
getServerTime().then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
const serverTimeDom = document.getElementById('serverTimeDom');
|
||||
// 时区
|
||||
const utcOffset = res.data.timeZone / 3600;
|
||||
serverTime.zone = `UTC ${utcOffset}`;
|
||||
// 时间戳
|
||||
serverTime.timestamp = parseInt(res.data.timestamp);
|
||||
serverTime.interval = setInterval(() => {
|
||||
@@ -188,10 +190,6 @@ function fnGetServerTime() {
|
||||
serverTimeDom.innerText = parseDateToStr(serverTime.timestamp);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// 时区
|
||||
const offsetHours = res.data.timeZone / 3600;
|
||||
serverTime.zone = `UTC ${offsetHours}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -201,14 +199,21 @@ document.addEventListener('visibilitychange', function () {
|
||||
if (document.visibilityState == 'hidden') {
|
||||
//切离该页面时执行
|
||||
clearInterval(serverTime.interval);
|
||||
serverTime.interval = null;
|
||||
}
|
||||
if (document.visibilityState == 'visible') {
|
||||
//切换到该页面时执行
|
||||
clearInterval(serverTime.interval);
|
||||
serverTime.interval = null;
|
||||
fnGetServerTime();
|
||||
useAlarmStore().fnGetActiveAlarmInfo();
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(serverTime.interval);
|
||||
serverTime.interval = null;
|
||||
});
|
||||
// ==== 服务器时间显示 end
|
||||
</script>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ export type OptionsType = {
|
||||
/**地址栏参数 */
|
||||
params?: Record<string, string | number | boolean | undefined>;
|
||||
/**onopen事件的回调函数 */
|
||||
onopen?: () => void;
|
||||
onopen?: Function;
|
||||
/**message事件的回调函数 */
|
||||
onmessage: (data: Record<string, any>) => void;
|
||||
/**error事件的回调函数 */
|
||||
@@ -114,7 +114,7 @@ export class WS {
|
||||
this.heartCheck(options.heartTimer);
|
||||
}
|
||||
if (typeof options.onopen === 'function') {
|
||||
options.onopen();
|
||||
options.onopen(ev);
|
||||
}
|
||||
};
|
||||
// 用于指定当从服务器接受到信息时的回调函数。
|
||||
@@ -160,23 +160,49 @@ export class WS {
|
||||
}
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
public send(data: Record<string, any>) {
|
||||
/**
|
||||
* 发送消息
|
||||
* @param data JSON数据
|
||||
* @returns
|
||||
*/
|
||||
public send(data: Record<string, any>): boolean {
|
||||
if (!this.ws) {
|
||||
console.warn('websocket unavailable');
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// 非正常状态关闭
|
||||
if (
|
||||
this.ws.readyState === WebSocket.CLOSED ||
|
||||
this.ws.readyState === WebSocket.CLOSING
|
||||
) {
|
||||
this.close();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
// 正在连接时
|
||||
if (this.ws.readyState === WebSocket.CONNECTING) {
|
||||
setTimeout(() => {
|
||||
this.ws && this.ws.send(JSON.stringify(data));
|
||||
}, 1000);
|
||||
}
|
||||
// 在连接的
|
||||
if (this.ws.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(JSON.stringify(data));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
this.ws.send(JSON.stringify(data));
|
||||
/**连接状态
|
||||
*
|
||||
* WebSocket.OPEN
|
||||
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/readyState)
|
||||
*/
|
||||
public state(): number {
|
||||
if (!this.ws) {
|
||||
return -1;
|
||||
}
|
||||
return this.ws.readyState;
|
||||
}
|
||||
|
||||
// 手动关闭socket
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { getNelistAll } from '@/api/configManage/neManage';
|
||||
import { listAllNeInfo } from '@/api/ne/neInfo';
|
||||
import { parseDataToOptions } from '@/utils/parse-tree-utils';
|
||||
import { getNeTraceInterfaceAll } from '@/api/traceManage/task';
|
||||
import { getNePerformanceList } from '@/api/perfManage/taskManage';
|
||||
@@ -58,10 +58,12 @@ const useNeInfoStore = defineStore('neinfo', {
|
||||
if (this.neList.length > 0) {
|
||||
return { code: 1, data: this.neList, msg: 'success' };
|
||||
}
|
||||
const res = await getNelistAll();
|
||||
const res = await listAllNeInfo({
|
||||
bandStatus: false,
|
||||
});
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
// 原始列表
|
||||
this.neList = res.data;
|
||||
this.neList = JSON.parse(JSON.stringify(res.data));
|
||||
|
||||
// 转级联数据
|
||||
const options = parseDataToOptions(
|
||||
|
||||
70
src/utils/cache-db-utils.ts
Normal file
70
src/utils/cache-db-utils.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
// 文档 https://localforage.docschina.org/
|
||||
import localforage from 'localforage';
|
||||
|
||||
/**数据级缓存设置 */
|
||||
export async function dbSet(storeName: string, key: string, value: any) {
|
||||
if (!storeName || !key || !value) {
|
||||
return false;
|
||||
}
|
||||
localforage.config({
|
||||
name: import.meta.env.VITE_APP_CODE,
|
||||
storeName: storeName,
|
||||
});
|
||||
try {
|
||||
await localforage.setItem(key, value);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**数据级缓存获取 */
|
||||
export async function dbGet(storeName: string, key: string) {
|
||||
if (!storeName || !key) {
|
||||
return null;
|
||||
}
|
||||
localforage.config({
|
||||
name: import.meta.env.VITE_APP_CODE,
|
||||
storeName: storeName,
|
||||
});
|
||||
let value: any = null;
|
||||
try {
|
||||
value = await localforage.getItem(key);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**数据级缓存移除 */
|
||||
export async function dbRemove(storeName: string, key: string) {
|
||||
if (!storeName || !key) {
|
||||
return false;
|
||||
}
|
||||
localforage.config({
|
||||
name: import.meta.env.VITE_APP_CODE,
|
||||
storeName: storeName,
|
||||
});
|
||||
try {
|
||||
await localforage.removeItem(key);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**数据级缓存设置JSON */
|
||||
export function dbSetJSON(storeName: string, key: string, jsonValue: object) {
|
||||
return dbSet(storeName, key, JSON.stringify(jsonValue));
|
||||
}
|
||||
|
||||
/**数据级缓存获取JSON */
|
||||
export async function dbGetJSON(storeName: string, key: string) {
|
||||
const value = await dbGet(storeName, key);
|
||||
if (typeof value === 'string') {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -83,7 +83,7 @@ export function parseDuration(seconds: number | string) {
|
||||
const duration = new Date(seconds * 1000);
|
||||
const hours = duration.getUTCHours();
|
||||
const minutes = duration.getUTCMinutes();
|
||||
const secondsLeft = duration.getUTCSeconds();
|
||||
const secondsLeft = duration.getUTCSeconds();
|
||||
if (+hours > 0) {
|
||||
return `${hours}h${minutes}m${secondsLeft}s`;
|
||||
}
|
||||
|
||||
@@ -122,9 +122,11 @@ export function parseSizeFromKBs(size: number): string {
|
||||
/**
|
||||
* 字节数转换速率
|
||||
* @param sizeByte 数值大小
|
||||
* @returns
|
||||
* @returns [值,单位]
|
||||
*/
|
||||
export function parseSizeFromKbs(sizeByte: number, timeInterval: number): any {
|
||||
sizeByte = Number(sizeByte) || 0;
|
||||
timeInterval = Number(timeInterval) || 1;
|
||||
// let realBit:any=((sizeByte * 8) / timeInterval);
|
||||
// if (realBit >= 0 && realBit < 1000) {
|
||||
// return [realBit.toFixed(2),' bits/sec'];
|
||||
|
||||
@@ -1170,7 +1170,7 @@ onMounted(() => {
|
||||
return;
|
||||
}
|
||||
// 默认选择AMF
|
||||
const item = neCascaderOptions.value.find(s => s.value === 'UPF');
|
||||
const item = neCascaderOptions.value.find(s => s.value === 'AMF');
|
||||
if (item && item.children) {
|
||||
const info = item.children[0];
|
||||
neTypeSelect.value = [info.neType, info.neId];
|
||||
@@ -1387,6 +1387,7 @@ onMounted(() => {
|
||||
</a-button>
|
||||
<TableColumnsDnd
|
||||
type="ghost"
|
||||
:cache-id="treeState.selectNode.key"
|
||||
:columns="treeState.selectNode.method === 'get' ? [...arrayState.columns.filter((s:any)=>s.key !== 'index')] : arrayState.columns"
|
||||
v-model:columns-dnd="arrayState.columnsDnd"
|
||||
></TableColumnsDnd>
|
||||
@@ -1488,6 +1489,7 @@ onMounted(() => {
|
||||
</a-button>
|
||||
<TableColumnsDnd
|
||||
type="ghost"
|
||||
:cache-id="`${treeState.selectNode.key}:${arrayChildState.loc}`"
|
||||
:columns="[...arrayChildState.columns]"
|
||||
v-model:columns-dnd="arrayChildState.columnsDnd"
|
||||
v-if="arrayChildState.loc"
|
||||
|
||||
@@ -209,16 +209,17 @@ let modalState: ModalStateType = reactive({
|
||||
visibleByImport: false,
|
||||
title: '网元',
|
||||
from: {
|
||||
id: undefined,
|
||||
dn: '',
|
||||
ip: '',
|
||||
neAddress: '',
|
||||
neId: '',
|
||||
neName: '',
|
||||
neType: '',
|
||||
port: '',
|
||||
neType: 'OMC',
|
||||
port: '3030',
|
||||
province: '',
|
||||
pvFlag: '',
|
||||
rmUid: '',
|
||||
pvFlag: 'PNF',
|
||||
rmUid: '4400HX1OMC001',
|
||||
vendorName: '',
|
||||
sync: true,
|
||||
},
|
||||
|
||||
668
src/views/dashboard/amfUE/index.vue
Normal file
668
src/views/dashboard/amfUE/index.vue
Normal file
@@ -0,0 +1,668 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw, ref, onUnmounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { message, Modal } from 'ant-design-vue/lib';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import {
|
||||
RESULT_CODE_ERROR,
|
||||
RESULT_CODE_SUCCESS,
|
||||
} from '@/constants/result-constants';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import { listAMFDataUE, delAMFDataUE } from '@/api/neData/amf';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import PQueue from 'p-queue';
|
||||
const { t } = useI18n();
|
||||
const { getDict } = useDictStore();
|
||||
const ws = new WS();
|
||||
const queue = new PQueue({ concurrency: 1, autoStart: true });
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**UE 事件认证代码类型 */
|
||||
ueAauthCode: DictType[];
|
||||
/**UE 事件类型 */
|
||||
ueEventType: DictType[];
|
||||
/**UE 事件CM状态 */
|
||||
ueEventCmState: DictType[];
|
||||
} = reactive({
|
||||
ueAauthCode: [],
|
||||
ueEventType: [],
|
||||
ueEventCmState: [],
|
||||
});
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**网元类型 */
|
||||
neType: 'AMF',
|
||||
neId: '001',
|
||||
eventType: 'auth-result',
|
||||
sortField: 'timestamp',
|
||||
sortOrder: 'desc',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
eventTypes.value = ['auth-result'];
|
||||
queryParams = Object.assign(queryParams, {
|
||||
eventType: 'auth-result',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**记录类型 */
|
||||
const eventTypes = ref<string[]>(['auth-result']);
|
||||
|
||||
/**查询记录类型变更 */
|
||||
function fnQueryEventTypeChange(value: any) {
|
||||
if (Array.isArray(value)) {
|
||||
queryParams.eventType = value.join(',');
|
||||
}
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**斑马纹 */
|
||||
striped: boolean;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
striped: false,
|
||||
seached: true,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'id',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'IMSI',
|
||||
dataIndex: 'eventJSON',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
customRender(opt) {
|
||||
const eventJSON = opt.value;
|
||||
return eventJSON.imsi;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.ue.eventType'),
|
||||
dataIndex: 'eventType',
|
||||
key: 'eventType',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.ue.result'),
|
||||
dataIndex: 'eventJSON',
|
||||
key: 'result',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.ue.time'),
|
||||
dataIndex: 'eventJSON',
|
||||
key: 'time',
|
||||
align: 'center',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格斑马纹 */
|
||||
function fnTableStriped(_record: unknown, index: number): any {
|
||||
return tableState.striped && index % 2 === 1 ? 'table-striped' : undefined;
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
/**最大ID值 */
|
||||
maxId: number;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
confirmLoading: false,
|
||||
maxId: 0,
|
||||
});
|
||||
|
||||
/**
|
||||
* 记录删除
|
||||
* @param id 编号
|
||||
*/
|
||||
function fnRecordDelete(id: string) {
|
||||
if (!id || modalState.confirmLoading) return;
|
||||
let msg = id;
|
||||
if (id === '0') {
|
||||
msg = `${id}... ${tableState.selectedRowKeys.length}`;
|
||||
id = tableState.selectedRowKeys.join(',');
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.dashboard.ue.delTip', { msg }),
|
||||
onOk() {
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
delAMFDataUE(id)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.operateOk'),
|
||||
duration: 3,
|
||||
});
|
||||
fnGetList(1);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**查询列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
listAMFDataUE(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
tablePagination.total = res.total;
|
||||
// 遍历处理cdr字符串数据
|
||||
tableState.data = res.rows.map(item => {
|
||||
let eventJSON = item.eventJSON;
|
||||
if (!eventJSON) {
|
||||
Reflect.set(item, 'eventJSON', {});
|
||||
}
|
||||
|
||||
try {
|
||||
eventJSON = JSON.parse(eventJSON);
|
||||
Reflect.set(item, 'eventJSON', eventJSON);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Reflect.set(item, 'eventJSON', {});
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
// 取最大值ID用作实时累加
|
||||
if (res.total > 0) {
|
||||
modalState.maxId = Number(res.rows[0].id);
|
||||
}
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**实时数据开关 */
|
||||
const realTimeData = ref<boolean>(false);
|
||||
|
||||
/**
|
||||
* 实时数据
|
||||
*/
|
||||
function fnRealTime() {
|
||||
realTimeData.value = !realTimeData.value;
|
||||
if (realTimeData.value) {
|
||||
// 建立链接
|
||||
const options: OptionsType = {
|
||||
url: '/ws',
|
||||
params: {
|
||||
/**订阅通道组
|
||||
*
|
||||
* UE会话事件-AMF (GroupID:1010)
|
||||
*/
|
||||
subGroupID: '1010',
|
||||
},
|
||||
onmessage: wsMessage,
|
||||
onerror: wsError,
|
||||
};
|
||||
ws.connect(options);
|
||||
} else {
|
||||
ws.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**接收数据后回调 */
|
||||
function wsError(ev: any) {
|
||||
// 接收数据后回调
|
||||
console.error(ev);
|
||||
}
|
||||
|
||||
/**接收数据后回调 */
|
||||
function wsMessage(res: Record<string, any>) {
|
||||
const { code, requestId, data } = res;
|
||||
if (code === RESULT_CODE_ERROR) {
|
||||
console.warn(res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// 订阅组信息
|
||||
if (!data?.groupId) {
|
||||
return;
|
||||
}
|
||||
// ueEvent CDR会话事件
|
||||
if (data.groupId === '1010') {
|
||||
const ueEvent = data.data;
|
||||
queue.add(async () => {
|
||||
modalState.maxId += 1;
|
||||
tableState.data.unshift({
|
||||
id: modalState.maxId,
|
||||
neType: ueEvent.neType,
|
||||
neName: ueEvent.neName, // 空
|
||||
rmUID: ueEvent.rmUID, // 空
|
||||
timestamp: ueEvent.timestamp,
|
||||
eventType: ueEvent.eventType,
|
||||
eventJSON: ueEvent.eventJSON,
|
||||
});
|
||||
tablePagination.total += 1;
|
||||
if (tableState.data.length > 100) {
|
||||
tableState.data.pop();
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 800));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([
|
||||
getDict('ue_auth_code'),
|
||||
getDict('ue_event_type'),
|
||||
getDict('ue_event_cm_state'),
|
||||
])
|
||||
.then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.ueAauthCode = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.ueEventType = resArr[1].value;
|
||||
}
|
||||
if (resArr[2].status === 'fulfilled') {
|
||||
dict.ueEventCmState = resArr[2].value;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
ws.close();
|
||||
});
|
||||
</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="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.dashboard.ue.eventType')"
|
||||
name="eventType "
|
||||
>
|
||||
<a-select
|
||||
v-model:value="eventTypes"
|
||||
mode="tags"
|
||||
:options="dict.ueEventType"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
@change="fnQueryEventTypeChange"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
{{ t('common.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-popconfirm
|
||||
placement="bottomLeft"
|
||||
:title="
|
||||
!realTimeData
|
||||
? t('views.dashboard.ue.realTimeDataStart')
|
||||
: t('views.dashboard.ue.realTimeDataStop')
|
||||
"
|
||||
ok-text="Yes"
|
||||
cancel-text="No"
|
||||
@confirm="fnRealTime()"
|
||||
>
|
||||
<a-button type="primary" :danger="realTimeData">
|
||||
<template #icon><FundOutlined /> </template>
|
||||
{{
|
||||
!realTimeData
|
||||
? t('views.dashboard.ue.realTimeDataStart')
|
||||
: t('views.dashboard.ue.realTimeDataStop')
|
||||
}}
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
:loading="modalState.confirmLoading"
|
||||
@click.prevent="fnRecordDelete('0')"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
{{ t('common.deleteText') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<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.tableStripedText') }}</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.striped"
|
||||
:checked-children="t('common.switch.open')"
|
||||
:un-checked-children="t('common.switch.shut')"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.sizeText') }}</template>
|
||||
<a-dropdown trigger="click" placement="bottomRight">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">
|
||||
{{ t('common.size.default') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="middle">
|
||||
{{ t('common.size.middle') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="small">
|
||||
{{ t('common.size.small') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:row-class-name="fnTableStriped"
|
||||
:scroll="{ x: tableColumns.length * 120, y: 'calc(100vh - 480px)' }"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
columnWidth: '48px',
|
||||
selectedRowKeys: tableState.selectedRowKeys,
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'eventType'">
|
||||
<DictTag :options="dict.ueEventType" :value="record.eventType" />
|
||||
</template>
|
||||
<template v-if="column.key === 'result'">
|
||||
<span v-if="record.eventType === 'auth-result'">
|
||||
<DictTag
|
||||
:options="dict.ueAauthCode"
|
||||
:value="record.eventJSON.authCode"
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
v-if="record.eventType === 'detach'"
|
||||
:title="record.eventJSON.detachTime"
|
||||
>
|
||||
<span>{{ t('views.dashboard.ue.resultOk') }}</span>
|
||||
</span>
|
||||
<span v-if="record.eventType === 'cm-state'">
|
||||
<DictTag
|
||||
:options="dict.ueEventCmState"
|
||||
:value="record.eventJSON.status"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'time'">
|
||||
<span
|
||||
v-if="record.eventType === 'auth-result'"
|
||||
:title="record.eventJSON.authTime"
|
||||
>
|
||||
{{ record.eventJSON.authTime }}
|
||||
</span>
|
||||
<span
|
||||
v-if="record.eventType === 'detach'"
|
||||
:title="record.eventJSON.detachTime"
|
||||
>
|
||||
{{ record.eventJSON.detachTime }}
|
||||
</span>
|
||||
<span
|
||||
v-if="record.eventType === 'cm-state'"
|
||||
:title="record.eventJSON.changeTime"
|
||||
>
|
||||
{{ record.eventJSON.changeTime }}
|
||||
</span>
|
||||
</template>
|
||||
<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.id)"
|
||||
>
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
<template #expandedRowRender="{ record }">
|
||||
<div style="width: 46%; padding-left: 32px; padding-bottom: 16px">
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.dashboard.ue.ueInfo') }}
|
||||
</a-divider>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.ue.neName') }}: </span>
|
||||
<span>{{ record.neName }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.ue.rmUID') }}: </span>
|
||||
<span>{{ record.rmUID }}</span>
|
||||
</div>
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.dashboard.ue.rowInfo') }}
|
||||
</a-divider>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.ue.time') }}: </span>
|
||||
<span
|
||||
v-if="record.eventType === 'auth-result'"
|
||||
:title="record.eventJSON.authTime"
|
||||
>
|
||||
{{ record.eventJSON.authTime }}
|
||||
</span>
|
||||
<span
|
||||
v-if="record.eventType === 'detach'"
|
||||
:title="record.eventJSON.detachTime"
|
||||
>
|
||||
{{ record.eventJSON.detachTime }}
|
||||
</span>
|
||||
<span
|
||||
v-if="record.eventType === 'cm-state'"
|
||||
:title="record.eventJSON.changeTime"
|
||||
>
|
||||
{{ record.eventJSON.changeTime }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.ue.eventType') }}: </span>
|
||||
<DictTag :options="dict.ueEventType" :value="record.eventType" />
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.ue.result') }}: </span>
|
||||
<span v-if="record.eventType === 'auth-result'">
|
||||
<DictTag
|
||||
:options="dict.ueAauthCode"
|
||||
:value="record.eventJSON.authCode"
|
||||
/>
|
||||
</span>
|
||||
<span v-if="record.eventType === 'detach'">
|
||||
{{ t('views.dashboard.ue.resultOK') }}
|
||||
</span>
|
||||
<span v-if="record.eventType === 'cm-state'">
|
||||
<DictTag
|
||||
:options="dict.ueEventCmState"
|
||||
:value="record.eventJSON.status"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.table-striped) td {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,53 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
import {
|
||||
listCacheName,
|
||||
listCacheKey,
|
||||
getCacheValue,
|
||||
clearCacheName,
|
||||
clearCacheKey,
|
||||
clearCacheSafe,
|
||||
} from '@/api/monitor/cache';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table/Table';
|
||||
import { message } from 'ant-design-vue/lib';
|
||||
import { hasPermissions } from '@/plugins/auth-user';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { WS, OptionsType } from '@/plugins/ws-websocket';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
const { t } = useI18n();
|
||||
|
||||
const ws = new WS();
|
||||
|
||||
onMounted(() => {
|
||||
const options: OptionsType = {
|
||||
url: '/ws',
|
||||
params: {
|
||||
subGroupID: '1005',
|
||||
},
|
||||
onmessage: ev => {
|
||||
// 接收数据后回调
|
||||
console.log(ev);
|
||||
},
|
||||
onerror: (ev: any) => {
|
||||
// 接收数据后回调
|
||||
console.log(ev);
|
||||
},
|
||||
};
|
||||
ws.connect(options);
|
||||
|
||||
setInterval(() => {
|
||||
ws.send({ a: 1 });
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
ws.close();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
669
src/views/dashboard/imsCDR/index.vue
Normal file
669
src/views/dashboard/imsCDR/index.vue
Normal file
@@ -0,0 +1,669 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw, ref, onUnmounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { message, Modal } from 'ant-design-vue/lib';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import {
|
||||
RESULT_CODE_ERROR,
|
||||
RESULT_CODE_SUCCESS,
|
||||
} from '@/constants/result-constants';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import { delIMSDataCDR, listIMSDataCDR } from '@/api/neData/ims';
|
||||
import { parseDateToStr, parseDuration } from '@/utils/date-utils';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import PQueue from 'p-queue';
|
||||
const { t } = useI18n();
|
||||
const { getDict } = useDictStore();
|
||||
const ws = new WS();
|
||||
const queue = new PQueue({ concurrency: 1, autoStart: true });
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**CDR SIP响应代码类别类型 */
|
||||
cdrSipCode: DictType[];
|
||||
/**CDR 呼叫类型 */
|
||||
cdrCallType: DictType[];
|
||||
} = reactive({
|
||||
cdrSipCode: [],
|
||||
cdrCallType: [],
|
||||
});
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**网元类型 */
|
||||
neType: 'IMS',
|
||||
neId: '001',
|
||||
recordType: 'MOC',
|
||||
sortField: 'timestamp',
|
||||
sortOrder: 'desc',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
recordTypes.value = ['MOC'];
|
||||
queryParams = Object.assign(queryParams, {
|
||||
recordType: 'MOC',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**记录类型 */
|
||||
const recordTypes = ref<string[]>(['MOC']);
|
||||
|
||||
/**查询记录类型变更 */
|
||||
function fnQueryRecordTypeChange(value: any) {
|
||||
if (Array.isArray(value)) {
|
||||
queryParams.recordType = value.join(',');
|
||||
}
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**斑马纹 */
|
||||
striped: boolean;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
striped: false,
|
||||
seached: true,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'id',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.recordType'),
|
||||
dataIndex: 'cdrJSON',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
return cdrJSON.recordType;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.type'),
|
||||
dataIndex: 'cdrJSON',
|
||||
key: 'callType',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.called'),
|
||||
dataIndex: 'cdrJSON',
|
||||
key: 'calledParty',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
return cdrJSON.calledParty;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.caller'),
|
||||
dataIndex: 'cdrJSON',
|
||||
key: 'callerParty',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
return cdrJSON.callerParty;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.duration'),
|
||||
dataIndex: 'cdrJSON',
|
||||
key: 'callDuration',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
return cdrJSON.callType === 'sms'
|
||||
? '-'
|
||||
: parseDuration(cdrJSON.callDuration);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.result'),
|
||||
dataIndex: 'cdrJSON',
|
||||
key: 'cause',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.time'),
|
||||
dataIndex: 'cdrJSON',
|
||||
align: 'center',
|
||||
width: 150,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
return parseDateToStr(+cdrJSON.releaseTime * 1000);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格斑马纹 */
|
||||
function fnTableStriped(_record: unknown, index: number): any {
|
||||
return tableState.striped && index % 2 === 1 ? 'table-striped' : undefined;
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
/**最大ID值 */
|
||||
maxId: number;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
confirmLoading: false,
|
||||
maxId: 0,
|
||||
});
|
||||
|
||||
/**
|
||||
* 记录删除
|
||||
* @param id 编号
|
||||
*/
|
||||
function fnRecordDelete(id: string) {
|
||||
if (!id || modalState.confirmLoading) return;
|
||||
let msg = id;
|
||||
if (id === '0') {
|
||||
msg = `${id}... ${tableState.selectedRowKeys.length}`;
|
||||
id = tableState.selectedRowKeys.join(',');
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.dashboard.cdr.delTip', { msg }),
|
||||
onOk() {
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
delIMSDataCDR(id)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.operateOk'),
|
||||
duration: 3,
|
||||
});
|
||||
fnGetList(1);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**查询列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
listIMSDataCDR(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
tablePagination.total = res.total;
|
||||
// 遍历处理cdr字符串数据
|
||||
tableState.data = res.rows.map(item => {
|
||||
let cdrJSON = item.cdrJSON;
|
||||
if (!cdrJSON) {
|
||||
Reflect.set(item, 'cdrJSON', {});
|
||||
}
|
||||
|
||||
try {
|
||||
cdrJSON = JSON.parse(cdrJSON);
|
||||
Reflect.set(item, 'cdrJSON', cdrJSON);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Reflect.set(item, 'cdrJSON', {});
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
// 取最大值ID用作实时累加
|
||||
if (res.total > 0) {
|
||||
modalState.maxId = Number(res.rows[0].id);
|
||||
}
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**实时数据开关 */
|
||||
const realTimeData = ref<boolean>(false);
|
||||
|
||||
/**
|
||||
* 实时数据
|
||||
*/
|
||||
function fnRealTime() {
|
||||
realTimeData.value = !realTimeData.value;
|
||||
if (realTimeData.value) {
|
||||
// 建立链接
|
||||
const options: OptionsType = {
|
||||
url: '/ws',
|
||||
params: {
|
||||
/**订阅通道组
|
||||
*
|
||||
* CDR会话事件-IMS (GroupID:1005)
|
||||
*/
|
||||
subGroupID: '1005',
|
||||
},
|
||||
onmessage: wsMessage,
|
||||
onerror: wsError,
|
||||
};
|
||||
ws.connect(options);
|
||||
} else {
|
||||
ws.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**接收数据后回调 */
|
||||
function wsError(ev: any) {
|
||||
// 接收数据后回调
|
||||
console.error(ev);
|
||||
}
|
||||
|
||||
/**接收数据后回调 */
|
||||
function wsMessage(res: Record<string, any>) {
|
||||
const { code, requestId, data } = res;
|
||||
if (code === RESULT_CODE_ERROR) {
|
||||
console.warn(res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// 订阅组信息
|
||||
if (!data?.groupId) {
|
||||
return;
|
||||
}
|
||||
// cdrEvent CDR会话事件
|
||||
if (data.groupId === '1005') {
|
||||
const cdrEvent = data.data;
|
||||
queue.add(async () => {
|
||||
modalState.maxId += 1;
|
||||
tableState.data.unshift({
|
||||
id: modalState.maxId,
|
||||
neType: cdrEvent.neType,
|
||||
neName: cdrEvent.neName,
|
||||
rmUID: cdrEvent.rmUID,
|
||||
timestamp: cdrEvent.timestamp,
|
||||
cdrJSON: cdrEvent.CDR,
|
||||
});
|
||||
tablePagination.total += 1;
|
||||
if (tableState.data.length > 100) {
|
||||
tableState.data.pop();
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 800));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('cdr_sip_code'), getDict('cdr_call_type')])
|
||||
.then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.cdrSipCode = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.cdrCallType = resArr[1].value;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
ws.close();
|
||||
});
|
||||
</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="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.dashboard.cdr.recordType')"
|
||||
name="recordType "
|
||||
>
|
||||
<a-select
|
||||
v-model:value="recordTypes"
|
||||
mode="tags"
|
||||
:options="
|
||||
['MOC', 'MTC', 'MOSM', 'MTSM'].map(v => ({ value: v }))
|
||||
"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
@change="fnQueryRecordTypeChange"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
{{ t('common.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-popconfirm
|
||||
placement="bottomLeft"
|
||||
:title="
|
||||
!realTimeData
|
||||
? t('views.dashboard.cdr.realTimeDataStart')
|
||||
: t('views.dashboard.cdr.realTimeDataStop')
|
||||
"
|
||||
ok-text="Yes"
|
||||
cancel-text="No"
|
||||
@confirm="fnRealTime()"
|
||||
>
|
||||
<a-button type="primary" :danger="realTimeData">
|
||||
<template #icon><FundOutlined /> </template>
|
||||
{{
|
||||
!realTimeData
|
||||
? t('views.dashboard.cdr.realTimeDataStart')
|
||||
: t('views.dashboard.cdr.realTimeDataStop')
|
||||
}}
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
:loading="modalState.confirmLoading"
|
||||
@click.prevent="fnRecordDelete('0')"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
{{ t('common.deleteText') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<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.tableStripedText') }}</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.striped"
|
||||
:checked-children="t('common.switch.open')"
|
||||
:un-checked-children="t('common.switch.shut')"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.sizeText') }}</template>
|
||||
<a-dropdown trigger="click" placement="bottomRight">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">
|
||||
{{ t('common.size.default') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="middle">
|
||||
{{ t('common.size.middle') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="small">
|
||||
{{ t('common.size.small') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:row-class-name="fnTableStriped"
|
||||
:scroll="{ x: tableColumns.length * 120, y: 'calc(100vh - 480px)' }"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
columnWidth: '48px',
|
||||
selectedRowKeys: tableState.selectedRowKeys,
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'callType'">
|
||||
<DictTag
|
||||
:options="dict.cdrCallType"
|
||||
:value="record.cdrJSON.callType"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'cause'">
|
||||
<span v-if="record.cdrJSON.callType !== 'sms'">
|
||||
<DictTag
|
||||
:options="dict.cdrSipCode"
|
||||
:value="record.cdrJSON.cause"
|
||||
value-option="0"
|
||||
/>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ t('views.dashboard.overview.userActivity.resultOK') }}
|
||||
</span>
|
||||
</template>
|
||||
<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.id)"
|
||||
>
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
<template #expandedRowRender="{ record }">
|
||||
<div style="width: 46%; padding-left: 32px; padding-bottom: 16px">
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.dashboard.cdr.cdrInfo') }}
|
||||
</a-divider>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.neName') }}: </span>
|
||||
<span>{{ record.neName }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.rmUID') }}: </span>
|
||||
<span>{{ record.rmUID }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.time') }}: </span>
|
||||
<span>{{ parseDateToStr(+record.timestamp * 1000) }}</span>
|
||||
</div>
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.dashboard.cdr.rowInfo') }}
|
||||
</a-divider>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.type') }}: </span>
|
||||
<DictTag
|
||||
:options="dict.cdrCallType"
|
||||
:value="record.cdrJSON.callType"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.duration') }}: </span>
|
||||
<span v-if="record.cdrJSON.callType !== 'sms'">
|
||||
{{ parseDuration(record.cdrJSON.callDuration) }}
|
||||
</span>
|
||||
<span v-else> - </span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.caller') }}: </span>
|
||||
<span>{{ record.cdrJSON.callerParty }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.called') }}: </span>
|
||||
<span>{{ record.cdrJSON.calledParty }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.result') }}: </span>
|
||||
<span v-if="record.cdrJSON.callType !== 'sms'">
|
||||
<DictTag
|
||||
:options="dict.cdrSipCode"
|
||||
:value="record.cdrJSON.cause"
|
||||
value-option="0"
|
||||
/>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ t('views.dashboard.overview.userActivity.resultOK') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.table-striped) td {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, onBeforeUnmount, nextTick, watch } from 'vue';
|
||||
import { onMounted, ref, nextTick, watch } from 'vue';
|
||||
import * as echarts from 'echarts/core';
|
||||
import { GridComponent, GridComponentOption } from 'echarts/components';
|
||||
import {
|
||||
@@ -98,7 +98,7 @@ const optionData: EChartsOption = {
|
||||
itemStyle: {
|
||||
color: function (params) {
|
||||
// 红色
|
||||
if (+params.value >= 70) {
|
||||
if (params.value && +params.value >= 70) {
|
||||
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#fff1f0' },
|
||||
{ offset: 0.5, color: '#ffa39e' },
|
||||
@@ -106,7 +106,7 @@ const optionData: EChartsOption = {
|
||||
]);
|
||||
}
|
||||
// 蓝色
|
||||
if (+params.value >= 30) {
|
||||
if (params.value && +params.value >= 30) {
|
||||
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#f0f5ff' },
|
||||
{ offset: 0.5, color: '#adc6ff' },
|
||||
@@ -161,9 +161,9 @@ const optionData: EChartsOption = {
|
||||
label: {
|
||||
formatter: params => {
|
||||
var text = `{a| ${params.value}%} `;
|
||||
if (+params.value >= 70) {
|
||||
if (params.value && +params.value >= 70) {
|
||||
text = `{c| ${params.value}%} `;
|
||||
} else if (+params.value >= 30) {
|
||||
} else if (params.value && +params.value >= 30) {
|
||||
text = `{b| ${params.value}%} `;
|
||||
}
|
||||
return text;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, onBeforeUnmount } from 'vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { listNe } from '@/api/ne/ne';
|
||||
import { listAllNeInfo } from '@/api/ne/neInfo';
|
||||
import { message } from 'ant-design-vue/lib';
|
||||
import { getGraphData } from '@/api/monitor/topology';
|
||||
import { Graph, GraphData, Tooltip } from '@antv/g6';
|
||||
@@ -149,7 +149,7 @@ function handleRanderGraph(
|
||||
function fnGraphDataLoad(reload: boolean = false) {
|
||||
Promise.all([
|
||||
getGraphData(graphState.group),
|
||||
listNe({
|
||||
listAllNeInfo({
|
||||
bandStatus: false,
|
||||
}),
|
||||
])
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
<template>
|
||||
<div ref="upfFlow" class="chart-container"></div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
import { listUPFData } from '@/api/perfManage/goldTarget';
|
||||
import { parseSizeFromKbs } from '@/utils/parse-utils';
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { listKPIData } from '@/api/perfManage/goldTarget';
|
||||
import * as echarts from 'echarts/core';
|
||||
import {
|
||||
TooltipComponent,
|
||||
@@ -20,6 +16,8 @@ import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { markRaw } from 'vue';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { upfFlowData, upfFlowParse } from '../../hooks/useUPFTotalFlow';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -39,9 +37,6 @@ type EChartsOption = echarts.ComposeOption<
|
||||
| LineSeriesOption
|
||||
>;
|
||||
|
||||
/**定时器ID */
|
||||
const timeoutId = ref<any>(null);
|
||||
|
||||
/**图DOM节点实例对象 */
|
||||
const upfFlow = ref<HTMLElement | undefined>(undefined);
|
||||
|
||||
@@ -57,132 +52,142 @@ function fnDesign(container: HTMLElement | undefined, option: EChartsOption) {
|
||||
}
|
||||
|
||||
option && upfFlowChart.value.setOption(option);
|
||||
window.onresize = function () {
|
||||
// echarts 窗口缩放自适应 随着div--echarts-records的大小来适应
|
||||
upfFlowChart.value.resize();
|
||||
};
|
||||
|
||||
// 创建 ResizeObserver 实例
|
||||
var observer = new ResizeObserver(entries => {
|
||||
if (upfFlowChart.value) {
|
||||
upfFlowChart.value.resize();
|
||||
}
|
||||
});
|
||||
// 监听元素大小变化
|
||||
observer.observe(container);
|
||||
}
|
||||
|
||||
//渲染速率图
|
||||
function initPicture() {
|
||||
clearTimeout(timeoutId.value);
|
||||
function handleRanderChart() {
|
||||
const { lineXTime, lineYUp, lineYDown } = upfFlowData.value;
|
||||
|
||||
let queryArr: any = [];
|
||||
const initTime: Date = new Date();
|
||||
const startTime: Date = new Date(initTime);
|
||||
startTime.setHours(0, 0, 0, 0); // 设置为今天的0点
|
||||
const endTime: Date = new Date(initTime);
|
||||
endTime.setHours(23, 59, 59, 59); // 设置为今天的12点
|
||||
queryArr = [parseDateToStr(startTime), parseDateToStr(endTime)];
|
||||
|
||||
listUPFData(queryArr).then(res => {
|
||||
timeoutId.value = setTimeout(() => {
|
||||
initPicture(); // 5秒后再次获取数据
|
||||
}, 5000);
|
||||
let timeArr: any = [];
|
||||
let upValue: any = [];
|
||||
let downValue: any = [];
|
||||
|
||||
res.upData.map((item: any) => {
|
||||
timeArr.push(item.timestamp);
|
||||
upValue.push(parseSizeFromKbs(item.value, item.granularity)[0]);
|
||||
});
|
||||
res.downData.map((item: any) => {
|
||||
downValue.push(parseSizeFromKbs(item.value, item.granularity)[0]);
|
||||
});
|
||||
|
||||
var charts = {
|
||||
unit: '(Mbps)',
|
||||
names: [
|
||||
t('views.dashboard.overview.upfFlow.up'),
|
||||
t('views.dashboard.overview.upfFlow.down'),
|
||||
],
|
||||
|
||||
lineX: timeArr,
|
||||
value: [upValue, downValue],
|
||||
};
|
||||
|
||||
var color = ['rgba(250, 219, 20', 'rgba(92, 123, 217'];
|
||||
var lineY: any = [];
|
||||
|
||||
for (var i = 0; i < charts.names.length; i++) {
|
||||
var x = i;
|
||||
if (x > color.length - 1) {
|
||||
x = color.length - 1;
|
||||
}
|
||||
var data = {
|
||||
name: charts.names[i],
|
||||
type: 'line',
|
||||
color: color[x] + ')',
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: color[x] + ', .5)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: color[x] + ', 0.5)',
|
||||
},
|
||||
]),
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||
shadowBlur: 10,
|
||||
},
|
||||
symbol: 'circle',
|
||||
symbolSize: 5,
|
||||
formatter: '{b}',
|
||||
data: charts.value[i],
|
||||
};
|
||||
lineY.push(data);
|
||||
}
|
||||
|
||||
const optionData: EChartsOption = {
|
||||
tooltip: {
|
||||
show: true, //是否显示提示框组件
|
||||
trigger: 'axis',
|
||||
//formatter:'{a0}:{c0}<br>{a1}:{c1}'
|
||||
formatter: function (param: any) {
|
||||
var tip = '';
|
||||
if (param !== null && param.length > 0) {
|
||||
tip += param[0].name + '<br />';
|
||||
for (var i = 0; i < param.length; i++) {
|
||||
tip +=
|
||||
param[i].marker +
|
||||
param[i].seriesName +
|
||||
': ' +
|
||||
param[i].value +
|
||||
'<br />';
|
||||
}
|
||||
}
|
||||
return tip;
|
||||
},
|
||||
var yAxisSeries: any = [
|
||||
{
|
||||
name: t('views.dashboard.overview.upfFlow.up'),
|
||||
type: 'line',
|
||||
color: 'rgba(250, 219, 20)',
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(250, 219, 20, .5)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(250, 219, 20, 0.5)',
|
||||
},
|
||||
]),
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||
shadowBlur: 10,
|
||||
},
|
||||
legend: {
|
||||
data: charts.names,
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
symbol: 'circle',
|
||||
symbolSize: 5,
|
||||
formatter: '{b}',
|
||||
data: lineYUp,
|
||||
},
|
||||
{
|
||||
name: t('views.dashboard.overview.upfFlow.down'),
|
||||
type: 'line',
|
||||
color: 'rgba(92, 123, 217)',
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(92, 123, 217, .5)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(92, 123, 217, 0.5)',
|
||||
},
|
||||
]),
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||
shadowBlur: 10,
|
||||
},
|
||||
symbol: 'circle',
|
||||
symbolSize: 5,
|
||||
formatter: '{b}',
|
||||
data: lineYDown,
|
||||
},
|
||||
];
|
||||
|
||||
const optionData: EChartsOption = {
|
||||
tooltip: {
|
||||
show: true, //是否显示提示框组件
|
||||
trigger: 'axis',
|
||||
//formatter:'{a0}:{c0}<br>{a1}:{c1}'
|
||||
formatter: function (param: any) {
|
||||
var tip = '';
|
||||
if (param !== null && param.length > 0) {
|
||||
tip += param[0].name + '<br />';
|
||||
for (var i = 0; i < param.length; i++) {
|
||||
tip +=
|
||||
param[i].marker +
|
||||
param[i].seriesName +
|
||||
': ' +
|
||||
param[i].value +
|
||||
'<br />';
|
||||
}
|
||||
}
|
||||
return tip;
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
data: yAxisSeries.map((s: any) => s.name),
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: 'rgb(0,253,255,0.6)',
|
||||
},
|
||||
left: 'center', // 设置图例居中
|
||||
},
|
||||
grid: {
|
||||
top: '14%',
|
||||
left: '4%',
|
||||
right: '4%',
|
||||
bottom: '12%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: lineXTime,
|
||||
axisLabel: {
|
||||
formatter: function (params: any) {
|
||||
return params.split(' ')[1];
|
||||
},
|
||||
fontSize: 14,
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: 'rgb(0,253,255,0.6)',
|
||||
},
|
||||
left: 'center',
|
||||
}, // 设置图例居中
|
||||
|
||||
grid: {
|
||||
top: '14%',
|
||||
left: '4%',
|
||||
right: '4%',
|
||||
bottom: '12%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: charts.lineX,
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
name: '(Mbps)',
|
||||
nameTextStyle: {
|
||||
fontSize: 12, // 设置文字距离x轴的距离
|
||||
padding: [0, -10, 0, 0], // 设置名称在x轴方向上的偏移
|
||||
},
|
||||
type: 'value',
|
||||
// splitNumber: 4,
|
||||
min: 0,
|
||||
//max: 300,
|
||||
axisLabel: {
|
||||
formatter: function (params: any) {
|
||||
return params.split(' ')[1];
|
||||
formatter: '{value}',
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: 'rgb(23,255,243,0.3)',
|
||||
},
|
||||
fontSize: 14,
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
@@ -190,44 +195,95 @@ function initPicture() {
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
name: charts.unit,
|
||||
type: 'value',
|
||||
// splitNumber: 4,
|
||||
min: 0,
|
||||
//max: 300,
|
||||
axisLabel: {
|
||||
formatter: '{value}',
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: 'rgb(23,255,243,0.3)',
|
||||
},
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: 'rgb(0,253,255,0.6)',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
series: lineY,
|
||||
};
|
||||
fnDesign(upfFlow.value, optionData);
|
||||
});
|
||||
],
|
||||
series: yAxisSeries,
|
||||
};
|
||||
fnDesign(upfFlow.value, optionData);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initPicture();
|
||||
});
|
||||
/**查询初始UPF数据 */
|
||||
function fnGetInitData() {
|
||||
// 查询10分钟前的
|
||||
const nowDate: Date = new Date();
|
||||
const tenMinutesAgo = new Date(nowDate.getTime() - 5 * 60 * 1000);
|
||||
|
||||
/**组件实例被卸载之后调用 */
|
||||
onUnmounted(() => {
|
||||
clearTimeout(timeoutId.value);
|
||||
listKPIData({
|
||||
neType: 'UPF',
|
||||
neId: '001',
|
||||
startTime: parseDateToStr(tenMinutesAgo),
|
||||
endTime: parseDateToStr(nowDate),
|
||||
// startTime: '2024-03-20 19:50:00',
|
||||
// endTime: '2024-03-20 19:55:00',
|
||||
interval: 5, // 5秒
|
||||
sortField: 'timeGroup',
|
||||
sortOrder: 'asc',
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
for (const item of res.data) {
|
||||
upfFlowParse(item);
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
handleRanderChart();
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => upfFlowData.value,
|
||||
v => {
|
||||
if (upfFlowChart.value == null) return;
|
||||
upfFlowChart.value.setOption({
|
||||
xAxis: {
|
||||
data: v.lineXTime,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: v.lineYUp,
|
||||
},
|
||||
{
|
||||
data: v.lineYDown,
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
fnGetInitData();
|
||||
|
||||
// setInterval(() => {
|
||||
// upfFlowData.value.lineXTime.push(parseDateToStr(new Date()));
|
||||
// const upN3 = parseSizeFromKbs(+145452, 5);
|
||||
// upfFlowData.value.lineYUp.push(upN3[0]);
|
||||
// const downN6 = parseSizeFromKbs(+232343, 5);
|
||||
// upfFlowData.value.lineYDown.push(downN6[0]);
|
||||
|
||||
// upfFlowChart.value.setOption({
|
||||
// xAxis: {
|
||||
// data: upfFlowData.value.lineXTime,
|
||||
// },
|
||||
// series: [
|
||||
// {
|
||||
// data: upfFlowData.value.lineYUp,
|
||||
// },
|
||||
// {
|
||||
// data: upfFlowData.value.lineYDown,
|
||||
// },
|
||||
// ],
|
||||
// });
|
||||
// }, 5000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="upfFlow" class="chart-container"></div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.chart-container {
|
||||
/* 设置图表容器大小和位置 */
|
||||
|
||||
@@ -85,9 +85,9 @@ onMounted(() => {
|
||||
<div class="card-cdr-item">
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.caller') }}:
|
||||
<span :title="item.data.callerParty">{{
|
||||
item.data.callerParty
|
||||
}}</span>
|
||||
<span :title="item.data.callerParty">
|
||||
{{ item.data.callerParty }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.called') }}:
|
||||
@@ -99,11 +99,12 @@ onMounted(() => {
|
||||
{{ t('views.dashboard.overview.userActivity.duration') }}:
|
||||
<span>{{ parseDuration(item.data.callDuration) }}</span>
|
||||
</div>
|
||||
<div v-else></div>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||
<span v-if="item.data.callType !== 'sms'">
|
||||
<DictTag :options="dict.cdrSipCode" :value="item.data.cause" />
|
||||
<DictTag :options="dict.cdrSipCode" :value="item.data.cause" value-option="0" />
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ t('views.dashboard.overview.userActivity.resultOK') }}
|
||||
|
||||
@@ -157,10 +157,28 @@
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 概览区域 衍生基站信息 */
|
||||
.skim.base {
|
||||
height: 20.6%;
|
||||
}
|
||||
|
||||
.skim.base .inner .data {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 45%;
|
||||
}
|
||||
.skim.base .inner .data .item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: baseline;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
/* 用户行为 */
|
||||
.userActivity {
|
||||
/* min-height: 35.8rem; */
|
||||
height: 66%;
|
||||
height: 60%;
|
||||
}
|
||||
.userActivity .inner .chart {
|
||||
width: 100%;
|
||||
@@ -247,3 +265,13 @@
|
||||
height: 100%;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
/* 跳转鼠标悬浮 */
|
||||
.toRouter:hover {
|
||||
cursor: pointer;
|
||||
color: #fff !important;
|
||||
}
|
||||
.toRouter:hover > *,
|
||||
.toRouter:hover > * > * {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,40 @@
|
||||
import { parseSizeFromBits } from '@/utils/parse-utils';
|
||||
import { parseSizeFromBits, parseSizeFromKbs } from '@/utils/parse-utils';
|
||||
import { ref } from 'vue';
|
||||
|
||||
/**UPF-流量数据 */
|
||||
export const upfFlowData = ref<{
|
||||
/**时间 */
|
||||
lineXTime: string[];
|
||||
/**上行 N3 */
|
||||
lineYUp: number[];
|
||||
/**下行 N6 */
|
||||
lineYDown: number[];
|
||||
/**容量 */
|
||||
cap: number;
|
||||
}>({
|
||||
lineXTime: [],
|
||||
lineYUp: [],
|
||||
lineYDown: [],
|
||||
cap: 0,
|
||||
});
|
||||
|
||||
/**UPF-流量数据 数据解析 */
|
||||
export function upfFlowParse(data: Record<string, string>) {
|
||||
upfFlowData.value.lineXTime.push(data['timeGroup']);
|
||||
const upN3 = parseSizeFromKbs(+data['UPF.03'], 5);
|
||||
upfFlowData.value.lineYUp.push(upN3[0]);
|
||||
const downN6 = parseSizeFromKbs(+data['UPF.06'], 5);
|
||||
upfFlowData.value.lineYDown.push(downN6[0]);
|
||||
upfFlowData.value.cap += 1;
|
||||
// 超过 25 弹出
|
||||
if (upfFlowData.value.cap > 25) {
|
||||
upfFlowData.value.lineXTime.shift();
|
||||
upfFlowData.value.lineYUp.shift();
|
||||
upfFlowData.value.lineYDown.shift();
|
||||
upfFlowData.value.cap -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
type TFType = {
|
||||
/**上行 N3 */
|
||||
up: string;
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
eventTotal,
|
||||
eventId,
|
||||
} from './useUserActivity';
|
||||
import { upfTotalFlow, upfTFParse } from './useUPFTotalFlow';
|
||||
import { upfTotalFlow, upfTFParse, upfFlowParse } from './useUPFTotalFlow';
|
||||
import { neStateParse } from './useTopology';
|
||||
import PQueue from 'p-queue';
|
||||
|
||||
@@ -102,6 +102,12 @@ export default function useWS() {
|
||||
return;
|
||||
}
|
||||
switch (data.groupId) {
|
||||
// kpiEvent 指标UPF
|
||||
case '12':
|
||||
if (data.data) {
|
||||
upfFlowParse(data.data);
|
||||
}
|
||||
break;
|
||||
// ueEvent UE会话事件
|
||||
case '1010':
|
||||
if (data.data) {
|
||||
@@ -112,7 +118,9 @@ export default function useWS() {
|
||||
eventTotal.value += 1;
|
||||
eventId.value = v.eId;
|
||||
await new Promise(resolve => setTimeout(resolve, 800));
|
||||
eventData.value.pop();
|
||||
if (eventData.value.length > 20) {
|
||||
eventData.value.pop();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -127,7 +135,9 @@ export default function useWS() {
|
||||
eventTotal.value += 1;
|
||||
eventId.value = v.eId;
|
||||
await new Promise(resolve => setTimeout(resolve, 800));
|
||||
eventData.value.pop();
|
||||
if (eventData.value.length > 20) {
|
||||
eventData.value.pop();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -201,10 +211,11 @@ export default function useWS() {
|
||||
params: {
|
||||
/**订阅通道组
|
||||
*
|
||||
* 指标UPF (GroupID:12)
|
||||
* UE会话事件-AMF (GroupID:1010)
|
||||
* CDR会话事件-IMS (GroupID:1005)
|
||||
*/
|
||||
subGroupID: '1010,1005',
|
||||
subGroupID: '12,1010,1005',
|
||||
},
|
||||
onmessage: wsMessage,
|
||||
onerror: wsError,
|
||||
|
||||
@@ -13,7 +13,6 @@ import { listSub } from '@/api/neUser/sub';
|
||||
import { listUENumBySMF } from '@/api/neUser/smf';
|
||||
import { listUENumByIMS } from '@/api/neUser/ims';
|
||||
import { listBase5G } from '@/api/neUser/base5G';
|
||||
|
||||
import {
|
||||
graphNodeClickID,
|
||||
graphState,
|
||||
@@ -26,7 +25,8 @@ import { useFullscreen } from '@vueuse/core';
|
||||
import useWS from './hooks/useWS';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
const { wsSend, cdrEventSend, ueEventSend, upfTFSend } = useWS();
|
||||
@@ -39,8 +39,14 @@ type SkimStateType = {
|
||||
smfUeNum: number;
|
||||
/**IMS在线用户数 */
|
||||
imsUeNum: number;
|
||||
/**基站数量 */
|
||||
nbNum: number;
|
||||
/**5G基站数量 */
|
||||
gnbNum: number;
|
||||
/**5G在线用户数量 */
|
||||
gnbUeNum: number;
|
||||
/**4G基站数量 */
|
||||
enbNum: number;
|
||||
/**4G在线用户数量 */
|
||||
enbUeNum: number;
|
||||
};
|
||||
|
||||
/**概览状态信息 */
|
||||
@@ -48,7 +54,10 @@ let skimState: SkimStateType = reactive({
|
||||
udmSubNum: 0,
|
||||
smfUeNum: 0,
|
||||
imsUeNum: 0,
|
||||
nbNum: 0,
|
||||
gnbNum: 0,
|
||||
gnbUeNum: 0,
|
||||
enbNum: 0,
|
||||
enbUeNum: 0,
|
||||
});
|
||||
|
||||
/**总览节点 */
|
||||
@@ -56,14 +65,13 @@ const viewportDom = ref<HTMLElement | null>(null);
|
||||
const { isFullscreen, toggle } = useFullscreen(viewportDom);
|
||||
|
||||
/**10s调度器 */
|
||||
const stateInterval = ref<any>(null);
|
||||
const interval10s = ref<any>(null);
|
||||
|
||||
/**网元状态定时器 */
|
||||
const stateTimeout = ref<any>(null);
|
||||
/**5s调度器 */
|
||||
const interval5s = ref<any>(null);
|
||||
|
||||
/**查询网元状态 */
|
||||
function fnGetState() {
|
||||
clearTimeout(stateTimeout.value);
|
||||
function fnGetNeState() {
|
||||
// 获取节点状态
|
||||
for (const node of graphState.data.nodes) {
|
||||
if (notNeNodes.includes(node.id)) continue;
|
||||
@@ -82,11 +90,6 @@ function fnGetState() {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
stateTimeout.value = setTimeout(() => {
|
||||
fnGetSkim(); // 获取概览信息
|
||||
fnGetState();
|
||||
}, 5_000);
|
||||
}
|
||||
|
||||
/**获取概览信息 */
|
||||
@@ -97,16 +100,16 @@ async function fnGetSkim() {
|
||||
pageNum: 1,
|
||||
pageSize: 1,
|
||||
}),
|
||||
listUENumBySMF('001'),
|
||||
listUENumByIMS('001'),
|
||||
listBase5G({
|
||||
neType: 'AMF',
|
||||
neId: '001',
|
||||
id: '',
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
}),
|
||||
listUENumBySMF('001'),
|
||||
listUENumByIMS('001'),
|
||||
listBase5G({
|
||||
neType: 'MME',
|
||||
neId: '001',
|
||||
}),
|
||||
]);
|
||||
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
@@ -118,33 +121,47 @@ async function fnGetSkim() {
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
const res1 = resArr[1].value;
|
||||
if (res1.code === RESULT_CODE_SUCCESS) {
|
||||
skimState.nbNum = res1.total;
|
||||
skimState.smfUeNum = res1.data;
|
||||
}
|
||||
}
|
||||
if (resArr[2].status === 'fulfilled') {
|
||||
const res2 = resArr[2].value;
|
||||
if (res2.code === RESULT_CODE_SUCCESS) {
|
||||
skimState.smfUeNum = res2.data;
|
||||
skimState.imsUeNum = res2.data;
|
||||
}
|
||||
}
|
||||
if (resArr[3].status === 'fulfilled') {
|
||||
const res3 = resArr[3].value;
|
||||
if (res3.code === RESULT_CODE_SUCCESS) {
|
||||
skimState.imsUeNum = res3.data;
|
||||
skimState.gnbNum = res3.total;
|
||||
skimState.gnbUeNum = 0;
|
||||
res3.rows.map((item: any) => {
|
||||
skimState.gnbUeNum += item.ueNum;
|
||||
});
|
||||
}
|
||||
}
|
||||
if (resArr[4].status === 'fulfilled') {
|
||||
const res4 = resArr[4].value;
|
||||
if (res4.code === RESULT_CODE_SUCCESS) {
|
||||
skimState.enbNum = res4.total;
|
||||
skimState.enbUeNum = 0;
|
||||
res4.rows.map((item: any) => {
|
||||
skimState.enbUeNum += item.ueNum;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**初始数据函数 */
|
||||
function loadData() {
|
||||
fnGetState(); // 获取网元状态
|
||||
fnGetNeState(); // 获取网元状态
|
||||
cdrEventSend();
|
||||
ueEventSend();
|
||||
upfTFSend(0);
|
||||
upfTFSend(7);
|
||||
upfTFSend(30);
|
||||
|
||||
stateInterval.value = setInterval(() => {
|
||||
interval10s.value = setInterval(() => {
|
||||
upfTFActive.value = upfTFActive.value >= 2 ? 0 : upfTFActive.value + 1;
|
||||
if (upfTFActive.value === 0) {
|
||||
upfTFSend(7);
|
||||
@@ -154,6 +171,16 @@ function loadData() {
|
||||
upfTFSend(0);
|
||||
}
|
||||
}, 10_000);
|
||||
|
||||
interval5s.value = setInterval(() => {
|
||||
fnGetSkim(); // 获取概览信息
|
||||
fnGetNeState(); // 获取网元状态
|
||||
}, 5_000);
|
||||
}
|
||||
|
||||
/**栏目信息跳转 */
|
||||
function fnToRouter(name: string, query?: any) {
|
||||
router.push({ name, query });
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@@ -163,8 +190,8 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearTimeout(stateTimeout.value);
|
||||
clearTimeout(stateInterval.value);
|
||||
clearInterval(interval10s.value);
|
||||
clearInterval(interval5s.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -192,7 +219,11 @@ onBeforeUnmount(() => {
|
||||
{{ t('views.dashboard.overview.skim.userTitle') }}
|
||||
</h3>
|
||||
<div class="data">
|
||||
<div class="item">
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('Sub_2010')"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<div>
|
||||
<UserOutlined
|
||||
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
|
||||
@@ -203,45 +234,98 @@ onBeforeUnmount(() => {
|
||||
{{ t('views.dashboard.overview.skim.users') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="item" style="margin: 0 12px">
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('Ims_2080')"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
style="margin: 0 12px"
|
||||
>
|
||||
<div>
|
||||
<img :src="svgUserIMS" style="width: 18px; margin-right: 8px" />
|
||||
{{ skimState.imsUeNum }}
|
||||
</div>
|
||||
<span>
|
||||
IMS {{ t('views.dashboard.overview.skim.session') }}
|
||||
{{ t('views.dashboard.overview.skim.imsUeNum') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('Ue_2081')"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<div>
|
||||
<img :src="svgUserSMF" style="width: 18px; margin-right: 8px" />
|
||||
{{ skimState.smfUeNum }}
|
||||
</div>
|
||||
<span>
|
||||
Data {{ t('views.dashboard.overview.skim.session') }}
|
||||
{{ t('views.dashboard.overview.skim.smfUeNum') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="skim panel">
|
||||
<div class="skim panel base">
|
||||
<div class="inner">
|
||||
<h3>
|
||||
<GlobalOutlined style="color: #68d8fe" />
|
||||
{{ t('views.dashboard.overview.skim.baseTitle') }}
|
||||
</h3>
|
||||
<div class="data">
|
||||
<div class="item">
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('Base5G_2082', { neType: 'AMF' })"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<div style="align-items: flex-start">
|
||||
<img
|
||||
:src="svgBase"
|
||||
style="width: 18px; margin-right: 8px; height: 2rem"
|
||||
/>
|
||||
{{ skimState.nbNum }}
|
||||
{{ skimState.gnbNum }}
|
||||
</div>
|
||||
<span>
|
||||
{{ t('views.dashboard.overview.skim.base') }}
|
||||
</span>
|
||||
<span>{{ t('views.dashboard.overview.skim.gnbBase') }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('Base5G_2082', { neType: 'AMF' })"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<div style="align-items: flex-start">
|
||||
<UserOutlined
|
||||
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
|
||||
/>
|
||||
{{ skimState.gnbUeNum }}
|
||||
</div>
|
||||
<span>{{ t('views.dashboard.overview.skim.gnbUeNum') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data">
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('Base5G_2082', { neType: 'MME' })"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<div style="align-items: flex-start">
|
||||
<img
|
||||
:src="svgBase"
|
||||
style="width: 18px; margin-right: 8px; height: 2rem"
|
||||
/>
|
||||
{{ skimState.enbNum }}
|
||||
</div>
|
||||
<span>{{ t('views.dashboard.overview.skim.enbBase') }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('Base5G_2082', { neType: 'MME' })"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<div style="align-items: flex-start">
|
||||
<UserOutlined
|
||||
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
|
||||
/>
|
||||
{{ skimState.enbUeNum }}
|
||||
</div>
|
||||
<span>{{ t('views.dashboard.overview.skim.enbUeNum') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -264,7 +348,11 @@ onBeforeUnmount(() => {
|
||||
<!-- 实时流量 -->
|
||||
<div class="upfFlow panel">
|
||||
<div class="inner">
|
||||
<h3>
|
||||
<h3
|
||||
class="toRouter"
|
||||
@click="fnToRouter('GoldTarget_2104')"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<AreaChartOutlined style="color: #68d8fe" />
|
||||
{{ t('views.dashboard.overview.upfFlow.title') }}
|
||||
</h3>
|
||||
@@ -276,7 +364,11 @@ onBeforeUnmount(() => {
|
||||
<!-- 网络拓扑 -->
|
||||
<div class="topology panel">
|
||||
<div class="inner">
|
||||
<h3>
|
||||
<h3
|
||||
class="toRouter"
|
||||
@click="fnToRouter('TopologyArchitecture_2128')"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<span>
|
||||
<ApartmentOutlined style="color: #68d8fe" />
|
||||
{{ t('views.dashboard.overview.topology.title') }}
|
||||
@@ -349,7 +441,11 @@ onBeforeUnmount(() => {
|
||||
<!-- 告警统计 -->
|
||||
<div class="alarmType panel">
|
||||
<div class="inner">
|
||||
<h3>
|
||||
<h3
|
||||
class="toRouter"
|
||||
@click="fnToRouter('HistoryAlarm_2097')"
|
||||
:title="t('views.dashboard.overview.toRouter')"
|
||||
>
|
||||
<PieChartOutlined style="color: #68d8fe" />
|
||||
{{ t('views.dashboard.overview.alarmTypeBar.alarmSum') }}
|
||||
</h3>
|
||||
|
||||
@@ -960,6 +960,7 @@ onMounted(() => {
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
<TableColumnsDnd
|
||||
cache-id="alarmActive"
|
||||
:columns="tableColumns"
|
||||
v-model:columns-dnd="tableColumnsDnd"
|
||||
></TableColumnsDnd>
|
||||
|
||||
@@ -755,6 +755,7 @@ onMounted(() => {
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
<TableColumnsDnd
|
||||
cache-id="alarmHistory"
|
||||
:columns="tableColumns"
|
||||
v-model:columns-dnd="tableColumnsDnd"
|
||||
></TableColumnsDnd>
|
||||
|
||||
@@ -73,6 +73,11 @@ let tableColumns: ColumnsType = [
|
||||
title: t('views.index.serialNum'),
|
||||
dataIndex: 'serialNum',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.index.expiryDate'),
|
||||
dataIndex: 'expiryDate',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.index.ipAddress'),
|
||||
|
||||
@@ -48,6 +48,7 @@ let state: StateType = reactive({
|
||||
key: '',
|
||||
operation: '',
|
||||
object: '',
|
||||
objectType: 'mml',
|
||||
param: [],
|
||||
},
|
||||
from: {
|
||||
@@ -102,6 +103,7 @@ function fnCleanFrom() {
|
||||
key: '',
|
||||
operation: '',
|
||||
object: '',
|
||||
objectType: 'mml',
|
||||
param: [],
|
||||
};
|
||||
state.from = {};
|
||||
@@ -114,14 +116,12 @@ function fnSendMML() {
|
||||
}
|
||||
|
||||
let cmdArr: string[] = [];
|
||||
const operation = state.mmlSelect.operation;
|
||||
const object = state.mmlSelect.object;
|
||||
const { operation, object, objectType, param } = state.mmlSelect;
|
||||
// 根据参数取值
|
||||
let argsArr: string[] = [];
|
||||
const param = toRaw(state.mmlSelect.param) || [];
|
||||
if (operation && Array.isArray(param)) {
|
||||
const from = toRaw(state.from);
|
||||
for (const item of param) {
|
||||
for (const item of toRaw(param)) {
|
||||
const value = from[item.name];
|
||||
|
||||
// 是否必填项且有效值
|
||||
@@ -175,20 +175,28 @@ function fnSendMML() {
|
||||
// 发送
|
||||
state.from.sendLoading = true;
|
||||
const [neType, neId] = state.neType;
|
||||
sendMMlByNE(neType, neId, cmdArr).then(res => {
|
||||
state.from.sendLoading = false;
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
let resultArr = res.data;
|
||||
for (let i = 0; i < resultArr.length; i++) {
|
||||
const str = resultArr[i];
|
||||
const logStr = str.replace(/(\r\n|\n)/g, '\n');
|
||||
const cmdStr = cmdArr[i];
|
||||
state.mmlCmdLog += `${cmdStr}\n${logStr}\n`;
|
||||
sendMMlByNE(neType, neId, objectType, cmdArr)
|
||||
.then(res => {
|
||||
state.from.sendLoading = false;
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
let resultArr = res.data;
|
||||
for (let i = 0; i < resultArr.length; i++) {
|
||||
const str = resultArr[i];
|
||||
const logStr = str.replace(/(\r\n|\n)/g, '\n');
|
||||
const cmdStr = cmdArr[i];
|
||||
state.mmlCmdLog += `${cmdStr}\n${logStr}\n`;
|
||||
}
|
||||
} else {
|
||||
state.mmlCmdLog += `${res.msg}\n`;
|
||||
}
|
||||
} else {
|
||||
state.mmlCmdLog += `${res.msg}\n`;
|
||||
}
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
// 控制台滚动底部
|
||||
const container = document.getElementsByClassName('cm-scroller')[0];
|
||||
if (container) {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**上传变更 */
|
||||
@@ -306,6 +314,15 @@ function ruleVerification(
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'file':
|
||||
if (filter) {
|
||||
const arr: string[] = filter.split(',');
|
||||
const itemArr = arr.filter(item => value.endsWith(item));
|
||||
if (itemArr.length === 0) {
|
||||
return [false, t('views.mmlManage.requireFile', { display })];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(t('views.mmlManage.requireUn', { display }), type);
|
||||
@@ -325,6 +342,7 @@ function fnNeChange(keys: any, _: any) {
|
||||
key: '',
|
||||
operation: '',
|
||||
object: '',
|
||||
objectType: 'mml',
|
||||
param: {},
|
||||
};
|
||||
fnGetList();
|
||||
@@ -344,6 +362,7 @@ function fnGetList() {
|
||||
for (const item of res.data) {
|
||||
const id = item['id'];
|
||||
const object = item['object'];
|
||||
const objectType = item['objectType'];
|
||||
const operation = item['operation'];
|
||||
const mmlDisplay = item['mmlDisplay'];
|
||||
// 可选属性参数
|
||||
@@ -362,20 +381,37 @@ function fnGetList() {
|
||||
key: item['category'],
|
||||
selectable: false,
|
||||
children: [
|
||||
{ key: id, title: mmlDisplay, object, operation, param },
|
||||
{
|
||||
key: id,
|
||||
title: mmlDisplay,
|
||||
object,
|
||||
objectType,
|
||||
operation,
|
||||
param,
|
||||
},
|
||||
],
|
||||
});
|
||||
autoCompleteArr.push({
|
||||
value: item['catDisplay'],
|
||||
key: item['category'],
|
||||
selectable: false,
|
||||
options: [{ key: id, value: mmlDisplay, object, operation, param }],
|
||||
options: [
|
||||
{
|
||||
key: id,
|
||||
value: mmlDisplay,
|
||||
object,
|
||||
objectType,
|
||||
operation,
|
||||
param,
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
treeArr[treeItemIndex].children.push({
|
||||
key: id,
|
||||
title: mmlDisplay,
|
||||
object,
|
||||
objectType,
|
||||
operation,
|
||||
param,
|
||||
});
|
||||
@@ -383,6 +419,7 @@ function fnGetList() {
|
||||
key: id,
|
||||
value: mmlDisplay,
|
||||
object,
|
||||
objectType,
|
||||
operation,
|
||||
param,
|
||||
});
|
||||
@@ -424,6 +461,7 @@ function fnAutoCompleteSelect(_: any, option: any) {
|
||||
key: option.key,
|
||||
operation: option.operation,
|
||||
object: option.object,
|
||||
objectType: option.objectType,
|
||||
param: option.param,
|
||||
};
|
||||
state.from = {};
|
||||
@@ -469,12 +507,35 @@ function fnAutoCompleteChange(value: any, _: any) {
|
||||
key: '',
|
||||
operation: '',
|
||||
object: '',
|
||||
objectType: state.mmlSelect.objectType,
|
||||
param: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**自动完成按键触发 */
|
||||
function fnAutoCompleteKeydown(evt: any) {
|
||||
if (evt.key === 'Enter') {
|
||||
// 阻止默认的换行行为
|
||||
evt.preventDefault();
|
||||
// 按下 Shift + Enter 键时换行
|
||||
if (evt.shiftKey) {
|
||||
// 插入换行符
|
||||
const textarea = evt.target;
|
||||
const start = textarea.selectionStart;
|
||||
const end = textarea.selectionEnd;
|
||||
const text = textarea.value;
|
||||
textarea.value = text.substring(0, start) + '\n' + text.substring(end);
|
||||
state.autoCompleteValue = textarea.value;
|
||||
// 更新光标位置
|
||||
textarea.selectionStart = textarea.selectionEnd = start + 1;
|
||||
} else {
|
||||
fnSendMML();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取网元网元列表
|
||||
neInfoStore.fnNelist().then(res => {
|
||||
@@ -590,8 +651,13 @@ onMounted(() => {
|
||||
:validate-on-rule-change="false"
|
||||
:validateTrigger="[]"
|
||||
>
|
||||
<a-form-item :label="t('views.mmlManage.cmdQuickEntry')">
|
||||
<a-form-item
|
||||
:label="t('views.mmlManage.cmdQuickEntry')"
|
||||
:help="t('views.mmlManage.cmdQuickEntryHelp')"
|
||||
>
|
||||
<!-- 非UPF通用4100 -->
|
||||
<a-auto-complete
|
||||
v-if="state.neType[0] !== 'UPF'"
|
||||
v-model:value="state.autoCompleteValue"
|
||||
:dropdown-match-select-width="500"
|
||||
style="width: 100%"
|
||||
@@ -599,9 +665,39 @@ onMounted(() => {
|
||||
@search="fnAutoCompleteSearch"
|
||||
@select="fnAutoCompleteSelect"
|
||||
@change="fnAutoCompleteChange"
|
||||
@keydown="fnAutoCompleteKeydown"
|
||||
>
|
||||
<a-textarea :placeholder="t('common.inputPlease')" auto-size />
|
||||
</a-auto-complete>
|
||||
<!-- 可选接口类型mml -->
|
||||
<a-input-group compact v-else>
|
||||
<a-auto-complete
|
||||
v-model:value="state.autoCompleteValue"
|
||||
:dropdown-match-select-width="500"
|
||||
style="width: 80%"
|
||||
:options="state.autoCompleteSearch"
|
||||
@search="fnAutoCompleteSearch"
|
||||
@select="fnAutoCompleteSelect"
|
||||
@change="fnAutoCompleteChange"
|
||||
@keydown="fnAutoCompleteKeydown"
|
||||
>
|
||||
<a-textarea
|
||||
:placeholder="t('common.inputPlease')"
|
||||
auto-size
|
||||
/>
|
||||
</a-auto-complete>
|
||||
<a-select
|
||||
v-model:value="state.mmlSelect.objectType"
|
||||
style="width: 20%"
|
||||
>
|
||||
<a-select-option value="mml">
|
||||
{{ t('views.mmlManage.neOperate.mml') }}
|
||||
</a-select-option>
|
||||
<a-select-option value="mml2">
|
||||
{{ t('views.mmlManage.neOperate.mml2') }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
@@ -717,10 +813,9 @@ onMounted(() => {
|
||||
</template>
|
||||
|
||||
<CodemirrorEdite
|
||||
v-model:value="state.mmlCmdLog"
|
||||
:value="state.mmlCmdLog"
|
||||
:disabled="true"
|
||||
:editor-style="{ height: '500px !important' }"
|
||||
:placeholder="t('views.mmlManage.cmdAwait')"
|
||||
height="500px"
|
||||
></CodemirrorEdite>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
@@ -18,7 +18,7 @@ let neOptions = ref<Record<string, any>[]>([]);
|
||||
/**对象信息状态类型 */
|
||||
type StateType = {
|
||||
/**网元ID */
|
||||
neId: string;
|
||||
neId: string | undefined;
|
||||
/**命令数据 tree */
|
||||
mmlTreeData: any[];
|
||||
/**命令选中 */
|
||||
@@ -37,7 +37,7 @@ type StateType = {
|
||||
|
||||
/**对象信息状态 */
|
||||
let state: StateType = reactive({
|
||||
neId: '',
|
||||
neId: undefined,
|
||||
mmlTreeData: [],
|
||||
mmlSelect: {
|
||||
title: '',
|
||||
@@ -108,6 +108,13 @@ function fnSendMML() {
|
||||
if (state.from.sendLoading) {
|
||||
return;
|
||||
}
|
||||
if (!state.neId) {
|
||||
message.warning({
|
||||
content: t('views.mmlManage.udmOpesrate.noOMC'),
|
||||
duration: 5,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let cmdArr: string[] = [];
|
||||
const operation = state.mmlSelect.operation;
|
||||
@@ -170,31 +177,47 @@ function fnSendMML() {
|
||||
|
||||
// 发送
|
||||
state.from.sendLoading = true;
|
||||
sendMMlByOMC(state.neId, cmdArr).then(res => {
|
||||
state.from.sendLoading = false;
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
let resultArr = res.data;
|
||||
for (let i = 0; i < resultArr.length; i++) {
|
||||
const str = resultArr[i];
|
||||
const logStr = str.replace(/(\r\n|\n)/g, '\n');
|
||||
const cmdStr = cmdArr[i];
|
||||
state.mmlCmdLog += `${cmdStr}\n${logStr}\n`;
|
||||
sendMMlByOMC(state.neId, cmdArr)
|
||||
.then(res => {
|
||||
state.from.sendLoading = false;
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
let resultArr = res.data;
|
||||
for (let i = 0; i < resultArr.length; i++) {
|
||||
const str = resultArr[i];
|
||||
const logStr = str.replace(/(\r\n|\n)/g, '\n');
|
||||
const cmdStr = cmdArr[i];
|
||||
state.mmlCmdLog += `${cmdStr}\n${logStr}\n`;
|
||||
}
|
||||
} else {
|
||||
state.mmlCmdLog += `${res.msg}\n`;
|
||||
}
|
||||
} else {
|
||||
state.mmlCmdLog += `${res.msg}\n`;
|
||||
}
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
// 控制台滚动底部
|
||||
const container = document.getElementsByClassName('cm-scroller')[0];
|
||||
if (container) {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**上传变更 */
|
||||
function fnUpload(up: UploadRequestOption, name: string) {
|
||||
const neId = state.neId;
|
||||
if (!neId) {
|
||||
message.warning({
|
||||
content: t('views.mmlManage.udmOpesrate.noOMC'),
|
||||
duration: 5,
|
||||
});
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.mmlManage.uploadFileTip'),
|
||||
onOk() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
state.from.uploadLoading = true;
|
||||
uploadFileToNE('OMC', state.neId, up.file as File, 5)
|
||||
uploadFileToNE('OMC', neId, up.file as File, 5)
|
||||
.then(res => {
|
||||
// 文件转存
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
@@ -300,6 +323,15 @@ function ruleVerification(
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'file':
|
||||
if (filter) {
|
||||
const arr: string[] = filter.split(',');
|
||||
const itemArr = arr.filter(item => value.endsWith(item));
|
||||
if (itemArr.length === 0) {
|
||||
return [false, t('views.mmlManage.requireFile', { display })];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(t('views.mmlManage.requireUn', { display }), type);
|
||||
@@ -449,6 +481,28 @@ function fnAutoCompleteChange(value: any, _: any) {
|
||||
}
|
||||
}
|
||||
|
||||
/**自动完成按键触发 */
|
||||
function fnAutoCompleteKeydown(evt: any) {
|
||||
if (evt.key === 'Enter') {
|
||||
// 阻止默认的换行行为
|
||||
evt.preventDefault();
|
||||
// 按下 Shift + Enter 键时换行
|
||||
if (evt.shiftKey) {
|
||||
// 插入换行符
|
||||
const textarea = evt.target;
|
||||
const start = textarea.selectionStart;
|
||||
const end = textarea.selectionEnd;
|
||||
const text = textarea.value;
|
||||
textarea.value = text.substring(0, start) + '\n' + text.substring(end);
|
||||
state.autoCompleteValue = textarea.value;
|
||||
// 更新光标位置
|
||||
textarea.selectionStart = textarea.selectionEnd = start + 1;
|
||||
} else {
|
||||
fnSendMML();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取网元网元列表
|
||||
useNeInfoStore()
|
||||
@@ -456,15 +510,16 @@ onMounted(() => {
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
if (res.data.length > 0) {
|
||||
let arr: Record<string, any>[] = [];
|
||||
res.data.forEach(i => {
|
||||
if (i.neType === 'OMC') {
|
||||
arr.push({ value: i.neId, label: i.neName });
|
||||
}
|
||||
});
|
||||
neOptions.value = arr;
|
||||
if (arr.length > 0) {
|
||||
state.neId = arr[0].value;
|
||||
// 获取可用OMC
|
||||
const omcArr = useNeInfoStore().getNeSelectOtions.find(
|
||||
item => item.value === 'OMC'
|
||||
);
|
||||
if (omcArr) {
|
||||
neOptions.value = omcArr.children;
|
||||
}
|
||||
// 是否有可选项
|
||||
if (neOptions.value.length > 0) {
|
||||
state.neId = neOptions.value[0].value;
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
}
|
||||
@@ -558,7 +613,10 @@ onMounted(() => {
|
||||
:validate-on-rule-change="false"
|
||||
:validateTrigger="[]"
|
||||
>
|
||||
<a-form-item :label="t('views.mmlManage.cmdQuickEntry')">
|
||||
<a-form-item
|
||||
:label="t('views.mmlManage.cmdQuickEntry')"
|
||||
:help="t('views.mmlManage.cmdQuickEntryHelp')"
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="state.autoCompleteValue"
|
||||
:dropdown-match-select-width="500"
|
||||
@@ -567,6 +625,7 @@ onMounted(() => {
|
||||
@search="fnAutoCompleteSearch"
|
||||
@select="fnAutoCompleteSelect"
|
||||
@change="fnAutoCompleteChange"
|
||||
@keydown="fnAutoCompleteKeydown"
|
||||
>
|
||||
<a-textarea :placeholder="t('common.inputPlease')" auto-size />
|
||||
</a-auto-complete>
|
||||
@@ -685,10 +744,9 @@ onMounted(() => {
|
||||
</template>
|
||||
|
||||
<CodemirrorEdite
|
||||
v-model:value="state.mmlCmdLog"
|
||||
:value="state.mmlCmdLog"
|
||||
:disabled="true"
|
||||
:editor-style="{ height: '500px !important' }"
|
||||
:placeholder="t('views.mmlManage.cmdAwait')"
|
||||
height="500px"
|
||||
></CodemirrorEdite>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
@@ -18,7 +18,7 @@ let neOptions = ref<Record<string, any>[]>([]);
|
||||
/**对象信息状态类型 */
|
||||
type StateType = {
|
||||
/**网元ID */
|
||||
neId: string;
|
||||
neId: string | undefined;
|
||||
/**命令数据 tree */
|
||||
mmlTreeData: any[];
|
||||
/**命令选中 */
|
||||
@@ -37,7 +37,7 @@ type StateType = {
|
||||
|
||||
/**对象信息状态 */
|
||||
let state: StateType = reactive({
|
||||
neId: '',
|
||||
neId: undefined,
|
||||
mmlTreeData: [],
|
||||
mmlSelect: {
|
||||
title: '',
|
||||
@@ -108,6 +108,13 @@ function fnSendMML() {
|
||||
if (state.from.sendLoading) {
|
||||
return;
|
||||
}
|
||||
if (!state.neId) {
|
||||
message.warning({
|
||||
content: t('views.mmlManage.udmOpesrate.noUDM'),
|
||||
duration: 5,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let cmdArr: string[] = [];
|
||||
const operation = state.mmlSelect.operation;
|
||||
@@ -170,31 +177,47 @@ function fnSendMML() {
|
||||
|
||||
// 发送
|
||||
state.from.sendLoading = true;
|
||||
sendMMlByUDM(state.neId, cmdArr).then(res => {
|
||||
state.from.sendLoading = false;
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
let resultArr = res.data;
|
||||
for (let i = 0; i < resultArr.length; i++) {
|
||||
const str = resultArr[i];
|
||||
const logStr = str.replace(/(\r\n|\n)/g, '\n');
|
||||
const cmdStr = cmdArr[i];
|
||||
state.mmlCmdLog += `${cmdStr}\n${logStr}\n`;
|
||||
sendMMlByUDM(state.neId, cmdArr)
|
||||
.then(res => {
|
||||
state.from.sendLoading = false;
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
let resultArr = res.data;
|
||||
for (let i = 0; i < resultArr.length; i++) {
|
||||
const str = resultArr[i];
|
||||
const logStr = str.replace(/(\r\n|\n)/g, '\n');
|
||||
const cmdStr = cmdArr[i];
|
||||
state.mmlCmdLog += `${cmdStr}\n${logStr}\n`;
|
||||
}
|
||||
} else {
|
||||
state.mmlCmdLog += `${res.msg}\n`;
|
||||
}
|
||||
} else {
|
||||
state.mmlCmdLog += `${res.msg}\n`;
|
||||
}
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
// 控制台滚动底部
|
||||
const container = document.getElementsByClassName('cm-scroller')[0];
|
||||
if (container) {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**上传变更 */
|
||||
function fnUpload(up: UploadRequestOption, name: string) {
|
||||
const neId = state.neId;
|
||||
if (!neId) {
|
||||
message.warning({
|
||||
content: t('views.mmlManage.udmOpesrate.noUDM'),
|
||||
duration: 5,
|
||||
});
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.mmlManage.uploadFileTip'),
|
||||
onOk() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
state.from.uploadLoading = true;
|
||||
uploadFileToNE('UDM', state.neId, up.file as File, 5)
|
||||
uploadFileToNE('UDM', neId, up.file as File, 5)
|
||||
.then(res => {
|
||||
// 文件转存
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
@@ -300,6 +323,15 @@ function ruleVerification(
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'file':
|
||||
if (filter) {
|
||||
const arr: string[] = filter.split(',');
|
||||
const itemArr = arr.filter(item => value.endsWith(item));
|
||||
if (itemArr.length === 0) {
|
||||
return [false, t('views.mmlManage.requireFile', { display })];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(t('views.mmlManage.requireUn', { display }), type);
|
||||
@@ -449,6 +481,28 @@ function fnAutoCompleteChange(value: any, _: any) {
|
||||
}
|
||||
}
|
||||
|
||||
/**自动完成按键触发 */
|
||||
function fnAutoCompleteKeydown(evt: any) {
|
||||
if (evt.key === 'Enter') {
|
||||
// 阻止默认的换行行为
|
||||
evt.preventDefault();
|
||||
// 按下 Shift + Enter 键时换行
|
||||
if (evt.shiftKey) {
|
||||
// 插入换行符
|
||||
const textarea = evt.target;
|
||||
const start = textarea.selectionStart;
|
||||
const end = textarea.selectionEnd;
|
||||
const text = textarea.value;
|
||||
textarea.value = text.substring(0, start) + '\n' + text.substring(end);
|
||||
state.autoCompleteValue = textarea.value;
|
||||
// 更新光标位置
|
||||
textarea.selectionStart = textarea.selectionEnd = start + 1;
|
||||
} else {
|
||||
fnSendMML();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取网元网元列表
|
||||
useNeInfoStore()
|
||||
@@ -456,15 +510,16 @@ onMounted(() => {
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
if (res.data.length > 0) {
|
||||
let arr: Record<string, any>[] = [];
|
||||
res.data.forEach(i => {
|
||||
if (i.neType === 'UDM') {
|
||||
arr.push({ value: i.neId, label: i.neName });
|
||||
}
|
||||
});
|
||||
neOptions.value = arr;
|
||||
if (arr.length > 0) {
|
||||
state.neId = arr[0].value;
|
||||
// 获取可用UDM
|
||||
const udmArr = useNeInfoStore().getNeSelectOtions.find(
|
||||
item => item.value === 'UDM'
|
||||
);
|
||||
if (udmArr) {
|
||||
neOptions.value = udmArr.children;
|
||||
}
|
||||
// 是否有可选项
|
||||
if (neOptions.value.length > 0) {
|
||||
state.neId = neOptions.value[0].value;
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
} else {
|
||||
@@ -563,7 +618,10 @@ onMounted(() => {
|
||||
:validate-on-rule-change="false"
|
||||
:validateTrigger="[]"
|
||||
>
|
||||
<a-form-item :label="t('views.mmlManage.cmdQuickEntry')">
|
||||
<a-form-item
|
||||
:label="t('views.mmlManage.cmdQuickEntry')"
|
||||
:help="t('views.mmlManage.cmdQuickEntryHelp')"
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="state.autoCompleteValue"
|
||||
:dropdown-match-select-width="500"
|
||||
@@ -572,6 +630,7 @@ onMounted(() => {
|
||||
@search="fnAutoCompleteSearch"
|
||||
@select="fnAutoCompleteSelect"
|
||||
@change="fnAutoCompleteChange"
|
||||
@keydown="fnAutoCompleteKeydown"
|
||||
>
|
||||
<a-textarea :placeholder="t('common.inputPlease')" auto-size />
|
||||
</a-auto-complete>
|
||||
@@ -690,10 +749,9 @@ onMounted(() => {
|
||||
</template>
|
||||
|
||||
<CodemirrorEdite
|
||||
v-model:value="state.mmlCmdLog"
|
||||
:value="state.mmlCmdLog"
|
||||
:disabled="true"
|
||||
:editor-style="{ height: '500px !important' }"
|
||||
:placeholder="t('views.mmlManage.cmdAwait')"
|
||||
height="500px"
|
||||
></CodemirrorEdite>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
25
src/views/monitor/cache/info.vue
vendored
25
src/views/monitor/cache/info.vue
vendored
@@ -16,7 +16,7 @@ import { getCache } from '@/api/monitor/cache';
|
||||
import { reactive, ref, onMounted } from 'vue';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
const { t } = useI18n();
|
||||
const { t, currentLocale } = useI18n();
|
||||
|
||||
echarts.use([
|
||||
ToolboxComponent,
|
||||
@@ -77,7 +77,11 @@ let cache: CacheType = reactive({
|
||||
function commandStatsChart() {
|
||||
const commandStatsDom = document.getElementById('commandstats');
|
||||
if (!commandStatsDom) return;
|
||||
const commandStatsEchart = echarts.init(commandStatsDom);
|
||||
const locale = currentLocale.value.split("_")[0]
|
||||
const commandStatsEchart = echarts.init(commandStatsDom, 'light', {
|
||||
// https://github.com/apache/echarts/tree/release/src/i18n 取值langEN.ts ==> EN
|
||||
locale: locale.toUpperCase(),
|
||||
});
|
||||
const option: echarts.ComposeOption<
|
||||
| ToolboxComponentOption
|
||||
| TooltipComponentOption
|
||||
@@ -126,9 +130,15 @@ function commandStatsChart() {
|
||||
],
|
||||
};
|
||||
commandStatsEchart.setOption(option);
|
||||
window.addEventListener('resize', function () {
|
||||
commandStatsEchart.resize();
|
||||
|
||||
// 创建 ResizeObserver 实例
|
||||
var observer = new ResizeObserver(entries => {
|
||||
if (commandStatsEchart) {
|
||||
commandStatsEchart.resize();
|
||||
}
|
||||
});
|
||||
// 监听元素大小变化
|
||||
observer.observe(commandStatsDom);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@@ -152,7 +162,7 @@ onMounted(() => {
|
||||
<template>
|
||||
<PageContainer :loading="loading">
|
||||
<a-card
|
||||
title="基本信息"
|
||||
:title="t('views.monitor.cacheInfo.baseInfo')"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', padding: 0 }"
|
||||
>
|
||||
@@ -207,7 +217,10 @@ onMounted(() => {
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
|
||||
<a-card :title="t('views.monitor.cacheInfo.commandstats')" :bordered="false">
|
||||
<a-card
|
||||
:title="t('views.monitor.cacheInfo.commandstats')"
|
||||
:bordered="false"
|
||||
>
|
||||
<div id="commandstats" style="height: 400px; width: 100%"></div>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { reactive, onMounted, ref, onBeforeUnmount } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { listNe, stateNe } from '@/api/ne/ne';
|
||||
import { listAllNeInfo, stateNeInfo } from '@/api/ne/neInfo';
|
||||
import { message } from 'ant-design-vue/lib';
|
||||
import { randerGroph, switchLayout } from './graph';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
@@ -27,16 +27,15 @@ function fnRanderData() {
|
||||
graphG6.value = randerGroph(t, graphG6Dom.value, graphG6Data);
|
||||
}
|
||||
|
||||
/**网元状态定时器 */
|
||||
const stateTimeout = ref<any>(null);
|
||||
/**网元状态调度器 */
|
||||
const interval10s = ref<any>(null);
|
||||
|
||||
/**查询网元状态 */
|
||||
async function fnGetState() {
|
||||
clearTimeout(stateTimeout.value);
|
||||
for (const node of graphG6Data.nodes) {
|
||||
const ne = node.info;
|
||||
// if (ne.neType === 'OMC') continue;
|
||||
const result = await stateNe(ne.neType, ne.neId);
|
||||
const result = await stateNeInfo(ne.neType, ne.neId);
|
||||
if (result.code === RESULT_CODE_SUCCESS) {
|
||||
ne.serverState = result.data;
|
||||
ne.serverState.refreshTime = parseDateToStr(
|
||||
@@ -48,12 +47,11 @@ async function fnGetState() {
|
||||
graphG6.value.setItemState(neShape, 'neState', ne.serverState.online);
|
||||
}
|
||||
}
|
||||
stateTimeout.value = setTimeout(() => fnGetState(), 30_000);
|
||||
}
|
||||
|
||||
/**查询全部网元数据列表 */
|
||||
function fnGetList(refresh: boolean = false) {
|
||||
listNe({
|
||||
listAllNeInfo({
|
||||
bandStatus: false,
|
||||
})
|
||||
.then(res => {
|
||||
@@ -149,6 +147,9 @@ function fnGetList(refresh: boolean = false) {
|
||||
}
|
||||
fnRanderData();
|
||||
fnGetState();
|
||||
interval10s.value = setInterval(() => {
|
||||
fnGetState(); // 获取网元状态
|
||||
}, 10_000);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -158,7 +159,7 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearTimeout(stateTimeout.value);
|
||||
clearInterval(interval10s.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
RESULT_CODE_ERROR,
|
||||
RESULT_CODE_SUCCESS,
|
||||
} from '@/constants/result-constants';
|
||||
import { listNe } from '@/api/ne/ne';
|
||||
import { listAllNeInfo } from '@/api/ne/neInfo';
|
||||
import { message } from 'ant-design-vue/lib';
|
||||
import { getGraphData } from '@/api/monitor/topology';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
nodeImageAnimateState,
|
||||
nodeRectAnimateState,
|
||||
} from '../topologyBuild/hooks/registerNode';
|
||||
import useNeOptions from './useNeOptions';
|
||||
import useNeOptions from '@/views/ne/neInfo/hooks/useNeOptions';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
const { t } = useI18n();
|
||||
@@ -97,7 +97,7 @@ const graphNodeMenu = new Menu({
|
||||
> ${t('views.configManage.neManage.stop')}
|
||||
</div>
|
||||
<div id="log" style="cursor: pointer; margin-bottom: 4px;">
|
||||
> ${t('views.monitor.topology.viewLogFile')}
|
||||
> ${t('views.configManage.neManage.log')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -240,7 +240,7 @@ function handleRanderGraph(
|
||||
function fnGraphDataLoad(reload: boolean = false) {
|
||||
Promise.all([
|
||||
getGraphData(graphState.group),
|
||||
listNe({
|
||||
listAllNeInfo({
|
||||
bandStatus: false,
|
||||
}),
|
||||
])
|
||||
@@ -328,16 +328,18 @@ function fnGraphDataLoad(reload: boolean = false) {
|
||||
} else {
|
||||
handleRanderGraph(graphG6Dom.value, graphState.data);
|
||||
}
|
||||
fnGetState(); // 获取网元状态
|
||||
fnGetState();
|
||||
interval10s.value = setInterval(() => {
|
||||
fnGetState(); // 获取网元状态
|
||||
}, 10_000);
|
||||
});
|
||||
}
|
||||
|
||||
/**网元状态定时器 */
|
||||
const stateTimeout = ref<any>(null);
|
||||
/**网元状态调度器 */
|
||||
const interval10s = ref<any>(null);
|
||||
|
||||
/**查询网元状态 */
|
||||
async function fnGetState() {
|
||||
clearTimeout(stateTimeout.value);
|
||||
// 获取节点状态
|
||||
for (const node of graphState.data.nodes) {
|
||||
if (notNeNodes.includes(node.id)) continue;
|
||||
@@ -352,8 +354,6 @@ async function fnGetState() {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
stateTimeout.value = setTimeout(() => fnGetState(), 30_000);
|
||||
}
|
||||
|
||||
/**接收数据后回调 */
|
||||
@@ -450,7 +450,7 @@ function wsMessage(res: Record<string, any>) {
|
||||
);
|
||||
}
|
||||
if (neS && notNeNodes.includes(edgeTarget)) {
|
||||
graphG6.value.setItemState(edge.id, 'line-dash', neS.neState.online);
|
||||
graphG6.value.setItemState(edge.id, 'line-dash', neS.neState.online);
|
||||
}
|
||||
if (neT && notNeNodes.includes(edgeSource)) {
|
||||
graphG6.value.setItemState(edge.id, 'line-dash', neT.neState.online);
|
||||
@@ -472,7 +472,7 @@ onMounted(() => {
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
ws.close();
|
||||
clearTimeout(stateTimeout.value);
|
||||
clearInterval(interval10s.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
838
src/views/ne/neHost/index.vue
Normal file
838
src/views/ne/neHost/index.vue
Normal file
@@ -0,0 +1,838 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import { Form, Modal, message } from 'ant-design-vue/lib';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import {
|
||||
addNeHost,
|
||||
delNeHost,
|
||||
getNeHost,
|
||||
listNeHost,
|
||||
testNeHost,
|
||||
updateNeHost,
|
||||
} from '@/api/ne/neHost';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
const { getDict } = useDictStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**主机类型 */
|
||||
neHostType: DictType[];
|
||||
/**分组 */
|
||||
neHostGroupId: DictType[];
|
||||
/**认证模式 */
|
||||
neHostAuthMode: DictType[];
|
||||
} = reactive({
|
||||
neHostType: [],
|
||||
neHostGroupId: [],
|
||||
neHostAuthMode: [],
|
||||
});
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**主机类型 */
|
||||
hostType: undefined,
|
||||
/**分组 */
|
||||
groupId: undefined,
|
||||
/**名称 */
|
||||
title: '',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
hostType: undefined,
|
||||
groupId: undefined,
|
||||
title: '',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
seached: true,
|
||||
data: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'hostId',
|
||||
align: 'center',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neHost.hostType'),
|
||||
dataIndex: 'hostType',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neHost.groupId'),
|
||||
dataIndex: 'groupId',
|
||||
key: 'groupId',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neHost.title'),
|
||||
dataIndex: 'title',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neHost.addr'),
|
||||
dataIndex: 'addr',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neHost.port'),
|
||||
dataIndex: 'port',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neHost.user'),
|
||||
dataIndex: 'user',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neHost.authMode'),
|
||||
dataIndex: 'authMode',
|
||||
key: 'authMode',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neHost.createTime'),
|
||||
dataIndex: 'createTime',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'hostId',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**查询信息列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
listNeHost(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||
tablePagination.total = res.total;
|
||||
tableState.data = res.rows;
|
||||
if (
|
||||
tablePagination.total <=
|
||||
(queryParams.pageNum - 1) * tablePagination.pageSize &&
|
||||
queryParams.pageNum !== 1
|
||||
) {
|
||||
tableState.loading = false;
|
||||
fnGetList(queryParams.pageNum - 1);
|
||||
}
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**新增框或修改框是否显示 */
|
||||
visibleByEdit: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
visibleByEdit: false,
|
||||
title: '信息',
|
||||
from: {
|
||||
hostId: undefined,
|
||||
hostType: 'ssh',
|
||||
groupId: '0',
|
||||
title: '',
|
||||
addr: '',
|
||||
port: 22,
|
||||
user: '',
|
||||
authMode: '0',
|
||||
password: '',
|
||||
privateKey: '',
|
||||
passPhrase: '',
|
||||
remark: '',
|
||||
},
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**对话框内表单属性和校验规则 */
|
||||
const modalStateFrom = Form.useForm(
|
||||
modalState.from,
|
||||
reactive({
|
||||
title: [
|
||||
{
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 50,
|
||||
message: t('views.ne.neHost.titlePlease'),
|
||||
},
|
||||
],
|
||||
addr: [
|
||||
{
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 128,
|
||||
message: t('views.ne.neHost.addrPlease'),
|
||||
},
|
||||
],
|
||||
port: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.ne.neHost.portPlease'),
|
||||
},
|
||||
],
|
||||
user: [
|
||||
{
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 50,
|
||||
message: t('views.ne.neHost.userPlease'),
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 128,
|
||||
message: t('views.ne.neHost.passwordPlease'),
|
||||
},
|
||||
],
|
||||
privateKey: [
|
||||
{
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 128,
|
||||
message: t('views.ne.neHost.privateKeyPlease'),
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param roleId 角色编号ID, 不传为新增
|
||||
*/
|
||||
function fnModalVisibleByEdit(roleId?: string | number) {
|
||||
if (!roleId) {
|
||||
modalStateFrom.resetFields();
|
||||
modalState.title = t('views.ne.neHost.addTitle');
|
||||
modalState.visibleByEdit = true;
|
||||
} else {
|
||||
if (modalState.confirmLoading) return;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
modalState.confirmLoading = true;
|
||||
// 查询角色详细同时根据角色ID查询菜单下拉树结构
|
||||
getNeHost(roleId).then(res => {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
modalState.from = Object.assign(modalState.from, res.data);
|
||||
modalState.title = t('views.ne.neHost.editTitle');
|
||||
modalState.visibleByEdit = true;
|
||||
} else {
|
||||
message.error(t('common.getInfoFail'), 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalOk() {
|
||||
if (modalState.confirmLoading) return;
|
||||
const form = toRaw(modalState.from);
|
||||
const validateArr = ['title', 'addr', 'port', 'user'];
|
||||
if (form.authMode === '0') {
|
||||
validateArr.push('password');
|
||||
} else {
|
||||
validateArr.push('privateKey');
|
||||
}
|
||||
modalStateFrom
|
||||
.validate(validateArr)
|
||||
.then(() => {
|
||||
modalState.confirmLoading = true;
|
||||
const neHost = form.hostId ? updateNeHost(form) : addNeHost(form);
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
neHost
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.operateOk'),
|
||||
duration: 2,
|
||||
});
|
||||
fnModalCancel();
|
||||
fnGetList(1);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.visibleByEdit = false;
|
||||
modalStateFrom.resetFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元主机删除
|
||||
* @param hostId 网元主机编号ID
|
||||
*/
|
||||
function fnRecordDelete(hostId: string) {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.ne.neHost.delTip', { num: hostId }),
|
||||
onOk() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
delNeHost(hostId).then(res => {
|
||||
hide();
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.operateOk'),
|
||||
duration: 2,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出测试连接
|
||||
*/
|
||||
function fnModalTest() {
|
||||
if (modalState.confirmLoading) return;
|
||||
const form = toRaw(modalState.from);
|
||||
const validateArr = ['title', 'addr', 'port', 'user'];
|
||||
if (form.authMode === '0') {
|
||||
validateArr.push('password');
|
||||
} else {
|
||||
validateArr.push('privateKey');
|
||||
}
|
||||
modalStateFrom
|
||||
.validate(validateArr)
|
||||
.then(() => {
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
testNeHost(form)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('views.ne.neHost.testOk'),
|
||||
duration: 2,
|
||||
});
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([
|
||||
getDict('ne_host_type'),
|
||||
getDict('ne_host_groupId'),
|
||||
getDict('ne_host_authMode'),
|
||||
]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.neHostType = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.neHostGroupId = resArr[1].value;
|
||||
}
|
||||
if (resArr[2].status === 'fulfilled') {
|
||||
dict.neHostAuthMode = resArr[2].value;
|
||||
}
|
||||
});
|
||||
// 获取列表数据
|
||||
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="t('views.ne.neHost.hostType')" name="hostType">
|
||||
<a-select
|
||||
v-model:value="queryParams.hostType"
|
||||
allow-clear
|
||||
:placeholder="t('common.selectPlease')"
|
||||
:options="dict.neHostType"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.ne.neHost.groupId')" name="groupId">
|
||||
<a-select
|
||||
v-model:value="queryParams.groupId"
|
||||
allow-clear
|
||||
:placeholder="t('common.selectPlease')"
|
||||
:options="dict.neHostGroupId"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.ne.neHost.title')" name="title">
|
||||
<a-input
|
||||
v-model:value="queryParams.title"
|
||||
:allow-clear="true"
|
||||
: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(1)">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
{{ t('common.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-button type="primary" @click.prevent="fnModalVisibleByEdit()">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
{{ t('common.addText') }}
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.searchBarText') }}</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
:checked-children="t('common.switch.show')"
|
||||
:un-checked-children="t('common.switch.hide')"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.sizeText') }}</template>
|
||||
<a-dropdown trigger="click" placement="bottomRight">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">
|
||||
{{ t('common.size.default') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="middle">
|
||||
{{ t('common.size.middle') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="small">
|
||||
{{ t('common.size.small') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="hostId"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: tableColumns.length * 120 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'groupId'">
|
||||
<DictTag :options="dict.neHostGroupId" :value="record.groupId" />
|
||||
</template>
|
||||
<template v-if="column.key === 'authMode'">
|
||||
<DictTag :options="dict.neHostAuthMode" :value="record.authMode" />
|
||||
</template>
|
||||
<template v-if="column.key === 'hostId'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.editText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByEdit(record.hostId)"
|
||||
>
|
||||
<template #icon><FormOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.deleteText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordDelete(record.hostId)"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<!-- 新增框或修改框 -->
|
||||
<a-modal
|
||||
width="800px"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:visible="modalState.visibleByEdit"
|
||||
:title="modalState.title"
|
||||
:confirm-loading="modalState.confirmLoading"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form
|
||||
name="modalStateFromByEdit"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 6 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.hostType')"
|
||||
name="hostType"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="modalState.from.hostType"
|
||||
default-value="ssh"
|
||||
:options="dict.neHostType"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.ne.neHost.groupId')" name="groupId">
|
||||
<a-select
|
||||
v-model:value="modalState.from.groupId"
|
||||
default-value="0"
|
||||
:options="dict.neHostGroupId"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.title')"
|
||||
name="title"
|
||||
v-bind="modalStateFrom.validateInfos.title"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.title"
|
||||
allow-clear
|
||||
:maxlength="50"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.addr')"
|
||||
name="addr"
|
||||
v-bind="modalStateFrom.validateInfos.addr"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.addr"
|
||||
allow-clear
|
||||
:maxlength="128"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.port')"
|
||||
name="port"
|
||||
v-bind="modalStateFrom.validateInfos.port"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="modalState.from.port"
|
||||
:min="10"
|
||||
:max="65535"
|
||||
:step="1"
|
||||
style="width: 100%"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.user')"
|
||||
name="user"
|
||||
v-bind="modalStateFrom.validateInfos.user"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.user"
|
||||
allow-clear
|
||||
:maxlength="50"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.authMode')"
|
||||
name="authMode"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="modalState.from.authMode"
|
||||
default-value="0"
|
||||
:options="dict.neHostAuthMode"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item
|
||||
v-if="modalState.from.authMode === '0'"
|
||||
:label="t('views.ne.neHost.password')"
|
||||
name="password"
|
||||
v-bind="modalStateFrom.validateInfos.password"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="modalState.from.password"
|
||||
:maxlength="128"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<template v-if="modalState.from.authMode === '1'">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.privateKey')"
|
||||
name="privateKey"
|
||||
v-bind="modalStateFrom.validateInfos.privateKey"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.privateKey"
|
||||
:auto-size="{ minRows: 4, maxRows: 6 }"
|
||||
:maxlength="3000"
|
||||
:show-count="true"
|
||||
:placeholder="t('views.ne.neHost.privateKeyPlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.passPhrase')"
|
||||
name="passPhrase"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="modalState.from.passPhrase"
|
||||
:maxlength="128"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.remark')"
|
||||
name="remark"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.remark"
|
||||
:auto-size="{ minRows: 1, maxRows: 6 }"
|
||||
:maxlength="450"
|
||||
:show-count="true"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.test')"
|
||||
name="test"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-button
|
||||
type="primary"
|
||||
shape="round"
|
||||
@click="fnModalTest"
|
||||
:loading="modalState.confirmLoading"
|
||||
>
|
||||
<template #icon><LinkOutlined /></template>
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
597
src/views/ne/neHostCommand/index.vue
Normal file
597
src/views/ne/neHostCommand/index.vue
Normal file
@@ -0,0 +1,597 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import { Form, Modal, message } from 'ant-design-vue/lib';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import {
|
||||
addNeHostCmd,
|
||||
delNeHostCmd,
|
||||
getNeHostCmd,
|
||||
listNeHostCmd,
|
||||
updateNeHostCmd,
|
||||
} from '@/api/ne/neHostCmd';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
const { getDict } = useDictStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**分组 */
|
||||
neHostCmdGroupId: DictType[];
|
||||
} = reactive({
|
||||
neHostCmdGroupId: [],
|
||||
});
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**命令类型 */
|
||||
cmdType: '',
|
||||
/**分组 */
|
||||
groupId: undefined,
|
||||
/**名称 */
|
||||
title: '',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
hostType: '',
|
||||
groupId: undefined,
|
||||
title: '',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
seached: true,
|
||||
data: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'cmdId',
|
||||
align: 'center',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neHostCmd.cmdType'),
|
||||
dataIndex: 'cmdType',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neHostCmd.groupId'),
|
||||
dataIndex: 'groupId',
|
||||
key: 'groupId',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neHostCmd.title'),
|
||||
dataIndex: 'title',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neHostCmd.createTime'),
|
||||
dataIndex: 'createTime',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'cmdId',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**查询备份信息列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
listNeHostCmd(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||
tablePagination.total = res.total;
|
||||
tableState.data = res.rows;
|
||||
if (
|
||||
tablePagination.total <=
|
||||
(queryParams.pageNum - 1) * tablePagination.pageSize &&
|
||||
queryParams.pageNum !== 1
|
||||
) {
|
||||
tableState.loading = false;
|
||||
fnGetList(queryParams.pageNum - 1);
|
||||
}
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**新增框或修改框是否显示 */
|
||||
visibleByEdit: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
visibleByEdit: false,
|
||||
title: '信息',
|
||||
from: {
|
||||
cmdId: undefined,
|
||||
cmdType: '',
|
||||
groupId: '0',
|
||||
title: '',
|
||||
command: '',
|
||||
remark: '',
|
||||
},
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**对话框内表单属性和校验规则 */
|
||||
const modalStateFrom = Form.useForm(
|
||||
modalState.from,
|
||||
reactive({
|
||||
title: [
|
||||
{
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 50,
|
||||
message: t('views.ne.neHostCmd.titlePlease'),
|
||||
},
|
||||
],
|
||||
command: [
|
||||
{
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 50,
|
||||
message: t('views.ne.neHostCmd.commandPlease'),
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param roleId 角色编号ID, 不传为新增
|
||||
*/
|
||||
function fnModalVisibleByEdit(roleId?: string | number) {
|
||||
if (!roleId) {
|
||||
modalStateFrom.resetFields();
|
||||
modalState.title = t('views.ne.neHostCmd.addTitle');
|
||||
modalState.visibleByEdit = true;
|
||||
} else {
|
||||
if (modalState.confirmLoading) return;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
modalState.confirmLoading = true;
|
||||
// 查询角色详细同时根据角色ID查询菜单下拉树结构
|
||||
getNeHostCmd(roleId).then(res => {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
modalState.from = Object.assign(modalState.from, res.data);
|
||||
modalState.title = t('views.ne.neHostCmd.editTitle');
|
||||
modalState.visibleByEdit = true;
|
||||
} else {
|
||||
message.error(t('common.getInfoFail'), 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalOk() {
|
||||
modalStateFrom
|
||||
.validate()
|
||||
.then(() => {
|
||||
modalState.confirmLoading = true;
|
||||
const from = toRaw(modalState.from);
|
||||
const neHost = from.cmdId ? updateNeHostCmd(from) : addNeHostCmd(from);
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
neHost
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.operateOk'),
|
||||
duration: 2,
|
||||
});
|
||||
fnModalCancel();
|
||||
fnGetList(1);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.visibleByEdit = false;
|
||||
modalStateFrom.resetFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元主机删除
|
||||
* @param cmdId 网元主机编号ID
|
||||
*/
|
||||
function fnRecordDelete(cmdId: string) {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.ne.neHostCmd.delTip', { num: cmdId }),
|
||||
onOk() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
delNeHostCmd(cmdId).then(res => {
|
||||
hide();
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.operateOk'),
|
||||
duration: 2,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('ne_host_cmd_groupId')]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.neHostCmdGroupId = resArr[0].value;
|
||||
}
|
||||
});
|
||||
// 获取列表数据
|
||||
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="t('views.ne.neHostCmd.cmdType')"
|
||||
name="cmdType"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="queryParams.cmdType"
|
||||
:allow-clear="true"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHostCmd.groupId')"
|
||||
name="groupId"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="queryParams.groupId"
|
||||
allow-clear
|
||||
:placeholder="t('common.selectPlease')"
|
||||
:options="dict.neHostCmdGroupId"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.ne.neHostCmd.title')" name="title">
|
||||
<a-input
|
||||
v-model:value="queryParams.title"
|
||||
:allow-clear="true"
|
||||
: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(1)">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
{{ t('common.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-button type="primary" @click.prevent="fnModalVisibleByEdit()">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
{{ t('common.addText') }}
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.searchBarText') }}</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
:checked-children="t('common.switch.show')"
|
||||
:un-checked-children="t('common.switch.hide')"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.sizeText') }}</template>
|
||||
<a-dropdown trigger="click" placement="bottomRight">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">
|
||||
{{ t('common.size.default') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="middle">
|
||||
{{ t('common.size.middle') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="small">
|
||||
{{ t('common.size.small') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="cmdId"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: tableColumns.length * 120 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'cmdId'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.editText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByEdit(record.cmdId)"
|
||||
>
|
||||
<template #icon><FormOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.deleteText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordDelete(record.cmdId)"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<!-- 新增框或修改框 -->
|
||||
<a-modal
|
||||
width="800px"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:visible="modalState.visibleByEdit"
|
||||
:title="modalState.title"
|
||||
:confirm-loading="modalState.confirmLoading"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form
|
||||
name="modalStateFromByEdit"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 6 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHostCmd.cmdType')"
|
||||
name="cmdType"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.cmdType"
|
||||
allow-clear
|
||||
:maxlength="50"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHostCmd.groupId')"
|
||||
name="groupId"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="modalState.from.groupId"
|
||||
default-value="0"
|
||||
:options="dict.neHostCmdGroupId"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHostCmd.title')"
|
||||
name="title"
|
||||
v-bind="modalStateFrom.validateInfos.title"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.title"
|
||||
allow-clear
|
||||
:maxlength="50"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHostCmd.command')"
|
||||
name="command"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.command"
|
||||
:auto-size="{ minRows: 4, maxRows: 6 }"
|
||||
:maxlength="500"
|
||||
:show-count="true"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHostCmd.remark')"
|
||||
name="remark"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.remark"
|
||||
:auto-size="{ minRows: 4, maxRows: 6 }"
|
||||
:maxlength="450"
|
||||
:show-count="true"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
701
src/views/ne/neInfo/components/EditModal.vue
Normal file
701
src/views/ne/neInfo/components/EditModal.vue
Normal file
@@ -0,0 +1,701 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw, watch } from 'vue';
|
||||
import { message, Form } from 'ant-design-vue/lib';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import { getNeInfo, addNeInfo, updateNeInfo } from '@/api/ne/neInfo';
|
||||
import { NE_TYPE_LIST } from '@/constants/ne-constants';
|
||||
import { testNeHost } from '@/api/ne/neHost';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
const { getDict } = useDictStore();
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(['ok', 'cancel', 'update:visible']);
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
editId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**主机类型 */
|
||||
neHostType: DictType[];
|
||||
/**分组 */
|
||||
neHostGroupId: DictType[];
|
||||
/**认证模式 */
|
||||
neHostAuthMode: DictType[];
|
||||
} = reactive({
|
||||
neHostType: [],
|
||||
neHostGroupId: [],
|
||||
neHostAuthMode: [],
|
||||
});
|
||||
|
||||
/**
|
||||
* 对话框弹出测试连接
|
||||
*/
|
||||
function fnModalTest(row: Record<string, any>) {
|
||||
if (modalState.confirmLoading) return;
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
testNeHost(row)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: `${row.addr}:${row.port} ${t('views.ne.neHost.testOk')}`,
|
||||
duration: 2,
|
||||
});
|
||||
} else {
|
||||
message.error({
|
||||
content: `${row.addr}:${row.port} ${res.msg}`,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**新增框或修改框是否显示 */
|
||||
visibleByEdit: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
visibleByEdit: false,
|
||||
title: '网元',
|
||||
from: {
|
||||
id: undefined,
|
||||
neId: '001',
|
||||
neType: 'OMC',
|
||||
neName: '',
|
||||
ip: '',
|
||||
port: 3030,
|
||||
pvFlag: 'PNF',
|
||||
rmUid: '4400HX1OMC001',
|
||||
neAddress: '',
|
||||
dn: '',
|
||||
vendorName: '',
|
||||
province: '',
|
||||
// 主机
|
||||
hosts: [
|
||||
{
|
||||
hostId: undefined,
|
||||
hostType: 'ssh',
|
||||
groupId: '1',
|
||||
title: 'SSH_NE_22',
|
||||
addr: '',
|
||||
port: 22,
|
||||
user: 'user',
|
||||
authMode: '0',
|
||||
password: 'user',
|
||||
privateKey: '',
|
||||
passPhrase: '',
|
||||
remark: '',
|
||||
},
|
||||
{
|
||||
hostId: undefined,
|
||||
hostType: 'telnet',
|
||||
groupId: '1',
|
||||
title: 'Telnet_NE_4100',
|
||||
addr: '',
|
||||
port: 4100,
|
||||
user: 'user',
|
||||
authMode: '0',
|
||||
password: 'user',
|
||||
remark: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**对话框内表单属性和校验规则 */
|
||||
const modalStateFrom = Form.useForm(
|
||||
modalState.from,
|
||||
reactive({
|
||||
neType: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入网元类型',
|
||||
},
|
||||
],
|
||||
neId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入网元标识',
|
||||
},
|
||||
],
|
||||
rmUid: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入资源唯一标识',
|
||||
},
|
||||
],
|
||||
ip: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入网元IP地址',
|
||||
},
|
||||
],
|
||||
neName: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入网元名称',
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param editId 网元id, 不传为新增
|
||||
*/
|
||||
function fnModalVisibleByEdit(editId: string) {
|
||||
if (!editId) {
|
||||
modalStateFrom.resetFields();
|
||||
modalState.title = t('views.configManage.neManage.addNe');
|
||||
modalState.visibleByEdit = true;
|
||||
} else {
|
||||
if (modalState.confirmLoading) return;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
modalState.confirmLoading = true;
|
||||
getNeInfo(editId).then(res => {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
modalState.from = Object.assign(modalState.from, res.data);
|
||||
modalState.title = '编辑网元信息';
|
||||
modalState.visibleByEdit = true;
|
||||
} else {
|
||||
message.error(t('common.getInfoFail'), 2);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalOk() {
|
||||
modalStateFrom
|
||||
.validate()
|
||||
.then(e => {
|
||||
modalState.confirmLoading = true;
|
||||
const from = toRaw(modalState.from);
|
||||
const result = from.id ? updateNeInfo(from) : addNeInfo(from);
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
result
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: '操作成功',
|
||||
duration: 3,
|
||||
});
|
||||
// 刷新缓存的网元信息
|
||||
useNeInfoStore().fnRefreshNelist();
|
||||
emit('ok');
|
||||
fnModalCancel();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${t('views.configManage.neManage.operFail')}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.visibleByEdit = false;
|
||||
modalStateFrom.resetFields();
|
||||
emit('cancel');
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单修改网元类型
|
||||
*/
|
||||
function fnNeTypeChange(v: any) {
|
||||
const hostsLen = modalState.from.hosts.length;
|
||||
// 网元默认只含22和4100
|
||||
if (hostsLen === 3 && v !== 'UPF') {
|
||||
modalState.from.hosts.pop();
|
||||
}
|
||||
// UPF标准版本可支持5002
|
||||
if (hostsLen === 2 && v === 'UPF') {
|
||||
modalState.from.hosts.push({
|
||||
hostId: undefined,
|
||||
hostType: 'telnet',
|
||||
groupId: '1',
|
||||
title: 'Telnet_NE_5002',
|
||||
addr: '',
|
||||
port: 5002,
|
||||
user: 'user',
|
||||
authMode: '0',
|
||||
password: 'user',
|
||||
remark: '',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单修改网元IP
|
||||
*/
|
||||
function fnNeIPChange(e: any) {
|
||||
const v = e.target.value;
|
||||
if (v.length < 7) return;
|
||||
for (const host of modalState.from.hosts) {
|
||||
host.addr = v;
|
||||
}
|
||||
}
|
||||
|
||||
/**监听是否显示,初始数据 */
|
||||
watch(
|
||||
() => props.visible,
|
||||
val => {
|
||||
if (val) fnModalVisibleByEdit(props.editId);
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([
|
||||
getDict('ne_host_type'),
|
||||
getDict('ne_host_groupId'),
|
||||
getDict('ne_host_authMode'),
|
||||
]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.neHostType = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.neHostGroupId = resArr[1].value;
|
||||
}
|
||||
if (resArr[2].status === 'fulfilled') {
|
||||
dict.neHostAuthMode = resArr[2].value;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DraggableModal
|
||||
width="800px"
|
||||
:body-style="{ maxHeight: '650px', 'overflow-y': 'auto' }"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:visible="modalState.visibleByEdit"
|
||||
:title="modalState.title"
|
||||
:confirm-loading="modalState.confirmLoading"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form
|
||||
name="modalStateFrom"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 6 }"
|
||||
:labelWrap="true"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.neManage.neType')"
|
||||
name="neType"
|
||||
v-bind="modalStateFrom.validateInfos.neType"
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="modalState.from.neType"
|
||||
:options="NE_TYPE_LIST.map(v => ({ value: v }))"
|
||||
@change="fnNeTypeChange"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="32"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
{{ t('views.configManage.neManage.neTypeTip') }}
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-auto-complete>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.neManage.pvflag')"
|
||||
name="pvFlag"
|
||||
v-bind="modalStateFrom.validateInfos.pvFlag"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="modalState.from.pvFlag"
|
||||
default-value="PNF"
|
||||
>
|
||||
<a-select-opt-group :label="t('views.configManage.neManage.pnf')">
|
||||
<a-select-option value="PNF">PNF</a-select-option>
|
||||
</a-select-opt-group>
|
||||
<a-select-opt-group :label="t('views.configManage.neManage.vnf')">
|
||||
<a-select-option value="VNF">VNF</a-select-option>
|
||||
</a-select-opt-group>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.neManage.neId')"
|
||||
name="neId"
|
||||
v-bind="modalStateFrom.validateInfos.neId"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.neId"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="32"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.neManage.neName')"
|
||||
name="neName"
|
||||
v-bind="modalStateFrom.validateInfos.neName"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.neName"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="64"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.neManage.ip')"
|
||||
name="ip"
|
||||
v-bind="modalStateFrom.validateInfos.ip"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.ip"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="128"
|
||||
@change="fnNeIPChange"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
<div>
|
||||
{{ t('views.ne.neInfo.ipAddr') }}
|
||||
</div>
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.neManage.port')"
|
||||
name="port"
|
||||
v-bind="modalStateFrom.validateInfos.port"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="modalState.from.port"
|
||||
style="width: 100%"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
placeholder="<=65535"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
<div>{{ t('views.configManage.neManage.portTip') }}</div>
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.configManage.neManage.uid')"
|
||||
name="rmUid"
|
||||
v-bind="modalStateFrom.validateInfos.rmUid"
|
||||
:label-col="{ span: 3 }"
|
||||
:labelWrap="true"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.rmUid"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="40"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
<div>
|
||||
{{ t('views.ne.neInfo.rmUID') }}
|
||||
</div>
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.neManage.mac')"
|
||||
name="neAddress"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.neAddress"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="64"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
<div>{{ t('views.configManage.neManage.macTip') }}</div>
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.configManage.neManage.dn')" name="dn">
|
||||
<a-input
|
||||
v-model:value="modalState.from.dn"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="255"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.neManage.vendorName')"
|
||||
name="vendorName"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.vendorName"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="64"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.neManage.province')"
|
||||
name="province"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.province"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="32"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.ne.neInfo.hostConfig') }}
|
||||
</a-divider>
|
||||
|
||||
<!-- 主机连接配置 -->
|
||||
<a-collapse class="collapse" ghost>
|
||||
<a-collapse-panel
|
||||
v-for="host in modalState.from.hosts"
|
||||
:key="host.title"
|
||||
:header="`${host.hostType.toUpperCase()} ${host.port}`"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.ne.neHost.addr')">
|
||||
<a-input
|
||||
v-model:value="host.addr"
|
||||
allow-clear
|
||||
:maxlength="128"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.ne.neHost.port')" name="port">
|
||||
<a-input-number
|
||||
v-model:value="host.port"
|
||||
:min="10"
|
||||
:max="65535"
|
||||
:step="1"
|
||||
style="width: 100%"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.ne.neHost.user')">
|
||||
<a-input
|
||||
v-model:value="host.user"
|
||||
allow-clear
|
||||
:maxlength="50"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.ne.neHost.authMode')">
|
||||
<a-select
|
||||
v-model:value="host.authMode"
|
||||
default-value="0"
|
||||
:options="dict.neHostAuthMode"
|
||||
:disabled="host.hostType === 'telnet'"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item
|
||||
v-if="host.authMode === '0'"
|
||||
:label="t('views.ne.neHost.password')"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="host.password"
|
||||
:maxlength="128"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<template v-if="host.authMode === '1'">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.privateKey')"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="host.privateKey"
|
||||
:auto-size="{ minRows: 4, maxRows: 6 }"
|
||||
:maxlength="3000"
|
||||
:show-count="true"
|
||||
:placeholder="t('views.ne.neHost.privateKeyPlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.passPhrase')"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="host.passPhrase"
|
||||
:maxlength="128"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.remark')"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="host.remark"
|
||||
:auto-size="{ minRows: 1, maxRows: 6 }"
|
||||
:maxlength="450"
|
||||
:show-count="true"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.test')"
|
||||
name="test"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-button
|
||||
type="primary"
|
||||
shape="round"
|
||||
@click="fnModalTest(host)"
|
||||
:loading="modalState.confirmLoading"
|
||||
>
|
||||
<template #icon><LinkOutlined /></template>
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-form>
|
||||
</DraggableModal>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.collapse :deep(.ant-collapse-item) > .ant-collapse-header {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
.collapse-header {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
@@ -1,13 +1,49 @@
|
||||
import { restartNf, stopNf } from '@/api/configManage/neManage';
|
||||
import { restartNf, startNf, stopNf } from '@/api/configManage/neManage';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { Modal, message } from 'ant-design-vue/lib';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { updateNeConfigReload } from '@/api/configManage/configParam';
|
||||
|
||||
export default function useNeOptions() {
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
|
||||
/**
|
||||
* 网元启动
|
||||
* @param row {neName,neType,neId}
|
||||
*/
|
||||
function fnNeStart(row: Record<string, any>) {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.configManage.neManage.totalSure', {
|
||||
msg: row.neName,
|
||||
oper: t('views.configManage.neManage.start'),
|
||||
}),
|
||||
onOk() {
|
||||
const key = 'startNf';
|
||||
message.loading({ content: t('common.loading'), key });
|
||||
startNf(row).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', {
|
||||
msg: t('views.configManage.neManage.start'),
|
||||
}),
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key: key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元重启
|
||||
* @param row {neName,neType,neId}
|
||||
@@ -78,13 +114,54 @@ export default function useNeOptions() {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元重新加载
|
||||
* @param row {neName,neType,neId}
|
||||
*/
|
||||
function fnNeReload(row: Record<string, any>) {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.configManage.neManage.totalSure', {
|
||||
msg: row.neName,
|
||||
oper: t('views.configManage.neManage.reload'),
|
||||
}),
|
||||
onOk() {
|
||||
const key = 'stopNf';
|
||||
message.loading({ content: t('common.loading'), key });
|
||||
updateNeConfigReload(row.neType, row.neId).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', {
|
||||
msg: t('views.configManage.neManage.reload'),
|
||||
}),
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key: key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转网元日志文件页面
|
||||
* @param row {neType,neId}
|
||||
*/
|
||||
function fnNeLogFile(row: Record<string, any>) {
|
||||
router.push(`/logManage/neFile?neType=${row.neType}&neId=${row.neId}`);
|
||||
router.push({
|
||||
name: 'NeFile_2123',
|
||||
query: {
|
||||
neType: row.neType,
|
||||
neId: row.neId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return { fnNeRestart, fnNeStop, fnNeLogFile };
|
||||
return { fnNeStart, fnNeRestart, fnNeStop, fnNeReload, fnNeLogFile };
|
||||
}
|
||||
718
src/views/ne/neInfo/index.vue
Normal file
718
src/views/ne/neInfo/index.vue
Normal file
@@ -0,0 +1,718 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { message, Modal } from 'ant-design-vue/lib';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import EditModal from './components/EditModal.vue';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import { listNeInfo, delNeInfo } from '@/api/ne/neInfo';
|
||||
import { NE_TYPE_LIST } from '@/constants/ne-constants';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import useNeOptions from './hooks/useNeOptions';
|
||||
const { getDict } = useDictStore();
|
||||
const { t } = useI18n();
|
||||
const { fnNeStart, fnNeRestart, fnNeStop, fnNeReload, fnNeLogFile } =
|
||||
useNeOptions();
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**网元信息状态 */
|
||||
neInfoStatus: DictType[];
|
||||
} = reactive({
|
||||
neInfoStatus: [],
|
||||
});
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**网元类型 */
|
||||
neType: '',
|
||||
/**带状态信息 */
|
||||
bandStatus: true,
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
neType: '',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**斑马纹 */
|
||||
striped: boolean;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
striped: false,
|
||||
seached: true,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('views.configManage.neManage.neType'),
|
||||
dataIndex: 'neType',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.configManage.neManage.neId'),
|
||||
dataIndex: 'neId',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.configManage.neManage.uid'),
|
||||
dataIndex: 'rmUid',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.configManage.neManage.neName'),
|
||||
dataIndex: 'neName',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.configManage.neManage.ip'),
|
||||
dataIndex: 'ip',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.configManage.neManage.port'),
|
||||
dataIndex: 'port',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neInfo.state'),
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格斑马纹 */
|
||||
function fnTableStriped(_record: unknown, index: number): any {
|
||||
return tableState.striped && index % 2 === 1 ? 'table-striped' : undefined;
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**新增框或修改框是否显示 */
|
||||
visibleByEdit: boolean;
|
||||
/**新增框或修改框ID */
|
||||
editId: string;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
visibleByEdit: false,
|
||||
editId: '',
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param noticeId 网元id, 不传为新增
|
||||
*/
|
||||
function fnModalVisibleByEdit(row?: Record<string, any>) {
|
||||
if (!row) {
|
||||
modalState.editId = '';
|
||||
} else {
|
||||
modalState.editId = row.id;
|
||||
}
|
||||
modalState.visibleByEdit = !modalState.visibleByEdit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalEditOk() {
|
||||
fnGetList(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalEditCancel() {
|
||||
modalState.editId = '';
|
||||
modalState.visibleByEdit = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录删除
|
||||
* @param id 编号
|
||||
*/
|
||||
function fnRecordDelete(id: string) {
|
||||
if (!id || modalState.confirmLoading) return;
|
||||
let msg = '';
|
||||
if (id === '0') {
|
||||
const neInfo: any = tableState.data.find(
|
||||
(item: any) => item.id === tableState.selectedRowKeys[0]
|
||||
);
|
||||
if (neInfo) {
|
||||
msg = neInfo.neName;
|
||||
}
|
||||
msg = `${msg}... ${tableState.selectedRowKeys.length}`;
|
||||
id = tableState.selectedRowKeys.join(',');
|
||||
} else {
|
||||
const neInfo: any = tableState.data.find((item: any) => item.id === id);
|
||||
if (neInfo) {
|
||||
msg = neInfo.neName;
|
||||
}
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: `确认删除网元名称为【${msg}】的数据项?`,
|
||||
onOk() {
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
delNeInfo(id)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: `操作成功`,
|
||||
duration: 3,
|
||||
});
|
||||
fnGetList(1);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录多项选择
|
||||
*/
|
||||
function fnRecordMore(type: string | number, row: Record<string, any>) {
|
||||
switch (type) {
|
||||
case 'delete':
|
||||
fnRecordDelete(row.id);
|
||||
break;
|
||||
case 'start':
|
||||
fnNeStart(row);
|
||||
break;
|
||||
case 'restart':
|
||||
fnNeRestart(row);
|
||||
break;
|
||||
case 'stop':
|
||||
fnNeStop(row);
|
||||
break;
|
||||
case 'reload':
|
||||
fnNeReload(row);
|
||||
break;
|
||||
case 'log':
|
||||
fnNeLogFile(row);
|
||||
break;
|
||||
default:
|
||||
console.warn(type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**查询列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
listNeInfo(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
tablePagination.total = res.total;
|
||||
// 遍历处理资源情况数值
|
||||
tableState.data = res.rows.map(item => {
|
||||
const neState = item.serverState;
|
||||
let sysCpuUsage = 0;
|
||||
let nfCpuUsage = 0;
|
||||
if (neState.cpu) {
|
||||
nfCpuUsage = neState.cpu.nfCpuUsage;
|
||||
if (nfCpuUsage > 100) {
|
||||
const nfCpu = +(neState.cpu.nfCpuUsage / 100);
|
||||
if (nfCpu > 100) {
|
||||
nfCpuUsage = 100;
|
||||
} else {
|
||||
nfCpuUsage = +nfCpu.toFixed(2);
|
||||
}
|
||||
}
|
||||
|
||||
sysCpuUsage = neState.cpu.sysCpuUsage;
|
||||
if (sysCpuUsage > 100) {
|
||||
const sysCpu = +(neState.cpu.sysCpuUsage / 100);
|
||||
if (sysCpu > 100) {
|
||||
sysCpuUsage = 100;
|
||||
} else {
|
||||
sysCpuUsage = +sysCpu.toFixed(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let sysMemUsage = 0;
|
||||
if (neState.mem) {
|
||||
let men = neState.mem.sysMemUsage;
|
||||
if (men > 100) {
|
||||
men = +(men / 100).toFixed(2);
|
||||
}
|
||||
sysMemUsage = men;
|
||||
}
|
||||
|
||||
let sysDiskUsage = 0;
|
||||
if (neState.disk && Array.isArray(neState.disk.partitionInfo)) {
|
||||
let disks: any[] = neState.disk.partitionInfo;
|
||||
disks = disks.sort((a, b) => +b.used - +a.used);
|
||||
if (disks.length > 0) {
|
||||
const { total, used } = disks[0];
|
||||
sysDiskUsage = +((used / total) * 100).toFixed(2);
|
||||
}
|
||||
}
|
||||
|
||||
Reflect.set(item, 'resoures', {
|
||||
sysDiskUsage,
|
||||
sysMemUsage,
|
||||
sysCpuUsage,
|
||||
nfCpuUsage,
|
||||
});
|
||||
|
||||
return item;
|
||||
});
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('ne_info_status')]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.neInfoStatus = resArr[0].value;
|
||||
}
|
||||
});
|
||||
|
||||
// 刷新缓存的网元信息
|
||||
useNeInfoStore()
|
||||
.fnRefreshNelist()
|
||||
.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="t('views.configManage.neManage.neType')"
|
||||
name="neType "
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="queryParams.neType"
|
||||
:options="NE_TYPE_LIST.map(v => ({ value: v }))"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
{{ t('common.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button type="primary" @click.prevent="fnModalVisibleByEdit()">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
{{ t('common.addText') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
:loading="modalState.confirmLoading"
|
||||
@click.prevent="fnRecordDelete('0')"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
{{ t('common.deleteText') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.searchBarText') }}</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
:checked-children="t('common.switch.show')"
|
||||
:un-checked-children="t('common.switch.hide')"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.tableStripedText') }}</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.striped"
|
||||
:checked-children="t('common.switch.open')"
|
||||
:un-checked-children="t('common.switch.shut')"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.sizeText') }}</template>
|
||||
<a-dropdown trigger="click" placement="bottomRight">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">
|
||||
{{ t('common.size.default') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="middle">
|
||||
{{ t('common.size.middle') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="small">
|
||||
{{ t('common.size.small') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:row-class-name="fnTableStriped"
|
||||
:scroll="{ x: tableColumns.length * 120, y: 'calc(100vh - 480px)' }"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
columnWidth: '48px',
|
||||
selectedRowKeys: tableState.selectedRowKeys,
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<DictTag :options="dict.neInfoStatus" :value="record.status" />
|
||||
</template>
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.editText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByEdit(record)"
|
||||
>
|
||||
<template #icon><FormOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>
|
||||
{{ t('views.configManage.neManage.restart') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordMore('restart', record)"
|
||||
>
|
||||
<template #icon><UndoOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>
|
||||
{{ t('views.configManage.neManage.stop') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordMore('stop', record)"
|
||||
>
|
||||
<template #icon><CloseSquareOutlined /> </template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="left">
|
||||
<template #title>{{ t('common.moreText') }}</template>
|
||||
<a-dropdown
|
||||
placement="bottomRight"
|
||||
:trigger="['hover', 'click']"
|
||||
>
|
||||
<a-button type="link">
|
||||
<template #icon><EllipsisOutlined /> </template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu @click="({ key }:any) => fnRecordMore(key, record)">
|
||||
<a-menu-item key="log">
|
||||
<FileTextOutlined />
|
||||
{{ t('views.configManage.neManage.log') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="start">
|
||||
<ThunderboltOutlined />
|
||||
{{ t('views.configManage.neManage.start') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item
|
||||
key="reload"
|
||||
v-if="
|
||||
!['OMC', 'PCF', 'IMS', 'MME'].includes(record.neType)
|
||||
"
|
||||
>
|
||||
<SyncOutlined />
|
||||
{{ t('views.configManage.neManage.reload') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="delete">
|
||||
<DeleteOutlined />
|
||||
{{ t('common.deleteText') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
<template #expandedRowRender="{ record }">
|
||||
<div style="width: 46%; padding-left: 32px; padding-bottom: 16px">
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.ne.neInfo.info') }}
|
||||
</a-divider>
|
||||
<div>
|
||||
<span>{{ t('views.ne.neInfo.serviceState') }}:</span>
|
||||
<a-tag
|
||||
:color="record.serverState.online ? 'processing' : 'error'"
|
||||
>
|
||||
{{
|
||||
record.serverState.online
|
||||
? t('views.ne.neInfo.normalcy')
|
||||
: t('views.ne.neInfo.exceptions')
|
||||
}}
|
||||
</a-tag>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.neInfo.version') }}:</span>
|
||||
<span>{{ record.serverState.version }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.neInfo.serialNum') }}:</span>
|
||||
<span>{{ record.serverState.sn }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.neInfo.expiryDate') }}:</span>
|
||||
<span>{{ record.serverState.expire }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 46%; padding-left: 32px; padding-bottom: 16px">
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.ne.neInfo.resourceInfo') }}
|
||||
</a-divider>
|
||||
<div>
|
||||
<span>{{ t('views.ne.neInfo.neCpu') }}:</span>
|
||||
<a-progress
|
||||
status="normal"
|
||||
:stroke-color="
|
||||
record.resoures.nfCpuUsage < 30
|
||||
? '#52c41a'
|
||||
: record.resoures.nfCpuUsage > 70
|
||||
? '#ff4d4f'
|
||||
: '#1890ff'
|
||||
"
|
||||
:percent="record.resoures.nfCpuUsage"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.neInfo.sysCpu') }}:</span>
|
||||
<a-progress
|
||||
status="normal"
|
||||
:stroke-color="
|
||||
record.resoures.sysCpuUsage < 30
|
||||
? '#52c41a'
|
||||
: record.resoures.sysCpuUsage > 70
|
||||
? '#ff4d4f'
|
||||
: '#1890ff'
|
||||
"
|
||||
:percent="record.resoures.sysCpuUsage"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.neInfo.sysMem') }}:</span>
|
||||
<a-progress
|
||||
status="normal"
|
||||
:stroke-color="
|
||||
record.resoures.sysMemUsage < 30
|
||||
? '#52c41a'
|
||||
: record.resoures.sysMemUsage > 70
|
||||
? '#ff4d4f'
|
||||
: '#1890ff'
|
||||
"
|
||||
:percent="record.resoures.sysMemUsage"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.neInfo.sysDisk') }}:</span>
|
||||
<a-progress
|
||||
status="normal"
|
||||
:stroke-color="
|
||||
record.resoures.sysDiskUsage < 30
|
||||
? '#52c41a'
|
||||
: record.resoures.sysDiskUsage > 70
|
||||
? '#ff4d4f'
|
||||
: '#1890ff'
|
||||
"
|
||||
:percent="record.resoures.sysDiskUsage"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 新增框或修改框 -->
|
||||
<EditModal
|
||||
:visible="modalState.visibleByEdit"
|
||||
:edit-id="modalState.editId"
|
||||
@ok="fnModalEditOk"
|
||||
@cancel="fnModalEditCancel"
|
||||
></EditModal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.table-striped) td {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
./components/useNeOptions
|
||||
@@ -640,7 +640,11 @@ function fnGetList(pageNum?: number) {
|
||||
}
|
||||
tablePagination.total = res.total;
|
||||
tableState.data = res.rows;
|
||||
if (tablePagination.total <=(queryParams.pageNum - 1) * tablePagination.pageSize &&queryParams.pageNum !== 1) {
|
||||
if (
|
||||
tablePagination.total <=
|
||||
(queryParams.pageNum - 1) * tablePagination.pageSize &&
|
||||
queryParams.pageNum !== 1
|
||||
) {
|
||||
tableState.loading = false;
|
||||
fnGetList(queryParams.pageNum - 1);
|
||||
}
|
||||
@@ -914,6 +918,7 @@ onMounted(() => {
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<TableColumnsDnd
|
||||
cache-id="udmAuthData"
|
||||
:columns="tableColumns"
|
||||
v-model:columns-dnd="tableColumnsDnd"
|
||||
></TableColumnsDnd>
|
||||
@@ -1018,7 +1023,11 @@ onMounted(() => {
|
||||
name="imsi"
|
||||
v-bind="modalStateFrom.validateInfos.imsi"
|
||||
>
|
||||
<a-input v-model:value="modalState.from.imsi" allow-clear :disabled="!!modalState.from.id">
|
||||
<a-input
|
||||
v-model:value="modalState.from.imsi"
|
||||
allow-clear
|
||||
:disabled="!!modalState.from.id"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
@@ -1092,7 +1101,7 @@ onMounted(() => {
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16" v-if="!modalState.from.id">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
label="KI"
|
||||
@@ -1103,6 +1112,7 @@ onMounted(() => {
|
||||
v-model:value="modalState.from.ki"
|
||||
allow-clear
|
||||
:maxlength="32"
|
||||
:disabled="!!modalState.from.id"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
@@ -1125,6 +1135,7 @@ onMounted(() => {
|
||||
v-model:value="modalState.from.opc"
|
||||
allow-clear
|
||||
:maxlength="32"
|
||||
:disabled="!!modalState.from.id"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { message } from 'ant-design-vue/lib';
|
||||
import { TableColumnsType, message } from 'ant-design-vue/lib';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import { listBase5G } from '@/api/neUser/base5G';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import { useRoute } from 'vue-router';
|
||||
const neInfoStore = useNeInfoStore();
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
/**网元参数 */
|
||||
@@ -70,7 +70,7 @@ let tableState: TabeStateType = reactive({
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
let tableColumns = ref<TableColumnsType>([
|
||||
{
|
||||
title: 'Radio ID',
|
||||
dataIndex: 'id',
|
||||
@@ -86,15 +86,18 @@ let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: 'Radio Name',
|
||||
dataIndex: 'name',
|
||||
align: 'center',
|
||||
width: 150,
|
||||
align: 'left',
|
||||
resizable: true,
|
||||
width: 200,
|
||||
minWidth: 150,
|
||||
maxWidth: 400,
|
||||
},
|
||||
{
|
||||
title: 'Radio Address',
|
||||
dataIndex: 'address',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
]);
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
@@ -165,6 +168,9 @@ function fnGetList(pageNum?: number) {
|
||||
tableState.loading = false;
|
||||
fnGetList(queryParams.pageNum - 1);
|
||||
}
|
||||
} else {//AMF返回404是代表没找到这个数据 GNB_NOT_FOUND
|
||||
tablePagination.total = 0;
|
||||
tableState.data = [];
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
@@ -172,7 +178,6 @@ function fnGetList(pageNum?: number) {
|
||||
|
||||
onMounted(() => {
|
||||
// 获取网元网元列表
|
||||
|
||||
neInfoStore
|
||||
.fnNelist()
|
||||
.then(res => {
|
||||
@@ -191,8 +196,11 @@ onMounted(() => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 默认选择UPF
|
||||
const item = neCascaderOptions.value.find(s => s.value === 'AMF');
|
||||
// 无查询参数neType时 默认选择AMF
|
||||
const queryNeType = (route.query.neType as string) || 'AMF';
|
||||
const item = neCascaderOptions.value.find(
|
||||
s => s.value === queryNeType
|
||||
);
|
||||
if (item && item.children) {
|
||||
const info = item.children[0];
|
||||
queryParams.neType = [info.neType, info.neId];
|
||||
@@ -200,7 +208,6 @@ onMounted(() => {
|
||||
const info = neCascaderOptions.value[0].children[0];
|
||||
queryParams.neType = [info.neType, info.neId];
|
||||
}
|
||||
console.log(neCascaderOptions.value);
|
||||
}
|
||||
} else {
|
||||
message.warning({
|
||||
@@ -328,6 +335,7 @@ onMounted(() => {
|
||||
:row-class-name="fnTableStriped"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ y: 'calc(100vh - 480px)' }"
|
||||
@resizeColumn="(w:number, col:any) => (col.width = w)"
|
||||
>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { message, Modal, Form } from 'ant-design-vue/lib';
|
||||
import { message, Modal, Form, TableColumnsType } from 'ant-design-vue/lib';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import UploadModal from '@/components/UploadModal/index.vue';
|
||||
import {
|
||||
listRules,
|
||||
@@ -71,67 +70,70 @@ let tableState: TabeStateType = reactive({
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
let tableColumns = ref<TableColumnsType>([
|
||||
{
|
||||
title: 'IMSI',
|
||||
dataIndex: 'imsi',
|
||||
sorter: (a: any, b: any) => Number(a.imsi) - Number(b.imsi),
|
||||
align: 'center',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'MSISDN',
|
||||
dataIndex: 'msisdn',
|
||||
sorter: (a: any, b: any) => Number(a.msisdn) - Number(b.msisdn),
|
||||
align: 'center',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'SAR',
|
||||
dataIndex: 'sar',
|
||||
align: 'center',
|
||||
width: 80,
|
||||
align: 'left',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title: 'RFSP',
|
||||
dataIndex: 'rfsp',
|
||||
align: 'center',
|
||||
align: 'left',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title: 'QOS Video',
|
||||
dataIndex: 'qosVideo',
|
||||
align: 'center',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'QOS Audio',
|
||||
dataIndex: 'qosAudio',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
align: 'left',
|
||||
resizable: true,
|
||||
width: 150,
|
||||
minWidth: 100,
|
||||
maxWidth: 300,
|
||||
},
|
||||
{
|
||||
title: 'PCC Rules',
|
||||
dataIndex: 'pccRules',
|
||||
align: 'center',
|
||||
align: 'left',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: 'SESS Rules',
|
||||
dataIndex: 'sessRules',
|
||||
align: 'center',
|
||||
align: 'left',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: 'HDR Enrich',
|
||||
dataIndex: 'hdrEnrich',
|
||||
align: 'center',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'UE Policy',
|
||||
dataIndex: 'uePolicy',
|
||||
align: 'center',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
@@ -140,7 +142,7 @@ let tableColumns: ColumnsType = [
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
];
|
||||
]);
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
@@ -166,8 +168,6 @@ type ModalStateType = {
|
||||
type: 'delete' | 'add' | 'update';
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
/**更新加载数据按钮 loading */
|
||||
loadDataLoading: boolean;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
@@ -190,7 +190,6 @@ let modalState: ModalStateType = reactive({
|
||||
isBatch: false,
|
||||
type: 'add',
|
||||
confirmLoading: false,
|
||||
loadDataLoading: false,
|
||||
});
|
||||
|
||||
/**对话框内表单属性和校验规则 */
|
||||
@@ -291,10 +290,28 @@ function fnModalOk() {
|
||||
result
|
||||
.then((res: any) => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: modalState.title }),
|
||||
duration: 3,
|
||||
});
|
||||
if (modalState.isBatch) {
|
||||
let okTip = '';
|
||||
if (modalState.type === 'add') {
|
||||
okTip = res.data;
|
||||
}
|
||||
if (modalState.type === 'update') {
|
||||
okTip = res.data;
|
||||
}
|
||||
if (modalState.type === 'delete') {
|
||||
okTip = t('common.msgSuccess', { msg: modalState.title });
|
||||
}
|
||||
message.success({
|
||||
content: okTip,
|
||||
duration: 5,
|
||||
});
|
||||
} else {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: modalState.title }),
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
|
||||
fnModalCancel();
|
||||
fnGetList();
|
||||
} else {
|
||||
@@ -363,7 +380,7 @@ function fnRecordDelete(row: Record<string, any>) {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', {
|
||||
msg: row.imsi + t('common.deleteText'),
|
||||
msg: `${t('common.deleteText')} ${row.imsi}`,
|
||||
}),
|
||||
key,
|
||||
duration: 2,
|
||||
@@ -475,8 +492,9 @@ function fnModalUploadImportUpload(file: File) {
|
||||
return res;
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data?.filePath) {
|
||||
uploadImportState.msg = t('views.neUser.pcf.uploadFileOk');
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data?.data) {
|
||||
uploadImportState.msg = res.data?.data;
|
||||
// uploadImportState.msg = t('views.neUser.pcf.uploadFileOk');
|
||||
} else if (res.code === RESULT_CODE_SUCCESS && res.data?.detail) {
|
||||
uploadImportState.msg = res.data?.detail;
|
||||
} else {
|
||||
@@ -713,6 +731,7 @@ onMounted(() => {
|
||||
:row-class-name="fnTableStriped"
|
||||
:pagination="false"
|
||||
:scroll="{ y: 'calc(100vh - 480px)' }"
|
||||
@resizeColumn="(w:number, col:any) => (col.width = w)"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'imsi'">
|
||||
|
||||
@@ -116,7 +116,7 @@ let tableColumns: ColumnsType = [
|
||||
},
|
||||
{
|
||||
title: 'Subscribed SNSSAIs',
|
||||
dataIndex: 'sar',
|
||||
dataIndex: 'nssai',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
},
|
||||
@@ -134,7 +134,7 @@ let tableColumns: ColumnsType = [
|
||||
},
|
||||
{
|
||||
title: 'Service Area Restrict',
|
||||
dataIndex: 'nssai',
|
||||
dataIndex: 'sar',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
},
|
||||
@@ -362,6 +362,32 @@ const modalStateFromOption = reactive({
|
||||
],
|
||||
});
|
||||
|
||||
//新增时初始SM Data
|
||||
const bigRows = ref<any[]>([
|
||||
{
|
||||
id: 0,
|
||||
sst: '',
|
||||
sd: '',
|
||||
smallRows: [
|
||||
{
|
||||
id: 0,
|
||||
dnn: '',
|
||||
smStaticIp: '',
|
||||
msIp: '',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
dnn: 'ims',
|
||||
smStaticIp: '',
|
||||
msIp: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
//小数组的index
|
||||
let smallRowIndexCounter = 0;
|
||||
|
||||
/**
|
||||
* 针对修改框的截取每位数值
|
||||
* @param num 二进制值: 10001 n:长度有几位
|
||||
@@ -405,6 +431,7 @@ function fnModalVisibleByEdit(imsi?: string) {
|
||||
getSub(neID, imsi)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
transformFormData(res.data.smData);
|
||||
let ardAll = parseInt(res.data.ard).toString(2).padStart(8, '0');
|
||||
let hplAll = parseInt(res.data.hplmnOdb).toString(2).padStart(8, '0');
|
||||
let odbAll = parseInt(res.data.epsOdb).toString(2).padStart(9, '0');
|
||||
@@ -466,22 +493,132 @@ const modalStateFrom = Form.useForm(
|
||||
staticIp: [
|
||||
{ required: true, message: 'static ip' + t('common.unableNull') },
|
||||
],
|
||||
smData: [
|
||||
{
|
||||
required: true,
|
||||
message: 'Subscribed SM Data' + t('common.unableNull'),
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* 封装为SM Data
|
||||
*/
|
||||
function transformData(data: any) {
|
||||
let transformedData = data.map((item: any) => {
|
||||
if (
|
||||
!item.sst ||
|
||||
!item.smallRows.every((smallRow: any) => smallRow.dnn)
|
||||
) {
|
||||
message.error({
|
||||
content: `${t('views.neUser.sub.smDataArrTip')}`,
|
||||
duration: 3,
|
||||
});
|
||||
throw new Error('sst, sd, and all dnn are required fields');
|
||||
}
|
||||
|
||||
let sstSd = item.sd?item.sst + '-' + item.sd:item.sst;
|
||||
let smallRowData = item.smallRows
|
||||
.map((smallRow: any) => {
|
||||
let parts = [smallRow.dnn];
|
||||
if (smallRow.smStaticIp) {
|
||||
parts.push(smallRow.smStaticIp);
|
||||
}
|
||||
if (smallRow.msIp) {
|
||||
parts.push(smallRow.msIp);
|
||||
}
|
||||
return parts.join('-');
|
||||
})
|
||||
.join('&');
|
||||
|
||||
return sstSd + '&' + smallRowData;
|
||||
});
|
||||
|
||||
return transformedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拆解SM Data成表单数据
|
||||
*/
|
||||
function transformFormData(data: any) {
|
||||
let allData = data ? data.split(';') : [];
|
||||
let bigIDFlag = 0;
|
||||
let smallIDFlag = 0;
|
||||
|
||||
let transformedData = allData.map((item: any) => {
|
||||
let json: any = {
|
||||
id: bigIDFlag++,
|
||||
sst: item.split('&')[0].split('-')[0],
|
||||
sd: item.split('&')[0].split('-')[1]?item.split('&')[0].split('-')[1]:'',
|
||||
smallRows: [],
|
||||
};
|
||||
item
|
||||
.split('&')
|
||||
.slice(1)
|
||||
.forEach((single: any) => {
|
||||
let smallRowJson: any = {
|
||||
id: smallIDFlag++,
|
||||
dnn: single.split('-')[0],
|
||||
smStaticIp: '',
|
||||
msIp: '',
|
||||
};
|
||||
let smStaticIpArr: any = [];
|
||||
single
|
||||
.split('-')
|
||||
.slice(1)
|
||||
.forEach((dnnParts: any) => {
|
||||
if (dnnParts.includes('/') && dnnParts.includes(':')) {
|
||||
//IPV6 既有/ 又有:
|
||||
smStaticIpArr.push(dnnParts);
|
||||
}
|
||||
if (!dnnParts.includes('/') && !dnnParts.includes(':')) {
|
||||
//IPV4 没有/ 也没有:
|
||||
smStaticIpArr.push(dnnParts);
|
||||
}
|
||||
|
||||
if (dnnParts.includes('/') && !dnnParts.includes(':')) {
|
||||
//msIp 只有/ 没有:
|
||||
smallRowJson.msIp = dnnParts;
|
||||
}
|
||||
});
|
||||
smallRowJson.smStaticIp = smStaticIpArr.join('-');
|
||||
json.smallRows.push(smallRowJson);
|
||||
});
|
||||
return json;
|
||||
});
|
||||
if (transformedData.length > 0) {
|
||||
bigRows.value = transformedData;
|
||||
} else {
|
||||
bigRows.value = [
|
||||
{
|
||||
id: 0,
|
||||
sst: '',
|
||||
sd: '',
|
||||
smallRows: [
|
||||
{
|
||||
id: 0,
|
||||
dnn: '',
|
||||
smStaticIp: '',
|
||||
msIp: '',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
dnn: 'ims',
|
||||
smStaticIp: '',
|
||||
msIp: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalOk() {
|
||||
const from = Object.assign({}, toRaw(modalState.from));
|
||||
|
||||
try {
|
||||
from.smData = transformData(bigRows.value).join(';');
|
||||
} catch (error: any) {
|
||||
console.error(error.message);
|
||||
return false;
|
||||
}
|
||||
let validateNames = ['imsi', 'msisdn', 'staticIp'];
|
||||
|
||||
if (from.id === '') {
|
||||
@@ -560,12 +697,6 @@ const modalStateBatchFrom = Form.useForm(
|
||||
staticIp: [
|
||||
{ required: true, message: 'static ip' + t('common.unableNull') },
|
||||
],
|
||||
smData: [
|
||||
{
|
||||
required: true,
|
||||
message: 'Subscribed SM Data' + t('common.unableNull'),
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
@@ -575,7 +706,12 @@ const modalStateBatchFrom = Form.useForm(
|
||||
*/
|
||||
function fnBatchModalOk() {
|
||||
const from = Object.assign({}, toRaw(modalState.BatchForm));
|
||||
|
||||
try {
|
||||
from.smData = transformData(bigRows.value).join(';');
|
||||
} catch (error: any) {
|
||||
console.error(error.message);
|
||||
return false;
|
||||
}
|
||||
modalStateBatchFrom
|
||||
.validate()
|
||||
.then(e => {
|
||||
@@ -695,6 +831,28 @@ function fnModalCancel() {
|
||||
modalState.visibleByEdit = false;
|
||||
modalState.visibleByView = false;
|
||||
modalStateFrom.resetFields();
|
||||
bigRows.value = [
|
||||
{
|
||||
id: 0,
|
||||
sst: '',
|
||||
sd: '',
|
||||
smallRows: [
|
||||
{
|
||||
id: 0,
|
||||
dnn: '',
|
||||
smStaticIp: '',
|
||||
msIp: '',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
dnn: 'ims',
|
||||
smStaticIp: '',
|
||||
msIp: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
smallRowIndexCounter = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -705,6 +863,28 @@ function fnBatchModalCancel() {
|
||||
modalState.visibleByBatch = false;
|
||||
modalState.visibleByView = false;
|
||||
modalStateBatchFrom.resetFields();
|
||||
bigRows.value = [
|
||||
{
|
||||
id: 0,
|
||||
sst: '',
|
||||
sd: '',
|
||||
smallRows: [
|
||||
{
|
||||
id: 0,
|
||||
dnn: '',
|
||||
smStaticIp: '',
|
||||
msIp: '',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
dnn: 'ims',
|
||||
smStaticIp: '',
|
||||
msIp: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
smallRowIndexCounter = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -961,6 +1141,41 @@ function fnModalUploadImportUpload(file: File) {
|
||||
});
|
||||
}
|
||||
|
||||
function addSmallRow(bigIndex: any) {
|
||||
const newSmallRow = {
|
||||
id: ++smallRowIndexCounter,
|
||||
dnn: '',
|
||||
smStaticIp: '',
|
||||
msIp: '',
|
||||
};
|
||||
bigRows.value[bigIndex].smallRows.push(newSmallRow);
|
||||
}
|
||||
|
||||
function addBigRow() {
|
||||
const newBigRow = {
|
||||
id: bigRows.value.length,
|
||||
sst: '',
|
||||
sd: '',
|
||||
smallRows: [
|
||||
{
|
||||
id: 0,
|
||||
dnn: '',
|
||||
smStaticIp: '',
|
||||
msIp: '',
|
||||
},
|
||||
],
|
||||
};
|
||||
bigRows.value.push(newBigRow);
|
||||
}
|
||||
|
||||
function delDNN(sonIndex: any, bigIndex: any) {
|
||||
bigRows.value[bigIndex].smallRows.splice(sonIndex, 1);
|
||||
}
|
||||
|
||||
function delBigRow(bigIndex: any) {
|
||||
bigRows.value.splice(bigIndex, 1);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取网元网元列表
|
||||
useNeInfoStore()
|
||||
@@ -1180,6 +1395,7 @@ onMounted(() => {
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<TableColumnsDnd
|
||||
cache-id="udmSubData"
|
||||
:columns="tableColumns"
|
||||
v-model:columns-dnd="tableColumnsDnd"
|
||||
></TableColumnsDnd>
|
||||
@@ -1284,6 +1500,7 @@ onMounted(() => {
|
||||
<a-form-item
|
||||
label="IMSI"
|
||||
name="imsi"
|
||||
:label-col="{ span: 5 }"
|
||||
v-bind="modalStateFrom.validateInfos.imsi"
|
||||
>
|
||||
<a-input v-model:value="modalState.from.imsi" allow-clear>
|
||||
@@ -1324,52 +1541,105 @@ onMounted(() => {
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-divider orientation="left"
|
||||
>Subscribed SM Data
|
||||
<a-tooltip title="Add SM Data">
|
||||
<a-button
|
||||
shape="circle"
|
||||
@click="addBigRow"
|
||||
style="margin-left: 10px"
|
||||
>
|
||||
<template #icon><plus-outlined /></template>
|
||||
</a-button> </a-tooltip
|
||||
></a-divider>
|
||||
<!-- 大数组布局 -->
|
||||
<div v-for="(row, index) in bigRows" :key="String(row.id)">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
label="SST-SD"
|
||||
name="row.sst"
|
||||
:label-col="{ span: 5 }"
|
||||
>
|
||||
<a-input-group>
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="10">
|
||||
<a-input v-model:value="row.sst" />
|
||||
</a-col>
|
||||
<span style="margin-top: 5px">-</span>
|
||||
<a-col :span="12">
|
||||
<a-input v-model:value="row.sd" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="4">
|
||||
<a-tooltip title="Add DNN">
|
||||
<a-button shape="circle" @click="addSmallRow(row.id)">
|
||||
<template #icon><plus-square-outlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-tooltip title="Delete SM Data" v-if="index !== 0">
|
||||
<a-button danger shape="circle" @click="delBigRow(index)">
|
||||
<template #icon><minus-outlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item
|
||||
v-if="!modalState.from.id"
|
||||
label="Subscribed SM Data"
|
||||
:label-col="{ span: 3 }"
|
||||
name="smData"
|
||||
v-bind="modalStateFrom.validateInfos.smData"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.smData"
|
||||
allow-clear
|
||||
:maxlength="128"
|
||||
<!-- 小数组布局 -->
|
||||
<div
|
||||
v-for="(smallRow, smallIndex) in row.smallRows"
|
||||
:key="String(smallRow.id)"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
{{ t('views.neUser.sub.smDataTip', { num: '128' }) }}
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="Subscribed SM Data"
|
||||
:label-col="{ span: 3 }"
|
||||
name="smData"
|
||||
v-else
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.smData"
|
||||
allow-clear
|
||||
:maxlength="128"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
{{ t('views.neUser.sub.smDataTip', { num: '128' }) }}
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="7" :xs="24">
|
||||
<a-form-item
|
||||
label="DNN/APN"
|
||||
name="dnn"
|
||||
:label-col="{ span: 10 }"
|
||||
>
|
||||
<a-input v-model:value="smallRow.dnn" allow-clear></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="6" :xs="24">
|
||||
<a-form-item
|
||||
label="Static IP"
|
||||
name="smStaticIp"
|
||||
:label-col="{ span: 5 }"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="smallRow.smStaticIp"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="8" :xs="24">
|
||||
<a-form-item label="Routing Behind MS IP" name="msIp">
|
||||
<a-input v-model:value="smallRow.msIp" allow-clear></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="1" :md="1" :xs="24" v-if="smallIndex !== 0">
|
||||
<a-tooltip title="Delete DNN">
|
||||
<a-button
|
||||
danger
|
||||
shape="circle"
|
||||
@click="delDNN(smallIndex, row.id)"
|
||||
>
|
||||
<template #icon><close-square-outlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
<a-collapse :bordered="false" ghost>
|
||||
<a-collapse-panel key="5G">
|
||||
<template #header>
|
||||
@@ -1735,8 +2005,8 @@ onMounted(() => {
|
||||
<a-form
|
||||
name="modalStateBatchFrom"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 6 }"
|
||||
:labelWrap="true"
|
||||
:label-col="{ span: 6 }"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
@@ -1800,31 +2070,102 @@ onMounted(() => {
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="24" :md="24" :xs="24">
|
||||
<a-form-item
|
||||
label="Subscribed SM Data"
|
||||
name="smData"
|
||||
:label-col="{ span: 3 }"
|
||||
v-bind="modalStateBatchFrom.validateInfos.smData"
|
||||
<a-divider orientation="left"
|
||||
>Subscribed SM Data
|
||||
<a-tooltip title="Add SM Data">
|
||||
<a-button
|
||||
shape="circle"
|
||||
@click="addBigRow"
|
||||
style="margin-left: 10px"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.BatchForm.smData"
|
||||
allow-clear
|
||||
:maxlength="128"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
{{ t('views.neUser.sub.smDataTip', { num: '128' }) }}
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
<template #icon><plus-outlined /></template>
|
||||
</a-button> </a-tooltip
|
||||
></a-divider>
|
||||
<!-- 大数组布局 -->
|
||||
<div v-for="(row, index) in bigRows" :key="String(row.id)">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="SST-SD" name="row.sst">
|
||||
<a-input-group>
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="10">
|
||||
<a-input v-model:value="row.sst" />
|
||||
</a-col>
|
||||
<span style="margin-top: 5px">-</span>
|
||||
<a-col :span="12">
|
||||
<a-input v-model:value="row.sd" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="4">
|
||||
<a-tooltip title="Add DNN">
|
||||
<a-button shape="circle" @click="addSmallRow(row.id)">
|
||||
<template #icon><plus-square-outlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-tooltip title="Delete SM Data" v-if="index !== 0">
|
||||
<a-button danger shape="circle" @click="delBigRow(index)">
|
||||
<template #icon><minus-outlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 小数组布局 -->
|
||||
<div
|
||||
v-for="(smallRow, smallIndex) in row.smallRows"
|
||||
:key="String(smallRow.id)"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="7" :xs="24">
|
||||
<a-form-item
|
||||
label="DNN/APN"
|
||||
name="dnn"
|
||||
:label-col="{ span: 12 }"
|
||||
>
|
||||
<a-input v-model:value="smallRow.dnn" allow-clear></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="6" :xs="24">
|
||||
<a-form-item
|
||||
label="Static IP"
|
||||
name="smStaticIp"
|
||||
:label-col="{ span: 5 }"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="smallRow.smStaticIp"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="8" :xs="24">
|
||||
<a-form-item label="Routing Behind MS IP" name="msIp">
|
||||
<a-input v-model:value="smallRow.msIp" allow-clear></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="1" :md="1" :xs="24" v-if="smallIndex !== 0">
|
||||
<a-tooltip title="Delete DNN">
|
||||
<a-button
|
||||
danger
|
||||
shape="circle"
|
||||
@click="delDNN(smallIndex, row.id)"
|
||||
>
|
||||
<template #icon><close-square-outlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a-collapse :bordered="false" ghost>
|
||||
<a-collapse-panel key="5G">
|
||||
<template #header>
|
||||
|
||||
@@ -429,6 +429,7 @@ onMounted(() => {
|
||||
:visible="modalState.visibleByView"
|
||||
:title="modalState.title"
|
||||
@cancel="fnModalCancel"
|
||||
:footer="null"
|
||||
>
|
||||
<a-form layout="horizontal" :label-col="{ span: 6 }" :labelWrap="true">
|
||||
<a-row :gutter="16">
|
||||
@@ -477,11 +478,6 @@ onMounted(() => {
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-button key="cancel" @click="fnModalCancel">
|
||||
{{ t('common.close') }}
|
||||
</a-button>
|
||||
</template>
|
||||
</DraggableModal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
@@ -1,23 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw } from 'vue';
|
||||
import { reactive, onMounted, ref, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { Modal } from 'ant-design-vue/lib';
|
||||
import { Form, message, Modal } from 'ant-design-vue/lib';
|
||||
import { SizeType } from 'ant-design-vue/lib/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { saveAs } from 'file-saver';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { getTraceRawInfo, listTraceData } from '@/api/traceManage/analysis';
|
||||
const { t } = useI18n();
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import {
|
||||
addCustom,
|
||||
delCustom,
|
||||
getCustom,
|
||||
listCustom,
|
||||
updateCustom,
|
||||
} from '@/api/perfManage/customTarget';
|
||||
const { getDict } = useDictStore();
|
||||
const { t, currentLocale } = useI18n();
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**原始严重程度 */
|
||||
activeAlarmSeverity: DictType[];
|
||||
} = reactive({
|
||||
activeAlarmSeverity: [],
|
||||
});
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**移动号 */
|
||||
imsi: '',
|
||||
/**移动号 */
|
||||
msisdn: '',
|
||||
/**网元类型 */
|
||||
neType: '',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
@@ -27,7 +40,7 @@ let queryParams = reactive({
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
imsi: '',
|
||||
neType: '',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
@@ -46,6 +59,8 @@ type TabeStateType = {
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
@@ -54,59 +69,31 @@ let tableState: TabeStateType = reactive({
|
||||
size: 'middle',
|
||||
seached: true,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('views.traceManage.analysis.trackTaskId'),
|
||||
dataIndex: 'taskId',
|
||||
title: t('views.perfManage.taskManage.neType'),
|
||||
dataIndex: 'neType',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.imsi'),
|
||||
dataIndex: 'imsi',
|
||||
title: t('views.perfManage.customTarget.kpiId'),
|
||||
dataIndex: 'kpiId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.msisdn'),
|
||||
dataIndex: 'msisdn',
|
||||
title: t('views.perfManage.customTarget.kpiSet'),
|
||||
dataIndex: 'kpiSet',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.srcIp'),
|
||||
dataIndex: 'srcAddr',
|
||||
title: t('views.perfManage.customTarget.period'),
|
||||
dataIndex: 'threshold',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.dstIp'),
|
||||
dataIndex: 'dstAddr',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.signalType'),
|
||||
dataIndex: 'ifType',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.msgType'),
|
||||
dataIndex: 'msgType',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.msgDirect'),
|
||||
dataIndex: 'msgDirect',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.analysis.rowTime'),
|
||||
dataIndex: 'timestamp',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
@@ -147,18 +134,59 @@ function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**查询备份信息列表, pageNum初始页数 */
|
||||
/**
|
||||
* 自定义指标删除
|
||||
* @param row 记录编号ID
|
||||
*/
|
||||
function fnRecordDelete(row: Record<string, any>) {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.perfManage.customTarget.delCustomTip', { num: row.id }),
|
||||
onOk() {
|
||||
const key = 'delThreshold';
|
||||
message.loading({ content: t('common.loading'), key });
|
||||
delCustom(row).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('views.perfManage.customTarget.delCustom', {
|
||||
num: row.id,
|
||||
}),
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key: key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**查询信息列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if(pageNum){
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
listTraceData(toRaw(queryParams)).then(res => {
|
||||
listCustom(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
tablePagination.total = res.total;
|
||||
tableState.data = res.rows;
|
||||
if (tablePagination.total <=(queryParams.pageNum - 1) * tablePagination.pageSize &&queryParams.pageNum !== 1) {
|
||||
if (
|
||||
tablePagination.total <=
|
||||
(queryParams.pageNum - 1) * tablePagination.pageSize &&
|
||||
queryParams.pageNum !== 1
|
||||
) {
|
||||
tableState.loading = false;
|
||||
fnGetList(queryParams.pageNum - 1);
|
||||
}
|
||||
@@ -167,178 +195,275 @@ function fnGetList(pageNum?: number) {
|
||||
});
|
||||
}
|
||||
|
||||
/**抽屉对象信息状态类型 */
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**抽屉框是否显示 */
|
||||
visible: boolean;
|
||||
/**详情框是否显示 */
|
||||
visibleByView: boolean;
|
||||
/**新增框或修改框是否显示 */
|
||||
visibleByEdit: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**网元类型设备对象 */
|
||||
neType: string[];
|
||||
/**网元类型性能测量集 */
|
||||
neTypPerformance: Record<string, any>[];
|
||||
/**网元类型对象类型集 */
|
||||
objectTypeArr: Record<string, any>[];
|
||||
/**已选择性能测量项 */
|
||||
selectedPre: string[];
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**抽屉对象信息状态 */
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
visible: false,
|
||||
visibleByView: false,
|
||||
visibleByEdit: false,
|
||||
title: '',
|
||||
neType: [],
|
||||
neTypPerformance: [],
|
||||
objectTypeArr: [],
|
||||
selectedPre: [],
|
||||
from: {
|
||||
rawData: '',
|
||||
rawDataHTML: '',
|
||||
downBtn: false,
|
||||
id: '',
|
||||
neType: '',
|
||||
objectType: '',
|
||||
expression: '',
|
||||
kpiSet: '',
|
||||
title: '',
|
||||
kpiId: '',
|
||||
period: 900,
|
||||
},
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* 对话框弹出显示
|
||||
* @param row 记录信息
|
||||
*/
|
||||
function fnModalVisible(row: Record<string, any>) {
|
||||
// 进制转数据
|
||||
const hexString = parseBase64Data(row.rawMsg);
|
||||
const rawData = convertToReadableFormat(hexString);
|
||||
modalState.from.rawData = rawData;
|
||||
// RAW解析HTML
|
||||
getTraceRawInfo(row.id).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
const htmlString = rawDataHTMLScript(res.msg);
|
||||
modalState.from.rawDataHTML = htmlString;
|
||||
modalState.from.downBtn = true;
|
||||
} else {
|
||||
modalState.from.rawDataHTML = t('views.traceManage.analysis.noData');
|
||||
/**对话框内表单属性和校验规则 */
|
||||
const modalStateFrom = Form.useForm(
|
||||
modalState.from,
|
||||
reactive({
|
||||
neType: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.traceManage.task.neTypePlease'),
|
||||
},
|
||||
],
|
||||
objectType: [
|
||||
{
|
||||
required: true,
|
||||
message:
|
||||
t('views.perfManage.customTarget.objectType') +
|
||||
t('common.unableNull'),
|
||||
},
|
||||
],
|
||||
expression: [
|
||||
{
|
||||
required: true,
|
||||
message:
|
||||
t('views.perfManage.customTarget.expression') +
|
||||
t('common.unableNull'),
|
||||
},
|
||||
],
|
||||
kpiId: [
|
||||
{
|
||||
required: true,
|
||||
message:
|
||||
t('views.perfManage.customTarget.kpiId') + t('common.unableNull'),
|
||||
},
|
||||
],
|
||||
title: [
|
||||
{
|
||||
required: true,
|
||||
message:
|
||||
t('views.perfManage.customTarget.title') + t('common.unableNull'),
|
||||
},
|
||||
],
|
||||
period: [
|
||||
{
|
||||
required: true,
|
||||
message:
|
||||
t('views.perfManage.customTarget.period') + t('common.unableNull'),
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**性能测量数据集选择初始 */
|
||||
function fnSelectPerformanceInit(value: any) {
|
||||
const performance = useNeInfoStore().perMeasurementList.filter(
|
||||
i => i.neType === value
|
||||
);
|
||||
if (modalState.from.objectType) modalState.from.objectType = '';
|
||||
if(modalState.selectedPre.length > 0) modalState.selectedPre = [];
|
||||
|
||||
modalState.from.expression = '';
|
||||
const arrSet = new Set<string>();
|
||||
performance.forEach((data: any) => {
|
||||
arrSet.add(data.objectType);
|
||||
});
|
||||
// 组装对象类型options
|
||||
modalState.objectTypeArr = Array.from(arrSet).map((value: any) => ({
|
||||
label: value,
|
||||
value: value,
|
||||
}));
|
||||
|
||||
//进行分组选择
|
||||
const groupedData = performance.reduce((groups: any, item: any) => {
|
||||
const { kpiCode, ...rest } = item;
|
||||
if (!groups[kpiCode]) {
|
||||
groups[kpiCode] = [];
|
||||
}
|
||||
groups[kpiCode].push(rest);
|
||||
return groups;
|
||||
}, {});
|
||||
|
||||
//渲染出性能测量集的选择项
|
||||
modalState.neTypPerformance = Object.keys(groupedData).map(kpiCode => {
|
||||
return {
|
||||
label: kpiCode,
|
||||
options: groupedData[kpiCode].map((item: any) => {
|
||||
return {
|
||||
value: item.kpiId,
|
||||
label:
|
||||
currentLocale.value === 'zh_CN'
|
||||
? JSON.parse(item.titleJson).cn
|
||||
: JSON.parse(item.titleJson).en,
|
||||
kpiCode: kpiCode,
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
modalState.title = t('views.traceManage.analysis.taskTitle', {
|
||||
num: row.imsi,
|
||||
});
|
||||
modalState.visible = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param noticeId 网元id, 不传为新增
|
||||
*/
|
||||
function fnModalVisibleClose() {
|
||||
modalState.visible = false;
|
||||
modalState.from.downBtn = false;
|
||||
modalState.from.rawDataHTML = '';
|
||||
modalState.from.rawData = '';
|
||||
function fnModalVisibleByEdit(id?: string) {
|
||||
if (!id) {
|
||||
modalStateFrom.resetFields();
|
||||
modalState.title = t('views.perfManage.customTarget.addCustom');
|
||||
modalState.visibleByEdit = true;
|
||||
} else {
|
||||
if (modalState.confirmLoading) return;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
modalState.confirmLoading = true;
|
||||
getCustom(id).then(res => {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
fnSelectPerformanceInit(res.data.neType);
|
||||
modalState.selectedPre = res.data.kpiSet
|
||||
? res.data.kpiSet.split(',')
|
||||
: [];
|
||||
modalState.from = Object.assign(modalState.from, res.data);
|
||||
modalState.title = t('views.perfManage.customTarget.editCustom');
|
||||
modalState.visibleByEdit = true;
|
||||
} else {
|
||||
message.error(t('views.perfManage.customTarget.errorCustomInfo'), 3);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 将Base64编码解码为字节数组
|
||||
function parseBase64Data(hexData: string) {
|
||||
// 将Base64编码解码为字节数组
|
||||
const byteString = atob(hexData);
|
||||
const byteArray = new Uint8Array(byteString.length);
|
||||
for (let i = 0; i < byteString.length; i++) {
|
||||
byteArray[i] = byteString.charCodeAt(i);
|
||||
}
|
||||
|
||||
// 将每一个字节转换为2位16进制数表示,并拼接起来
|
||||
let hexString = '';
|
||||
for (let i = 0; i < byteArray.length; i++) {
|
||||
const hex = byteArray[i].toString(16);
|
||||
hexString += hex.length === 1 ? '0' + hex : hex;
|
||||
}
|
||||
return hexString;
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalOk() {
|
||||
modalStateFrom
|
||||
.validate()
|
||||
.then(e => {
|
||||
// if (modalState.selectedPre.length === 0) {
|
||||
// message.error({
|
||||
// content: `${res.msg}`,
|
||||
// duration: 3,
|
||||
// });
|
||||
// }
|
||||
modalState.from.kpiSet = modalState.selectedPre.join(',');
|
||||
const from = toRaw(modalState.from);
|
||||
//return false;
|
||||
modalState.confirmLoading = true;
|
||||
const perfTask = from.id ? updateCustom(from) : addCustom(from);
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
perfTask
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: modalState.title }),
|
||||
duration: 3,
|
||||
});
|
||||
modalStateFrom.resetFields();
|
||||
fnModalCancel();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
modalState.confirmLoading = false;
|
||||
fnGetList();
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
|
||||
});
|
||||
}
|
||||
|
||||
// 转换十六进制字节流为可读格式和ASCII码表示
|
||||
function convertToReadableFormat(hexString: string) {
|
||||
let result = '';
|
||||
let asciiResult = '';
|
||||
let arr = [];
|
||||
let row = 100;
|
||||
for (let i = 0; i < hexString.length; i += 2) {
|
||||
const hexChars = hexString.substring(i, i + 2);
|
||||
const decimal = parseInt(hexChars, 16);
|
||||
const asciiChar =
|
||||
decimal >= 32 && decimal <= 126 ? String.fromCharCode(decimal) : '.';
|
||||
|
||||
result += hexChars + ' ';
|
||||
asciiResult += asciiChar;
|
||||
|
||||
if ((i + 2) % 32 === 0) {
|
||||
arr.push({
|
||||
row: row,
|
||||
code: result,
|
||||
asciiText: asciiResult,
|
||||
});
|
||||
result = '';
|
||||
asciiResult = '';
|
||||
row += 10;
|
||||
}
|
||||
if (2 + i == hexString.length) {
|
||||
arr.push({
|
||||
row: row,
|
||||
code: result,
|
||||
asciiText: asciiResult,
|
||||
});
|
||||
result = '';
|
||||
asciiResult = '';
|
||||
row += 10;
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.visibleByEdit = false;
|
||||
modalState.confirmLoading = false;
|
||||
modalStateFrom.resetFields();
|
||||
modalState.neType = [];
|
||||
modalState.neTypPerformance = [];
|
||||
modalState.selectedPre = [];
|
||||
}
|
||||
|
||||
// 信息详情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 fnSelectPer(s: any, option: any) {
|
||||
modalState.from.expression += s;
|
||||
}
|
||||
|
||||
/**信息文件下载 */
|
||||
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`);
|
||||
},
|
||||
});
|
||||
/**
|
||||
* 多选框取消性能指标 表达式的变化
|
||||
*/
|
||||
function fnDelPer(s: any, option: any) {
|
||||
modalState.from.expression = modalState.from.expression.replace(s, '');
|
||||
}
|
||||
|
||||
// function checkText(e:any){
|
||||
// console.log(e);
|
||||
// const reg = /^[*+-/]*$/;
|
||||
|
||||
// }
|
||||
|
||||
onMounted(() => {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('active_alarm_severity')]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.activeAlarmSeverity = resArr[0].value;
|
||||
}
|
||||
});
|
||||
|
||||
Promise.allSettled([
|
||||
// 获取网元网元列表
|
||||
useNeInfoStore().fnNelist(),
|
||||
// 获取性能测量集列表
|
||||
useNeInfoStore().fnNeTaskPerformance(),
|
||||
]).finally(() => {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -354,26 +479,15 @@ onMounted(() => {
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.analysis.imsi')"
|
||||
name="imsi"
|
||||
:label="t('views.traceManage.task.neType')"
|
||||
name="neType "
|
||||
>
|
||||
<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-auto-complete
|
||||
v-model:value="queryParams.neType"
|
||||
:options="useNeInfoStore().getNeSelectOtions"
|
||||
allow-clear
|
||||
:placeholder="t('views.traceManage.task.neTypePlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
@@ -396,12 +510,17 @@ onMounted(() => {
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title> </template>
|
||||
<template #title>
|
||||
<a-button type="primary" @click.prevent="fnModalVisibleByEdit()">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
{{ t('common.addText') }}
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>{{ t('common.searchBarText') }}</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
@@ -410,15 +529,15 @@ onMounted(() => {
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>{{ t('common.sizeText') }}</template>
|
||||
<a-dropdown trigger="click">
|
||||
<a-dropdown trigger="click" placement="bottomRight">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
@@ -458,9 +577,18 @@ onMounted(() => {
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>查看详情</template>
|
||||
<a-button type="link" @click.prevent="fnModalVisible(record)">
|
||||
<template #icon><ProfileOutlined /></template>
|
||||
<template #title>{{ t('common.editText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByEdit(record.id)"
|
||||
>
|
||||
<template #icon><FormOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.deleteText') }}</template>
|
||||
<a-button type="link" @click.prevent="fnRecordDelete(record)">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
@@ -469,42 +597,130 @@ onMounted(() => {
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 详情框 -->
|
||||
<!-- 新增框或修改框 -->
|
||||
<a-modal
|
||||
width="800px"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:visible="modalState.visibleByEdit"
|
||||
:title="modalState.title"
|
||||
:visible="modalState.visible"
|
||||
@cancel="fnModalVisibleClose"
|
||||
:confirm-loading="modalState.confirmLoading"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<div class="raw-title">
|
||||
{{ t('views.traceManage.analysis.signalData') }}
|
||||
</div>
|
||||
<a-row
|
||||
class="raw"
|
||||
:gutter="16"
|
||||
v-for="v in modalState.from.rawData"
|
||||
:key="v.row"
|
||||
>
|
||||
<a-col class="num" :span="2">{{ v.row }}</a-col>
|
||||
<a-col class="code" :span="12">{{ v.code }}</a-col>
|
||||
<a-col class="txt" :span="10">{{ v.asciiText }}</a-col>
|
||||
</a-row>
|
||||
<a-divider />
|
||||
<div class="raw-title">
|
||||
{{ t('views.traceManage.analysis.signalDetail') }}
|
||||
<a-button
|
||||
type="dashed"
|
||||
size="small"
|
||||
@click.prevent="fnDownloadFile"
|
||||
v-if="modalState.from.downBtn"
|
||||
<a-form name="modalStateFrom" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.traceManage.task.neType')"
|
||||
name="neType"
|
||||
v-bind="modalStateFrom.validateInfos.neType"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="modalState.from.neType"
|
||||
:options="useNeInfoStore().getNeSelectOtions"
|
||||
@change="fnSelectPerformanceInit"
|
||||
:allow-clear="false"
|
||||
:placeholder="t('views.traceManage.task.neTypePlease')"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.objectType')"
|
||||
name="kpiSobjectTypeet"
|
||||
v-show="modalState.from.neType"
|
||||
v-bind="modalStateFrom.validateInfos.objectType"
|
||||
>
|
||||
<a-select
|
||||
placeholder="Please select"
|
||||
v-model:value="modalState.from.objectType"
|
||||
:options="modalState.objectTypeArr"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.kpiId')"
|
||||
name="kpiId"
|
||||
v-bind="modalStateFrom.validateInfos.kpiId"
|
||||
>
|
||||
<a-input v-model:value="modalState.from.kpiId" allow-clear>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.period')"
|
||||
name="period"
|
||||
v-bind="modalStateFrom.validateInfos.period"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="modalState.from.period"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
:options="[
|
||||
{ label: '5S', value: 5 },
|
||||
{ label: '1M', value: 60 },
|
||||
{ label: '5M', value: 300 },
|
||||
{ label: '15M', value: 900 },
|
||||
{ label: '30M', value: 1800 },
|
||||
{ label: '60M', value: 3600 },
|
||||
]"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.title')"
|
||||
name="title"
|
||||
v-bind="modalStateFrom.validateInfos.title"
|
||||
>
|
||||
<template #icon>
|
||||
<DownloadOutlined />
|
||||
</template>
|
||||
{{ t('views.traceManage.analysis.taskDownText') }}
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="raw-html" v-html="modalState.from.rawDataHTML"></div>
|
||||
<a-input v-model:value="modalState.from.title" allow-clear> </a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.expression')"
|
||||
name="expression"
|
||||
v-bind="modalStateFrom.validateInfos.expression"
|
||||
>
|
||||
<a-input v-model:value="modalState.from.expression" allow-clear >
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="21" :md="21" :xs="24">
|
||||
<a-form-item name="perSelect">
|
||||
<a-select
|
||||
mode="multiple"
|
||||
style="margin-left: 80px"
|
||||
placeholder="Please select"
|
||||
allow-clear
|
||||
v-model:value="modalState.selectedPre"
|
||||
:options="modalState.neTypPerformance"
|
||||
@select="fnSelectPer"
|
||||
@deselect="fnDelPer"
|
||||
></a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.perfManage.customTarget.description')"
|
||||
name="description"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.description"
|
||||
:auto-size="{ minRows: 2, maxRows: 6 }"
|
||||
:maxlength="250"
|
||||
:show-count="true"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
@@ -513,26 +729,4 @@ onMounted(() => {
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.raw {
|
||||
&-title {
|
||||
color: #000000d9;
|
||||
font-size: 24px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.num {
|
||||
background-color: #e5e5e5;
|
||||
}
|
||||
.code {
|
||||
background-color: #e7e6ff;
|
||||
}
|
||||
.txt {
|
||||
background-color: #ffe3e5;
|
||||
}
|
||||
|
||||
&-html {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -537,7 +537,7 @@ function wsMessage(res: Record<string, any>) {
|
||||
if (data.groupId === '10') {
|
||||
const kpiEvent = data.data;
|
||||
// 非对应网元类型
|
||||
if (kpiEvent.neType !== state.neType[0]) return;
|
||||
if (kpiEvent.neType !== queryParams.neType) return;
|
||||
|
||||
for (const key of Object.keys(data.data)) {
|
||||
const v = kpiEvent[key];
|
||||
@@ -680,7 +680,11 @@ onBeforeUnmount(() => {
|
||||
<a-col :lg="2" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetListTitle()">
|
||||
<a-button
|
||||
type="primary"
|
||||
:loading="tableState.loading"
|
||||
@click.prevent="fnGetListTitle()"
|
||||
>
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
@@ -709,6 +713,7 @@ onBeforeUnmount(() => {
|
||||
</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
:loading="tableState.loading"
|
||||
@click.prevent="fnRecordExport()"
|
||||
v-show="tableState.showTable"
|
||||
>
|
||||
@@ -740,6 +745,7 @@ onBeforeUnmount(() => {
|
||||
</a-tooltip>
|
||||
<TableColumnsDnd
|
||||
v-if="tableColumns.length > 0"
|
||||
:cache-id="`kpiTarget_${state.neType[0]}`"
|
||||
:columns="tableColumns"
|
||||
v-model:columns-dnd="tableColumnsDnd"
|
||||
></TableColumnsDnd>
|
||||
|
||||
@@ -311,6 +311,7 @@ function fnModalVisibleByEdit(id?: string) {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
fnSelectPerformanceInit(res.data.neType)
|
||||
modalState.from = Object.assign(modalState.from, res.data);
|
||||
modalState.title = t('views.perfManage.perfThreshold.editThre');
|
||||
modalState.visibleByEdit = true;
|
||||
|
||||
@@ -490,6 +490,7 @@ function fnModalVisibleByEdit(id?: string) {
|
||||
}
|
||||
|
||||
modalState.neType = [res.data.neType, JSON.parse(res.data.neIds)[0]];
|
||||
fnSelectPerformanceInit(res.data.neType) //初始性能测量数据
|
||||
modalState.from.neId=JSON.parse(res.data.neIds)[0];
|
||||
modalState.timeRangePicker = [res.data.startTime, res.data.endTime];
|
||||
modalState.from = Object.assign(modalState.from, res.data);
|
||||
@@ -559,6 +560,7 @@ function fnModalOk() {
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
fnModalCancel();
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
|
||||
@@ -139,7 +139,7 @@ let tableColumns: ColumnsType = [
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.system.log.login.loginTime'),
|
||||
dataIndex: 'loginTime',
|
||||
@@ -412,20 +412,6 @@ onMounted(() => {
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.log.login.loginTime')"
|
||||
name="queryRangePicker"
|
||||
>
|
||||
<a-range-picker
|
||||
v-model:value="queryRangePicker"
|
||||
allow-clear
|
||||
bordered
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
@@ -440,6 +426,22 @@ onMounted(() => {
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.log.login.loginTime')"
|
||||
name="queryRangePicker"
|
||||
>
|
||||
<a-range-picker
|
||||
v-model:value="queryRangePicker"
|
||||
allow-clear
|
||||
bordered
|
||||
:show-time="{ format: 'HH:mm:ss' }"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%"
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
@@ -370,7 +370,11 @@ function fnGetList(pageNum?: number) {
|
||||
}
|
||||
tablePagination.total = res.total;
|
||||
tableState.data = res.rows;
|
||||
if (tablePagination.total <=(queryParams.pageNum - 1) * tablePagination.pageSize &&queryParams.pageNum !== 1) {
|
||||
if (
|
||||
tablePagination.total <=
|
||||
(queryParams.pageNum - 1) * tablePagination.pageSize &&
|
||||
queryParams.pageNum !== 1
|
||||
) {
|
||||
tableState.loading = false;
|
||||
fnGetList(queryParams.pageNum - 1);
|
||||
}
|
||||
@@ -407,7 +411,7 @@ onMounted(() => {
|
||||
<!-- 表格搜索栏 -->
|
||||
<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.system.log.operate.operModule')"
|
||||
name="title"
|
||||
@@ -415,7 +419,7 @@ onMounted(() => {
|
||||
<a-input v-model:value="queryParams.title" allow-clear></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.log.operate.operUser')"
|
||||
name="operName"
|
||||
@@ -426,7 +430,7 @@ onMounted(() => {
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.log.operate.workType')"
|
||||
name="businessType"
|
||||
@@ -439,7 +443,7 @@ onMounted(() => {
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.log.operate.operStatus')"
|
||||
name="status"
|
||||
@@ -452,7 +456,7 @@ onMounted(() => {
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.log.operate.operTime')"
|
||||
name="queryRangePicker"
|
||||
@@ -461,12 +465,14 @@ onMounted(() => {
|
||||
v-model:value="queryRangePicker"
|
||||
allow-clear
|
||||
bordered
|
||||
value-format="YYYY-MM-DD"
|
||||
:show-time="{ format: 'HH:mm:ss' }"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%"
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||
|
||||
@@ -293,7 +293,7 @@ const modalStateFrom = Form.useForm(
|
||||
{
|
||||
required: true,
|
||||
pattern: regExpPasswd,
|
||||
message: t('views.system.user.userNameTip'),
|
||||
message: t('views.system.user.passwdTip'),
|
||||
},
|
||||
],
|
||||
nickName: [
|
||||
|
||||
@@ -70,8 +70,7 @@ function fnDownload() {
|
||||
fetch(url)
|
||||
.then(response => response.blob())
|
||||
.then(blob => {
|
||||
const fileName = url.substring(url.lastIndexOf('/') + 1);
|
||||
saveAs(blob, fileName);
|
||||
saveAs(blob, t('views.tool.help.fileName'));
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
|
||||
346
src/views/tool/neQuickSetup/components/StepActivate.vue
Normal file
346
src/views/tool/neQuickSetup/components/StepActivate.vue
Normal file
@@ -0,0 +1,346 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw, ref, onUnmounted } from 'vue';
|
||||
import { message, Modal, Upload } from 'ant-design-vue/lib';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import { FileType } from 'ant-design-vue/lib/upload/interface';
|
||||
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
|
||||
import { uploadFile } from '@/api/tool/file';
|
||||
import {
|
||||
codeNeLicense,
|
||||
changeNeLicense,
|
||||
stateNeLicense,
|
||||
} from '@/api/ne/neLicense';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { stepState } from '../hooks/useStep';
|
||||
import saveAs from 'file-saver';
|
||||
const { copy } = useClipboard({ legacy: true });
|
||||
const { t } = useI18n();
|
||||
|
||||
/**授权激活对象信息状态类型 */
|
||||
type LicenseStateType = {
|
||||
/**步骤 */
|
||||
setp: 'license' | 'verify';
|
||||
/**主机ID */
|
||||
hostId: string;
|
||||
/**表单数据 */
|
||||
from: {
|
||||
neType: string;
|
||||
neId: string;
|
||||
activationRequestCode: string;
|
||||
licensePath: string;
|
||||
reload: boolean;
|
||||
};
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
/**上传文件 */
|
||||
uploadFiles: any[];
|
||||
};
|
||||
|
||||
/**授权激活对象信息状态 */
|
||||
const licenseState: LicenseStateType = reactive({
|
||||
setp: 'license',
|
||||
hostId: '',
|
||||
from: {
|
||||
neType: '',
|
||||
neId: '',
|
||||
activationRequestCode: '',
|
||||
licensePath: '',
|
||||
reload: true,
|
||||
},
|
||||
confirmLoading: false,
|
||||
uploadFiles: [],
|
||||
});
|
||||
|
||||
/**表单上传前检查或转换压缩 */
|
||||
function fnBeforeUploadFile(file: FileType) {
|
||||
if (licenseState.confirmLoading) return false;
|
||||
if (!file.name.endsWith('.ini')) {
|
||||
message.error('只支持上传文件格式 .ini', 3);
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
const isLt2M = file.size / 1024 / 1024 < 2;
|
||||
if (!isLt2M) {
|
||||
message.error('文件必须小于2MB', 3);
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**表单上传文件 */
|
||||
function fnUploadFile(up: UploadRequestOption) {
|
||||
// 发送请求
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
licenseState.confirmLoading = true;
|
||||
let formData = new FormData();
|
||||
formData.append('file', up.file);
|
||||
formData.append('subPath', 'license');
|
||||
uploadFile(formData)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success('上传成功', 3);
|
||||
// 改为完成状态
|
||||
const file = licenseState.uploadFiles[0];
|
||||
file.percent = 100;
|
||||
file.status = 'done';
|
||||
// 预置到表单
|
||||
const { fileName } = res.data;
|
||||
licenseState.from.licensePath = fileName;
|
||||
} else {
|
||||
message.error(res.msg, 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
licenseState.confirmLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**复制授权申请码 */
|
||||
function fnCopyCode() {
|
||||
const code = licenseState.from.activationRequestCode;
|
||||
if (!code) return;
|
||||
copy(code).then(() => {
|
||||
message.success('已成功复制', 3);
|
||||
});
|
||||
}
|
||||
|
||||
/**下载授权申请码文件 */
|
||||
function fnDownCode() {
|
||||
const { activationRequestCode, neType, neId } = licenseState.from;
|
||||
if (!activationRequestCode) return;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: '确认将授权申请码下载为文件进行保存?',
|
||||
onOk() {
|
||||
const blob = new Blob([activationRequestCode], {
|
||||
type: 'text/plain',
|
||||
});
|
||||
saveAs(blob, `${neType}_${neId}_code.txt`);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**读取授权申请码 */
|
||||
function fnGetCode() {
|
||||
const { neType, neId } = licenseState.from;
|
||||
licenseState.confirmLoading = true;
|
||||
codeNeLicense(neType, neId).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
licenseState.from.activationRequestCode = res.data;
|
||||
licenseState.confirmLoading = false;
|
||||
} else {
|
||||
message.error(res.msg, 3);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**启动服务验证 */
|
||||
function fnRunCheck() {
|
||||
if (licenseState.confirmLoading) return;
|
||||
const form = toRaw(licenseState.from);
|
||||
if (form.activationRequestCode === '' || form.licensePath === '') {
|
||||
message.error(t('common.errorFields', { num: 1 }), 3);
|
||||
return;
|
||||
}
|
||||
Object.assign(form, { hostId: licenseState.hostId });
|
||||
licenseState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
changeNeLicense(form)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success('网元开始进行校验', 3);
|
||||
fnVerifyTask();
|
||||
} else {
|
||||
message.error(res.msg, 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
licenseState.confirmLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**校验状态 */
|
||||
const verifyState = reactive({
|
||||
timer: null as any,
|
||||
/**执行次数 */
|
||||
count: 0,
|
||||
/**信息日志 */
|
||||
msgArr: [] as string[],
|
||||
/**数据 sn expire */
|
||||
data: null as any,
|
||||
});
|
||||
|
||||
/**巡检校验任务 */
|
||||
function fnVerifyTask() {
|
||||
licenseState.setp = 'verify';
|
||||
verifyState.timer = setInterval(() => {
|
||||
if (verifyState.count > 15) {
|
||||
clearTimeout(verifyState.timer);
|
||||
verifyState.msgArr.unshift(
|
||||
`第 ${
|
||||
verifyState.count + 1
|
||||
} 次:网元验证激活失败,请重新上传有效激活文件。`
|
||||
);
|
||||
return;
|
||||
}
|
||||
const { neType, neId } = licenseState.from;
|
||||
stateNeLicense(neType, neId).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(`${neType} ${neId} 网元激活成功`, 3);
|
||||
verifyState.data = res.data;
|
||||
// 记录当前步骤状态信息
|
||||
stepState.states[stepState.current] = { from: res.data };
|
||||
stepState.stepNext = true;
|
||||
}
|
||||
verifyState.count += 1;
|
||||
verifyState.msgArr.unshift(`第 ${verifyState.count} 次:${res.msg}`);
|
||||
});
|
||||
}, 2_000);
|
||||
}
|
||||
|
||||
/**巡检重新校验 */
|
||||
function fnVerifyTaskStop() {
|
||||
clearTimeout(verifyState.timer);
|
||||
verifyState.count = 0;
|
||||
verifyState.msgArr = [];
|
||||
licenseState.setp = 'license';
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 读取步骤:网元信息
|
||||
const stepPrevFrom = stepState.states[1].from;
|
||||
const { neType, neId } = stepPrevFrom;
|
||||
licenseState.from.neType = neType;
|
||||
licenseState.from.neId = neId;
|
||||
licenseState.hostId = stepPrevFrom.hostIds.split(',')[0];
|
||||
// 获取code
|
||||
fnGetCode();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clearTimeout(verifyState.timer);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-form
|
||||
name="modalStateFrom"
|
||||
layout="horizontal"
|
||||
autocomplete="off"
|
||||
:validate-on-rule-change="false"
|
||||
:validateTrigger="[]"
|
||||
:label-col="{ span: 3 }"
|
||||
:wrapper-col="{ span: 12 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<div>---- 授权申请</div>
|
||||
|
||||
<template v-if="licenseState.setp === 'license'">
|
||||
<a-form-item label="授权申请码" name="comment" :required="true">
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
v-model:value="licenseState.from.activationRequestCode"
|
||||
:disabled="true"
|
||||
style="width: calc(100% - 200px)"
|
||||
/>
|
||||
<a-tooltip title="复制">
|
||||
<a-button type="default" @click="fnCopyCode()">
|
||||
<template #icon><CopyOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="下载">
|
||||
<a-button type="primary" @click="fnDownCode()">
|
||||
<template #icon><DownloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="授权激活文件" name="file" :required="true">
|
||||
<a-upload
|
||||
name="file"
|
||||
v-model:file-list="licenseState.uploadFiles"
|
||||
accept=".ini"
|
||||
list-type="text"
|
||||
:max-count="1"
|
||||
:show-upload-list="{
|
||||
showPreviewIcon: false,
|
||||
showRemoveIcon: false,
|
||||
showDownloadIcon: false,
|
||||
}"
|
||||
:before-upload="fnBeforeUploadFile"
|
||||
:custom-request="fnUploadFile"
|
||||
:disabled="licenseState.confirmLoading"
|
||||
>
|
||||
<a-button type="default"> 上传文件 </a-button>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item name="check" :wrapper-col="{ span: 14, offset: 3 }">
|
||||
<div style="align-items: center">
|
||||
<a-button
|
||||
type="primary"
|
||||
shape="round"
|
||||
@click="fnRunCheck()"
|
||||
:loading="licenseState.confirmLoading"
|
||||
>
|
||||
<template #icon><LinkOutlined /></template>
|
||||
授权校验
|
||||
</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<div>---- 校验信息</div>
|
||||
|
||||
<template v-if="licenseState.setp === 'verify'">
|
||||
<a-form-item
|
||||
name="info"
|
||||
label="巡检信息"
|
||||
:label-col="{ span: 3 }"
|
||||
:wrapper-col="{ span: 24 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-result
|
||||
:status="verifyState.data ? 'success' : 'info'"
|
||||
:title="verifyState.data ? '成功激活' : '请等待网元验证结果'"
|
||||
>
|
||||
<template #extra>
|
||||
<a-button
|
||||
@click="fnVerifyTaskStop()"
|
||||
v-if="verifyState.data === null"
|
||||
>
|
||||
返回重新校验
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<div class="verify-msg" v-if="verifyState.data === null">
|
||||
<p
|
||||
style="font-size: 16px"
|
||||
v-for="(s, i) in verifyState.msgArr"
|
||||
:key="i"
|
||||
>
|
||||
<close-circle-outlined :style="{ color: 'red' }" />
|
||||
{{ s }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p style="font-size: 16px">序列号:{{ verifyState.data.sn }}</p>
|
||||
<p style="font-size: 16px">
|
||||
许可证到期时间:{{ verifyState.data.expire }}
|
||||
</p>
|
||||
</div>
|
||||
</a-result>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.verify-msg {
|
||||
height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
410
src/views/tool/neQuickSetup/components/StepCheck.vue
Normal file
410
src/views/tool/neQuickSetup/components/StepCheck.vue
Normal file
@@ -0,0 +1,410 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw } from 'vue';
|
||||
import { message, Form, Modal } from 'ant-design-vue/lib';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { neHostAuthorizedRSA, neHostCheckInfo } from '@/api/ne/neHost';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import { stepState } from '../hooks/useStep';
|
||||
const { getDict } = useDictStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**认证模式 */
|
||||
neHostAuthMode: DictType[];
|
||||
} = reactive({
|
||||
neHostAuthMode: [],
|
||||
});
|
||||
|
||||
/**检查对象信息状态类型 */
|
||||
type CheckStateType = {
|
||||
/**服务器信息 */
|
||||
info: Record<string, any>;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**检查对象信息状态 */
|
||||
let checkState: CheckStateType = reactive({
|
||||
info: {
|
||||
addr: '未连接',
|
||||
kernelName: '-',
|
||||
kernelRelease: '-',
|
||||
machine: '-',
|
||||
nodename: '-',
|
||||
prettyName: '-',
|
||||
sshLink: false,
|
||||
sudo: false,
|
||||
},
|
||||
from: {
|
||||
hostId: undefined,
|
||||
hostType: 'ssh',
|
||||
groupId: '1',
|
||||
title: 'SSH_NE_22',
|
||||
addr: '192.168.5.57',
|
||||
port: 22,
|
||||
user: 'agtuser',
|
||||
authMode: '0',
|
||||
password: 'QWERqwer',
|
||||
privateKey: '',
|
||||
passPhrase: '',
|
||||
remark: '',
|
||||
},
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**表单属性和校验规则 */
|
||||
const checkStateFrom = Form.useForm(
|
||||
checkState.from,
|
||||
reactive({
|
||||
addr: [
|
||||
{
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 128,
|
||||
message: t('views.ne.neHost.addrPlease'),
|
||||
},
|
||||
],
|
||||
port: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.ne.neHost.portPlease'),
|
||||
},
|
||||
],
|
||||
user: [
|
||||
{
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 50,
|
||||
message: t('views.ne.neHost.userPlease'),
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 128,
|
||||
message: t('views.ne.neHost.passwordPlease'),
|
||||
},
|
||||
],
|
||||
privateKey: [
|
||||
{
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 128,
|
||||
message: t('views.ne.neHost.privateKeyPlease'),
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**测试连接检查信息 */
|
||||
function fnCheckInfo() {
|
||||
if (checkState.confirmLoading) return;
|
||||
const form = toRaw(checkState.from);
|
||||
const validateArr = ['addr', 'port', 'user'];
|
||||
if (form.authMode === '0') {
|
||||
validateArr.push('password');
|
||||
} else {
|
||||
validateArr.push('privateKey');
|
||||
}
|
||||
|
||||
checkState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
checkStateFrom
|
||||
.validate(validateArr)
|
||||
.then(() => {
|
||||
Object.assign(checkState.info, {
|
||||
addr: '未连接',
|
||||
kernelName: '-',
|
||||
kernelRelease: '-',
|
||||
machine: '-',
|
||||
nodename: '-',
|
||||
prettyName: '-',
|
||||
sshLink: false,
|
||||
sudo: false,
|
||||
});
|
||||
stepState.stepNext = false;
|
||||
return neHostCheckInfo(form);
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
checkState.info = res.data;
|
||||
if (!res.data.sudo) {
|
||||
message.warning({
|
||||
content: `请配置服务器授予当前用户无密码 sudo 权限,确保有权限进行软件包安装`,
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!res.data.sshLink) {
|
||||
message.warning({
|
||||
content: `请配置服务器间免密信任关系,确保服务器间文件传输功能`,
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
message.success({
|
||||
content: `${form.addr}:${form.port} ${t('views.ne.neHost.testOk')}`,
|
||||
duration: 2,
|
||||
});
|
||||
// 记录当前步骤状态信息
|
||||
stepState.states[stepState.current] = {
|
||||
info: checkState.info,
|
||||
from: checkState.from,
|
||||
};
|
||||
stepState.stepNext = true;
|
||||
} else {
|
||||
message.error({
|
||||
content: `${form.addr}:${form.port} ${res.msg}`,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
checkState.confirmLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**测试连接检查信息表单重置 */
|
||||
function fnCheckInfoReset() {
|
||||
checkStateFrom.resetFields();
|
||||
}
|
||||
|
||||
/**SSH连接-免密直连 */
|
||||
function fnSSHLink() {
|
||||
if (checkState.info.sshLink) return;
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '是否要配置免密直连?',
|
||||
onOk: () => {
|
||||
const form = toRaw(checkState.from);
|
||||
neHostAuthorizedRSA(form).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: `操作成功`,
|
||||
duration: 2,
|
||||
});
|
||||
checkState.info.sshLink = true;
|
||||
} else {
|
||||
message.error({
|
||||
content: `操作失败`,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('ne_host_authMode')]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.neHostAuthMode = resArr[0].value;
|
||||
}
|
||||
});
|
||||
// 状态还原
|
||||
const state = stepState.states[stepState.current];
|
||||
if (state) {
|
||||
if (state.info) {
|
||||
const info = toRaw(state.info);
|
||||
Object.assign(checkState.info, info);
|
||||
}
|
||||
if (state.from) {
|
||||
const from = toRaw(state.from);
|
||||
Object.assign(checkState.from, from);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-descriptions :column="{ lg: 3, md: 2, sm: 2, xs: 1 }" bordered>
|
||||
<a-descriptions-item label="服务器IP" :span="3">
|
||||
{{ checkState.info.addr }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="系统">
|
||||
{{ checkState.info.kernelName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="架构">
|
||||
{{ checkState.info.machine }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="内核">
|
||||
{{ checkState.info.kernelRelease }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item>
|
||||
<template #label>
|
||||
平台
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title> 支持 Ubuntu、Ubuntu </template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
{{ checkState.info.prettyName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="主机名">
|
||||
{{ checkState.info.nodename }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="授予权限">
|
||||
<a-tag :color="checkState.info.sudo ? 'success' : 'error'">
|
||||
<template #icon>
|
||||
<CheckCircleOutlined v-if="checkState.info.sudo" />
|
||||
<CloseCircleOutlined v-else />
|
||||
</template>
|
||||
可提权
|
||||
</a-tag>
|
||||
|
||||
<a-tag
|
||||
:color="checkState.info.sshLink ? 'success' : 'error'"
|
||||
style="cursor: pointer"
|
||||
@click="fnSSHLink()"
|
||||
>
|
||||
<template #icon>
|
||||
<CheckCircleOutlined v-if="checkState.info.sshLink" />
|
||||
<CloseCircleOutlined v-else />
|
||||
</template>
|
||||
可免密直连
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="连接检查" :span="2">
|
||||
<a-form
|
||||
name="checkStateFrom"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 6 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.addr')"
|
||||
name="addr"
|
||||
v-bind="checkStateFrom.validateInfos.addr"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="checkState.from.addr"
|
||||
allow-clear
|
||||
:maxlength="128"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.port')"
|
||||
name="port"
|
||||
v-bind="checkStateFrom.validateInfos.port"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="checkState.from.port"
|
||||
:min="10"
|
||||
:max="65535"
|
||||
:step="1"
|
||||
style="width: 100%"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.user')"
|
||||
name="user"
|
||||
v-bind="checkStateFrom.validateInfos.user"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="checkState.from.user"
|
||||
allow-clear
|
||||
:maxlength="50"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.ne.neHost.authMode')">
|
||||
<a-select
|
||||
v-model:value="checkState.from.authMode"
|
||||
default-value="0"
|
||||
:options="dict.neHostAuthMode"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item
|
||||
v-if="checkState.from.authMode === '0'"
|
||||
:label="t('views.ne.neHost.password')"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
name="password"
|
||||
v-bind="checkStateFrom.validateInfos.password"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="checkState.from.password"
|
||||
:maxlength="128"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<template v-if="checkState.from.authMode === '1'">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.privateKey')"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
name="privateKey"
|
||||
v-bind="checkStateFrom.validateInfos.privateKey"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="checkState.from.privateKey"
|
||||
:auto-size="{ minRows: 4, maxRows: 6 }"
|
||||
:maxlength="3000"
|
||||
:show-count="true"
|
||||
:placeholder="t('views.ne.neHost.privateKeyPlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.passPhrase')"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="checkState.from.passPhrase"
|
||||
:maxlength="128"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<a-form-item :wrapper-col="{ span: 8, offset: 3 }">
|
||||
<a-button
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
@click="fnCheckInfo()"
|
||||
:loading="checkState.confirmLoading"
|
||||
>
|
||||
进行连接
|
||||
</a-button>
|
||||
<a-button style="margin-left: 12px" @click="fnCheckInfoReset()">
|
||||
重置
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
227
src/views/tool/neQuickSetup/components/StepConfig.vue
Normal file
227
src/views/tool/neQuickSetup/components/StepConfig.vue
Normal file
@@ -0,0 +1,227 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, onBeforeMount } from 'vue';
|
||||
import { message } from 'ant-design-vue/lib';
|
||||
import {
|
||||
RESULT_CODE_ERROR,
|
||||
RESULT_CODE_SUCCESS,
|
||||
} from '@/constants/result-constants';
|
||||
import { stepState } from '../hooks/useStep';
|
||||
import { getConfigFile, saveConfigFile } from '@/api/ne/neInfo';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
|
||||
/**配置对象信息状态类型 */
|
||||
type ConfigState = {
|
||||
/**展开指定的树节点 */
|
||||
expandedKeys: string[];
|
||||
/**设置选中的树节点 */
|
||||
selectedKeys: string[];
|
||||
/**表单数据 */
|
||||
treeData: any[];
|
||||
/**等待 loading */
|
||||
treeLoading: boolean;
|
||||
/**选中的树节点 */
|
||||
selected: { title: string; key: string };
|
||||
/**设置选中的数据内容 */
|
||||
keyTextData: string;
|
||||
/**设置选中等待 loading */
|
||||
keyTextLoading: boolean;
|
||||
};
|
||||
|
||||
/**配置对象信息状态 */
|
||||
let configState: ConfigState = reactive({
|
||||
expandedKeys: ['000'],
|
||||
selectedKeys: ['oam_manager.yaml'],
|
||||
treeData: [
|
||||
{
|
||||
title: 'NE',
|
||||
key: '000',
|
||||
children: [
|
||||
{
|
||||
title: 'oam_manager',
|
||||
key: 'oam_manager.yaml',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
treeLoading: false,
|
||||
selected: { title: 'oam_manager', key: 'oam_manager.yaml' },
|
||||
keyTextData: '...',
|
||||
keyTextLoading: false,
|
||||
});
|
||||
|
||||
/**查询可选命令列表 */
|
||||
function fnSelectConfigNode(_: any, info: any) {
|
||||
const { title, key } = info.node;
|
||||
configState.selectedKeys = [key];
|
||||
configState.selected = { title, key };
|
||||
fnGetConfigFile(key);
|
||||
}
|
||||
|
||||
/**查询配置列表 */
|
||||
function fnGetConfigFile(filePath: string = '') {
|
||||
if (filePath) {
|
||||
if (configState.keyTextLoading) return;
|
||||
configState.keyTextLoading = true;
|
||||
} else {
|
||||
if (configState.treeLoading) return;
|
||||
configState.treeLoading = true;
|
||||
}
|
||||
|
||||
// 读取第二步的网元信息
|
||||
const stepNeInfo = stepState.states[1].from;
|
||||
getConfigFile(stepNeInfo.neType, stepNeInfo.neId, filePath)
|
||||
.then(res => {
|
||||
// 目录列表
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
const treeItem = {
|
||||
title: stepNeInfo.neType,
|
||||
key: stepNeInfo.neId,
|
||||
children: [] as any,
|
||||
};
|
||||
treeItem.children = res.data.map((fileName: string) => {
|
||||
return {
|
||||
title: fileName.replace(/\.[^.]+$/, ''),
|
||||
key: fileName,
|
||||
};
|
||||
});
|
||||
configState.treeData = [treeItem];
|
||||
configState.expandedKeys = [stepNeInfo.neId];
|
||||
// 选中加载显示内容
|
||||
if (treeItem.children.length > 0) {
|
||||
const fileItem = treeItem.children[0];
|
||||
configState.selectedKeys = [fileItem.key];
|
||||
configState.selected = fileItem;
|
||||
fnGetConfigFile(fileItem.key);
|
||||
}
|
||||
}
|
||||
|
||||
// 单文件内容
|
||||
if (res.code === RESULT_CODE_SUCCESS && typeof res.data === 'string') {
|
||||
configState.keyTextData = res.data;
|
||||
}
|
||||
|
||||
// 出错
|
||||
if (res.code === RESULT_CODE_ERROR) {
|
||||
message.error({
|
||||
content: res.msg,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (filePath) {
|
||||
configState.keyTextLoading = false;
|
||||
} else {
|
||||
configState.treeLoading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**保存配置文件 */
|
||||
function fnSaveConfigFile() {
|
||||
if (configState.keyTextLoading) return;
|
||||
// 读取第二步的网元信息
|
||||
const stepNeInfo = stepState.states[1].from;
|
||||
saveConfigFile({
|
||||
neType: stepNeInfo.neType,
|
||||
neId: stepNeInfo.neId,
|
||||
filePath: configState.selected.key,
|
||||
content: configState.keyTextData,
|
||||
sync: true, // 同步到网元
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
saveState.saveTime = parseDateToStr(new Date(), 'HH:mm:ss');
|
||||
// 记录当前步骤状态信息
|
||||
stepState.states[stepState.current] = {
|
||||
treeData: configState.treeData,
|
||||
};
|
||||
stepState.stepNext = true;
|
||||
} else {
|
||||
message.error({
|
||||
content: res.msg,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
saveState.timer = null;
|
||||
});
|
||||
}
|
||||
|
||||
/**保存文件数据状态 */
|
||||
const saveState = reactive({
|
||||
timer: null as any,
|
||||
saveTime: '',
|
||||
});
|
||||
|
||||
/**文件数据变更 */
|
||||
function fnChangekeyTextData(val: string) {
|
||||
console.log(val);
|
||||
// 在用户停止输入3秒后,执行保存请求的操作
|
||||
if (saveState.timer) {
|
||||
clearTimeout(saveState.timer);
|
||||
}
|
||||
stepState.stepNext = false;
|
||||
saveState.timer = setTimeout(fnSaveConfigFile, 3_000);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fnGetConfigFile();
|
||||
});
|
||||
|
||||
onBeforeMount(() => {
|
||||
clearTimeout(saveState.timer);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-row :gutter="8">
|
||||
<a-col :lg="6" :md="6" :xs="24">
|
||||
<a-spin :spinning="configState.treeLoading">
|
||||
<a-directory-tree
|
||||
v-model:expandedKeys="configState.expandedKeys"
|
||||
v-model:selectedKeys="configState.selectedKeys"
|
||||
@select="fnSelectConfigNode"
|
||||
:tree-data="configState.treeData"
|
||||
></a-directory-tree>
|
||||
</a-spin>
|
||||
</a-col>
|
||||
<a-col :lg="18" :md="18" :xs="24">
|
||||
<a-spin :spinning="configState.treeLoading || configState.keyTextLoading">
|
||||
<CodemirrorEdite
|
||||
v-model:value="configState.keyTextData"
|
||||
:disabled="configState.keyTextLoading"
|
||||
lang="yaml"
|
||||
height="500px"
|
||||
@change="fnChangekeyTextData"
|
||||
></CodemirrorEdite>
|
||||
<div
|
||||
class="edite-tip"
|
||||
:class="{ 'edite-tip__ing': saveState.timer > 0 }"
|
||||
>
|
||||
{{ configState.selected.title }}
|
||||
<template v-if="saveState.saveTime.length > 1">
|
||||
| 最近保存: {{ saveState.saveTime }}
|
||||
</template>
|
||||
</div>
|
||||
</a-spin>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.edite-tip {
|
||||
min-height: 24px;
|
||||
padding: 0 4px;
|
||||
color: #fff;
|
||||
line-height: 24px;
|
||||
background: var(--ant-primary-color);
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s, border 0s, line-height 0s, box-shadow 0s;
|
||||
&__ing {
|
||||
background: var(--ant-primary-color-disabled);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
182
src/views/tool/neQuickSetup/components/StepFinish.vue
Normal file
182
src/views/tool/neQuickSetup/components/StepFinish.vue
Normal file
@@ -0,0 +1,182 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted } from 'vue';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { stateNeInfo } from '@/api/ne/neInfo';
|
||||
import { stepState } from '../hooks/useStep';
|
||||
const { t } = useI18n();
|
||||
|
||||
/**状态数据 */
|
||||
const state = reactive({
|
||||
data: {} as Record<string, any>,
|
||||
resoures: {} as Record<string, any>,
|
||||
});
|
||||
|
||||
function getNeState() {
|
||||
// 读取第二步的网元信息
|
||||
const stepNeInfo = stepState.states[1].from;
|
||||
stateNeInfo(stepNeInfo.neType, stepNeInfo.neId).then(res => {
|
||||
// 单文件内容
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
state.data = res.data;
|
||||
|
||||
let sysCpuUsage = 0;
|
||||
let nfCpuUsage = 0;
|
||||
if (res.data.cpu) {
|
||||
nfCpuUsage = res.data.cpu.nfCpuUsage;
|
||||
if (nfCpuUsage > 100) {
|
||||
const nfCpu = +(res.data.cpu.nfCpuUsage / 100);
|
||||
if (nfCpu > 100) {
|
||||
nfCpuUsage = 100;
|
||||
} else {
|
||||
nfCpuUsage = +nfCpu.toFixed(2);
|
||||
}
|
||||
}
|
||||
|
||||
sysCpuUsage = res.data.cpu.sysCpuUsage;
|
||||
if (sysCpuUsage > 100) {
|
||||
const sysCpu = +(res.data.cpu.sysCpuUsage / 100);
|
||||
if (sysCpu > 100) {
|
||||
sysCpuUsage = 100;
|
||||
} else {
|
||||
sysCpuUsage = +sysCpu.toFixed(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let sysMemUsage = 0;
|
||||
if (res.data.mem) {
|
||||
let men = res.data.mem.sysMemUsage;
|
||||
if (men > 100) {
|
||||
men = +(men / 100).toFixed(2);
|
||||
}
|
||||
sysMemUsage = men;
|
||||
}
|
||||
|
||||
let sysDiskUsage = 0;
|
||||
if (res.data.disk && Array.isArray(res.data.disk.partitionInfo)) {
|
||||
let disks: any[] = res.data.disk.partitionInfo;
|
||||
disks = disks.sort((a, b) => +b.used - +a.used);
|
||||
if (disks.length > 0) {
|
||||
const { total, used } = disks[0];
|
||||
sysDiskUsage = +((used / total) * 100).toFixed(2);
|
||||
}
|
||||
}
|
||||
|
||||
Reflect.set(state, 'resoures', {
|
||||
sysDiskUsage,
|
||||
sysMemUsage,
|
||||
sysCpuUsage,
|
||||
nfCpuUsage,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getNeState();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="width: 46%; padding-left: 32px; padding-bottom: 16px">
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.ne.neInfo.info') }}
|
||||
</a-divider>
|
||||
<div>
|
||||
<span>{{ t('views.ne.neInfo.serviceState') }}:</span>
|
||||
<a-tag :color="state.data.online ? 'processing' : 'error'">
|
||||
{{
|
||||
state.data.online
|
||||
? t('views.ne.neInfo.normalcy')
|
||||
: t('views.ne.neInfo.exceptions')
|
||||
}}
|
||||
</a-tag>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.neInfo.version') }}:</span>
|
||||
<span>{{ state.data.version }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.neInfo.serialNum') }}:</span>
|
||||
<span>{{ state.data.sn }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.neInfo.expiryDate') }}:</span>
|
||||
<span>{{ state.data.expire }}</span>
|
||||
</div>
|
||||
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.ne.neInfo.resourceInfo') }}
|
||||
</a-divider>
|
||||
<div>
|
||||
<span>{{ t('views.ne.neInfo.neCpu') }}:</span>
|
||||
<a-progress
|
||||
status="normal"
|
||||
:stroke-color="
|
||||
state.resoures.nfCpuUsage < 30
|
||||
? '#52c41a'
|
||||
: state.resoures.nfCpuUsage > 70
|
||||
? '#ff4d4f'
|
||||
: '#1890ff'
|
||||
"
|
||||
:percent="state.resoures.nfCpuUsage"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.neInfo.sysCpu') }}:</span>
|
||||
<a-progress
|
||||
status="normal"
|
||||
:stroke-color="
|
||||
state.resoures.sysCpuUsage < 30
|
||||
? '#52c41a'
|
||||
: state.resoures.sysCpuUsage > 70
|
||||
? '#ff4d4f'
|
||||
: '#1890ff'
|
||||
"
|
||||
:percent="state.resoures.sysCpuUsage"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.neInfo.sysMem') }}:</span>
|
||||
<a-progress
|
||||
status="normal"
|
||||
:stroke-color="
|
||||
state.resoures.sysMemUsage < 30
|
||||
? '#52c41a'
|
||||
: state.resoures.sysMemUsage > 70
|
||||
? '#ff4d4f'
|
||||
: '#1890ff'
|
||||
"
|
||||
:percent="state.resoures.sysMemUsage"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.neInfo.sysDisk') }}:</span>
|
||||
<a-progress
|
||||
status="normal"
|
||||
:stroke-color="
|
||||
state.resoures.sysDiskUsage < 30
|
||||
? '#52c41a'
|
||||
: state.resoures.sysDiskUsage > 70
|
||||
? '#ff4d4f'
|
||||
: '#1890ff'
|
||||
"
|
||||
:percent="state.resoures.sysDiskUsage"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.collapse :deep(.ant-collapse-item) > .ant-collapse-header {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
.collapse-header {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
706
src/views/tool/neQuickSetup/components/StepInstall.vue
Normal file
706
src/views/tool/neQuickSetup/components/StepInstall.vue
Normal file
@@ -0,0 +1,706 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw, ref } from 'vue';
|
||||
import { message, Form, Upload } from 'ant-design-vue/lib';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import {
|
||||
RESULT_CODE_ERROR,
|
||||
RESULT_CODE_SUCCESS,
|
||||
} from '@/constants/result-constants';
|
||||
import { NE_TYPE_LIST } from '@/constants/ne-constants';
|
||||
import TerminalSSH from '@/components/TerminalSSH/index.vue';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import { checkInstallNeSoftware, listNeSoftware } from '@/api/ne/neSoftware';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { FileType } from 'ant-design-vue/lib/upload/interface';
|
||||
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
|
||||
import { uploadFileChunk } from '@/api/tool/file';
|
||||
import { stepState } from '../hooks/useStep';
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(['next']);
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'id',
|
||||
align: 'left',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title: 'neType',
|
||||
dataIndex: 'neType',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'fileName',
|
||||
dataIndex: 'fileName',
|
||||
key: 'fileName',
|
||||
align: 'left',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
title: 'version',
|
||||
dataIndex: 'version',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'comment',
|
||||
dataIndex: 'comment',
|
||||
key: 'comment',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: 'updateTime',
|
||||
dataIndex: 'updateTime',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
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;
|
||||
tableState.queryParams.pageNum = page;
|
||||
tableState.queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**查询参数 */
|
||||
queryParams: Record<string, any>;
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
queryParams: {
|
||||
neType: '',
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
loading: false,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(
|
||||
keys: (string | number)[],
|
||||
selectedRows: any[]
|
||||
) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
// 选择的表单数据填充
|
||||
const row = selectedRows[0];
|
||||
installState.from.neType = row.neType;
|
||||
installState.from.name = row.name;
|
||||
installState.from.path = row.path;
|
||||
installState.from.version = row.version;
|
||||
installState.from.description = row.description;
|
||||
}
|
||||
|
||||
/**查询列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
tableState.queryParams.pageNum = pageNum;
|
||||
}
|
||||
listNeSoftware(toRaw(tableState.queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
tablePagination.total = res.total;
|
||||
// 遍历处理资源情况数值
|
||||
tableState.data = res.rows;
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**安装对象信息状态类型 */
|
||||
type InstallStateType = {
|
||||
/**步骤 */
|
||||
setp: 'pkg' | 'preinput' | 'ssh';
|
||||
/**安装步骤命令 */
|
||||
setpSSHArr: string[];
|
||||
/**主机ID */
|
||||
hostId: string;
|
||||
/**文件操作类型 上传 or 选择 */
|
||||
optionType: 'upload' | 'option';
|
||||
/**表单数据 */
|
||||
from: {
|
||||
/**网元类型 */
|
||||
neType: string;
|
||||
name: string;
|
||||
path: string;
|
||||
version: string;
|
||||
description: string;
|
||||
};
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
/**上传文件 */
|
||||
uploadFiles: any[];
|
||||
/**预输入 */
|
||||
preinput: Record<string, any>;
|
||||
};
|
||||
|
||||
/**安装对象信息状态 */
|
||||
let installState: InstallStateType = reactive({
|
||||
setp: 'pkg',
|
||||
setpSSHArr: [],
|
||||
hostId: '',
|
||||
optionType: 'upload',
|
||||
from: {
|
||||
neType: '',
|
||||
name: '',
|
||||
path: '',
|
||||
version: '',
|
||||
description: '',
|
||||
},
|
||||
confirmLoading: false,
|
||||
uploadFiles: [],
|
||||
preinput: {
|
||||
// IMS
|
||||
pubIP: '192.168.5.57',
|
||||
mcc: '001',
|
||||
mnc: '01',
|
||||
priIP: '172.16.16.51',
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 表单修改网元类型
|
||||
*/
|
||||
function fnNeTypeChange(v: any) {
|
||||
tableState.queryParams.neType = v;
|
||||
if (installState.optionType === 'option') {
|
||||
fnGetList(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单修改文件操作类型
|
||||
*/
|
||||
function fnOptionTypeChange() {
|
||||
if (installState.optionType === 'upload') {
|
||||
installState.from.name = '';
|
||||
installState.from.path = '';
|
||||
installState.from.version = '';
|
||||
installState.from.description = '';
|
||||
}
|
||||
if (installState.optionType === 'option') {
|
||||
tableState.queryParams.neType = installState.from.neType;
|
||||
fnGetList(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**表单属性和校验规则 */
|
||||
const installStateFrom = Form.useForm(
|
||||
installState.from,
|
||||
reactive({
|
||||
neType: [
|
||||
{
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 32,
|
||||
message: t('views.configManage.softwareManage.neTypePlease'),
|
||||
},
|
||||
],
|
||||
version: [
|
||||
{
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 64,
|
||||
message: t('views.configManage.softwareManage.versionPlease'),
|
||||
},
|
||||
],
|
||||
path: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.configManage.softwareManage.updateFilePlease'),
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**表单上传前检查或转换压缩 */
|
||||
function fnBeforeUploadFile(file: FileType) {
|
||||
if (installState.confirmLoading) return false;
|
||||
const fileName = file.name;
|
||||
const suff = fileName.substring(fileName.lastIndexOf('.'));
|
||||
if (!['.deb', '.rpm'].includes(suff)) {
|
||||
message.error(
|
||||
t('views.configManage.softwareManage.onlyAble', {
|
||||
fileText: '(.deb、.rpm)',
|
||||
}),
|
||||
3
|
||||
);
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
// 根据给定的软件名取版本号 ims-r2.2312.x-ub22.deb
|
||||
const matches = fileName.match(/([0-9.]+[0-9x]+)/);
|
||||
if (matches) {
|
||||
installState.from.version = matches[0];
|
||||
}
|
||||
const neTypeIndex = fileName.indexOf('-');
|
||||
if (neTypeIndex !== -1) {
|
||||
const neType = fileName.substring(0, neTypeIndex).toUpperCase();
|
||||
if (installState.from.neType !== neType) {
|
||||
message.error('请上传对应网元类型的安装包', 3);
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**表单上传文件 */
|
||||
function fnUploadFile(up: UploadRequestOption) {
|
||||
// 发送请求
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
installState.confirmLoading = true;
|
||||
uploadFileChunk(up.file as File, 5, 'software')
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success('上传成功', 3);
|
||||
// 改为完成状态
|
||||
const file = installState.uploadFiles[0];
|
||||
file.percent = 100;
|
||||
file.status = 'done';
|
||||
// 预置到表单
|
||||
const { fileName, originalFileName } = res.data;
|
||||
installState.from.name = originalFileName;
|
||||
installState.from.path = fileName;
|
||||
} else {
|
||||
message.error(res.msg, 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
installState.confirmLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**软件包运行检查 */
|
||||
function fnRunCheck() {
|
||||
if (installState.confirmLoading) return;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
installStateFrom
|
||||
.validate()
|
||||
.then(() => {
|
||||
const form = toRaw(installState.from);
|
||||
Object.assign(form, { hostId: installState.hostId });
|
||||
installState.confirmLoading = true;
|
||||
return checkInstallNeSoftware(form);
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
installState.setpSSHArr = res.data;
|
||||
installState.setp = 'preinput';
|
||||
} else {
|
||||
message.error(res.msg, 3);
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
installState.confirmLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**安装检查预输入 */
|
||||
function fnInstallPreinput() {
|
||||
if (installState.confirmLoading) return;
|
||||
// IMS
|
||||
if (installState.from.neType === 'IMS') {
|
||||
const modipplmn = installState.setpSSHArr[1];
|
||||
if (modipplmn.includes('modipplmn.sh')) {
|
||||
installState.setpSSHArr[1] = modipplmn
|
||||
.replace('{PUBIP}', installState.preinput.pubIP)
|
||||
.replace('{MCC}', installState.preinput.mcc)
|
||||
.replace('{MNC}', installState.preinput.mnc);
|
||||
}
|
||||
const modintraip = installState.setpSSHArr[2];
|
||||
if (modintraip.includes('modintraip.sh')) {
|
||||
installState.setpSSHArr[2] = modintraip.replace(
|
||||
'{PRIIP}',
|
||||
installState.preinput.priIP
|
||||
);
|
||||
}
|
||||
}
|
||||
// 其他
|
||||
//
|
||||
|
||||
installState.setp = 'ssh';
|
||||
}
|
||||
|
||||
/**安装终端 */
|
||||
const installTerminal = ref();
|
||||
|
||||
/**
|
||||
* 终端连接状态
|
||||
* @param data 主机连接结果
|
||||
*/
|
||||
function fnTerminalConnect(data: Record<string, any>) {
|
||||
console.log('fnTerminalConnect', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 终端消息数据
|
||||
* @param data 主机连接结果
|
||||
*/
|
||||
function fnTerminalMessage(res: Record<string, any>) {
|
||||
const { code, requestId, data } = res;
|
||||
if (code === RESULT_CODE_ERROR) {
|
||||
console.warn(res.msg);
|
||||
return;
|
||||
}
|
||||
console.log('fnTerminalMessage', data);
|
||||
|
||||
// 安装遇到问题
|
||||
if (data.includes('Errors were encountered while processing:')) {
|
||||
installTerminal.value.send('logout');
|
||||
return;
|
||||
}
|
||||
|
||||
// 安装成功后退出
|
||||
if (data.includes('software install successful')) {
|
||||
installTerminal.value.send('logout');
|
||||
message.success('软件安装成功', 3);
|
||||
// 记录当前步骤状态信息
|
||||
stepState.states[stepState.current] = { from: {} };
|
||||
stepState.stepNext = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// IMS预输入
|
||||
if (data.includes('(P/I/S-CSCF Config)? <y/n>')) {
|
||||
installTerminal.value.send(installState.preinput.pisCSCF);
|
||||
return;
|
||||
}
|
||||
|
||||
// 命令结束后继续输入命令
|
||||
if (data.endsWith('$ ')) {
|
||||
console.log('结束');
|
||||
const cmdStr = installState.setpSSHArr.shift();
|
||||
if (cmdStr) {
|
||||
installTerminal.value.send(cmdStr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 终端关闭状态
|
||||
* @param data 主机连接结果
|
||||
*/
|
||||
function fnTerminalClose(data: Record<string, any>) {
|
||||
console.log('fnTerminalClose', data);
|
||||
}
|
||||
|
||||
/**终端重新安装 */
|
||||
function fnTerminalReset() {
|
||||
installState.setp = 'pkg';
|
||||
installState.optionType = 'option';
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 读取步骤:网元信息
|
||||
const stepPrevFrom = stepState.states[1].from;
|
||||
installState.from.neType = stepPrevFrom.neType;
|
||||
installState.hostId = stepPrevFrom.hostIds.split(',')[0];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-form
|
||||
name="installStateFrom"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 3 }"
|
||||
:wrapper-col="{ span: 8 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<div>---- 安装软件包</div>
|
||||
|
||||
<template v-if="installState.setp === 'pkg'">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.softwareManage.neType')"
|
||||
name="neType"
|
||||
v-bind="installStateFrom.validateInfos.neType"
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="installState.from.neType"
|
||||
:disabled="true"
|
||||
:options="NE_TYPE_LIST.map(v => ({ value: v }))"
|
||||
@change="fnNeTypeChange"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="32"
|
||||
:disabled="true"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
{{ t('views.configManage.neManage.neTypeTip') }}
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-auto-complete>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="软件来源" name="optionType">
|
||||
<a-radio-group
|
||||
v-model:value="installState.optionType"
|
||||
button-style="solid"
|
||||
@change="fnOptionTypeChange"
|
||||
:disabled="installState.confirmLoading"
|
||||
>
|
||||
<a-radio-button value="upload">新上传</a-radio-button>
|
||||
<a-radio-button value="option">已上传</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 重新上传 -->
|
||||
<template v-if="installState.optionType === 'upload'">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.softwareManage.version')"
|
||||
name="version"
|
||||
v-bind="installStateFrom.validateInfos.version"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="installState.from.version"
|
||||
allow-clear
|
||||
:placeholder="t('views.configManage.softwareManage.versionPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('views.configManage.softwareManage.updateComment')"
|
||||
name="comment"
|
||||
v-bind="installStateFrom.validateInfos.comment"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="installState.from.description"
|
||||
:auto-size="{ minRows: 1, maxRows: 4 }"
|
||||
:maxlength="500"
|
||||
:show-count="true"
|
||||
:placeholder="
|
||||
t('views.configManage.softwareManage.updateCommentPlease')
|
||||
"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('views.configManage.softwareManage.updateFile')"
|
||||
name="file"
|
||||
v-bind="installStateFrom.validateInfos.path"
|
||||
>
|
||||
<a-upload
|
||||
name="file"
|
||||
v-model:file-list="installState.uploadFiles"
|
||||
accept=".rpm,.deb"
|
||||
list-type="text"
|
||||
:max-count="1"
|
||||
:show-upload-list="{
|
||||
showPreviewIcon: false,
|
||||
showRemoveIcon: false,
|
||||
showDownloadIcon: false,
|
||||
}"
|
||||
:before-upload="fnBeforeUploadFile"
|
||||
:custom-request="fnUploadFile"
|
||||
:disabled="installState.confirmLoading"
|
||||
>
|
||||
<a-button type="default" :disabled="installState.confirmLoading">
|
||||
{{ t('views.configManage.softwareManage.selectFile') }}
|
||||
</a-button>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 选择已上传 -->
|
||||
<template v-if="installState.optionType === 'option'">
|
||||
<a-form-item label="选择记录" name="option" :wrapper-col="{ span: 24 }">
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:pagination="tablePagination"
|
||||
size="small"
|
||||
:scroll="{ x: tableColumns.length * 100, y: '400px' }"
|
||||
:row-selection="{
|
||||
type: 'radio',
|
||||
columnWidth: '48px',
|
||||
selectedRowKeys: tableState.selectedRowKeys,
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'fileName'">
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>{{ record.path }}</template>
|
||||
<div style="cursor: pointer">{{ record.fileName }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.key === 'comment'">
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>{{ record.comment }}</template>
|
||||
<div style="cursor: pointer">{{ record.comment }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<a-form-item :wrapper-col="{ span: 14, offset: 3 }">
|
||||
<a-button
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
@click="fnRunCheck()"
|
||||
:loading="installState.confirmLoading"
|
||||
>
|
||||
安装检查
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<div>--- 安装前预输入</div>
|
||||
|
||||
<template v-if="installState.setp === 'preinput'">
|
||||
<a-form-item
|
||||
name="info"
|
||||
label="安装前预输入"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<div style="align-items: center">-----</div>
|
||||
</a-form-item>
|
||||
|
||||
<!-- IMS 预输入 -->
|
||||
<template v-if="installState.from.neType === 'IMS'">
|
||||
<a-form-item label="P/I/S-CSCF Config" name="pisCSCF">
|
||||
<a-input
|
||||
v-model:value="installState.preinput.pisCSCF"
|
||||
allow-clear
|
||||
placeholder="P/I/S-CSCF Config"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="modipplmn IP" name="pubIP">
|
||||
<a-input
|
||||
v-model:value="installState.preinput.pubIP"
|
||||
allow-clear
|
||||
placeholder="IMS modipplmn IP"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="modipplmn mcc" name="mcc">
|
||||
<a-input
|
||||
v-model:value="installState.preinput.mcc"
|
||||
allow-clear
|
||||
placeholder="IMS modipplmn mcc"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="modipplmn mnc" name="mnc">
|
||||
<a-input
|
||||
v-model:value="installState.preinput.mnc"
|
||||
allow-clear
|
||||
placeholder="IMS modipplmn mnc"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="modintraip priIP" name="priIP">
|
||||
<a-input
|
||||
v-model:value="installState.preinput.priIP"
|
||||
allow-clear
|
||||
placeholder="IMS modintraip priIP"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<a-form-item :wrapper-col="{ span: 14, offset: 3 }">
|
||||
<a-button
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
@click="fnInstallPreinput()"
|
||||
:loading="installState.confirmLoading"
|
||||
>
|
||||
开始安装
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<div>---- 安装进行信息</div>
|
||||
|
||||
<template v-if="installState.setp === 'ssh'">
|
||||
<a-form-item
|
||||
name="info"
|
||||
label="安装日志"
|
||||
:label-col="{ span: 3 }"
|
||||
:wrapper-col="{ span: 24 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<TerminalSSH
|
||||
ref="installTerminal"
|
||||
:id="installState.hostId"
|
||||
:hostId="installState.hostId"
|
||||
init-cmd="clear"
|
||||
@connect="fnTerminalConnect"
|
||||
@message="fnTerminalMessage"
|
||||
@close="fnTerminalClose"
|
||||
>
|
||||
</TerminalSSH>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :wrapper-col="{ span: 14, offset: 3 }">
|
||||
<a-button
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
@click="fnTerminalReset()"
|
||||
:loading="installState.confirmLoading"
|
||||
>
|
||||
返回重新选择安装
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
460
src/views/tool/neQuickSetup/components/StepNeInfo.vue
Normal file
460
src/views/tool/neQuickSetup/components/StepNeInfo.vue
Normal file
@@ -0,0 +1,460 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw } from 'vue';
|
||||
import { message, Form, Modal } from 'ant-design-vue/lib';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import { addNeInfo, updateNeInfo, getTypeAndIDNeInfo } from '@/api/ne/neInfo';
|
||||
import { NE_TYPE_LIST } from '@/constants/ne-constants';
|
||||
import { stepState } from '../hooks/useStep';
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(['next']);
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**新增框或修改框是否显示 */
|
||||
visibleByEdit: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
visibleByEdit: false,
|
||||
title: '网元',
|
||||
from: {
|
||||
id: undefined,
|
||||
neId: '001',
|
||||
neType: 'OMC',
|
||||
neName: '',
|
||||
ip: '',
|
||||
port: 3030,
|
||||
pvFlag: 'PNF',
|
||||
rmUid: '4400HX1OMC001',
|
||||
neAddress: '',
|
||||
dn: '',
|
||||
vendorName: '',
|
||||
province: '',
|
||||
// 主机
|
||||
hosts: [
|
||||
{
|
||||
hostId: undefined,
|
||||
hostType: 'ssh',
|
||||
groupId: '1',
|
||||
title: 'SSH_NE_22',
|
||||
addr: '',
|
||||
port: 22,
|
||||
user: 'user',
|
||||
authMode: '0',
|
||||
password: 'user',
|
||||
privateKey: '',
|
||||
passPhrase: '',
|
||||
remark: '',
|
||||
},
|
||||
{
|
||||
hostId: undefined,
|
||||
hostType: 'telnet',
|
||||
groupId: '1',
|
||||
title: 'Telnet_NE_4100',
|
||||
addr: '',
|
||||
port: 4100,
|
||||
user: 'user',
|
||||
authMode: '0',
|
||||
password: 'user',
|
||||
remark: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**对话框内表单属性和校验规则 */
|
||||
const modalStateFrom = Form.useForm(
|
||||
modalState.from,
|
||||
reactive({
|
||||
neType: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入网元类型',
|
||||
},
|
||||
],
|
||||
neId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入网元标识',
|
||||
},
|
||||
],
|
||||
rmUid: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入资源唯一标识',
|
||||
},
|
||||
],
|
||||
ip: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入网元IP地址',
|
||||
},
|
||||
],
|
||||
neName: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入网元名称',
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**测试连接检查信息 */
|
||||
function fnNeInfo() {
|
||||
const from = toRaw(modalState.from);
|
||||
modalStateFrom
|
||||
.validate()
|
||||
.then(e => {
|
||||
modalState.confirmLoading = true;
|
||||
return getTypeAndIDNeInfo(from.neType, from.neId);
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.warning({
|
||||
content: `${from.neType} 已存在网元标识:${from.neId} ,资源唯一标识:${from.rmUid}`,
|
||||
duration: 3,
|
||||
});
|
||||
from.id = res.data.id;
|
||||
from.hostIds = res.data.hostIds;
|
||||
const hostIds = res.data.hostIds.split(',');
|
||||
if (hostIds.length == 2) {
|
||||
from.hosts[0].hostId = hostIds[0];
|
||||
from.hosts[1].hostId = hostIds[1];
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
message.success({
|
||||
content: `${from.neType} 可使用网元标识:${from.neId}`,
|
||||
duration: 3,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.then(state => {
|
||||
let confirmTitle = '新增提示';
|
||||
let confirmContent = '是否新增为新的网元信息并继续?';
|
||||
if (state) {
|
||||
confirmTitle = '更新提示';
|
||||
confirmContent = '是否更新替换已存在网元信息并继续?';
|
||||
}
|
||||
Modal.confirm({
|
||||
title: confirmTitle,
|
||||
content: confirmContent,
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
onCancel: () => {
|
||||
from.id = undefined;
|
||||
},
|
||||
onOk: () => {
|
||||
const result = from.id ? updateNeInfo(from) : addNeInfo(from);
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
result
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: '操作成功',
|
||||
duration: 3,
|
||||
});
|
||||
// 刷新缓存的网元信息
|
||||
useNeInfoStore()
|
||||
.fnRefreshNelist()
|
||||
.then(neRes => {
|
||||
const itemNe = neRes.data.find(
|
||||
(item: any) =>
|
||||
item.neType === from.neType && item.neId === from.neId
|
||||
);
|
||||
if (itemNe) {
|
||||
Object.assign(from, itemNe);
|
||||
// 记录当前步骤状态信息
|
||||
stepState.states[stepState.current] = { from };
|
||||
stepState.stepNext = true;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
message.error({
|
||||
content: `${t('views.configManage.neManage.operFail')}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
|
||||
})
|
||||
.finally(() => {
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 读取步骤:环境检查
|
||||
const stepPrevFrom = stepState.states[0].from;
|
||||
modalState.from.ip = stepPrevFrom.addr;
|
||||
Object.assign(modalState.from.hosts[0], stepPrevFrom);
|
||||
Object.assign(modalState.from.hosts[1], {
|
||||
addr: stepPrevFrom.addr,
|
||||
user: 'admin',
|
||||
password: 'admin',
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-form
|
||||
name="modalStateFrom"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 6 }"
|
||||
:labelWrap="true"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.neManage.neType')"
|
||||
name="neType"
|
||||
v-bind="modalStateFrom.validateInfos.neType"
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="modalState.from.neType"
|
||||
:options="NE_TYPE_LIST.map(v => ({ value: v }))"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="32"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
{{ t('views.configManage.neManage.neTypeTip') }}
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-auto-complete>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.neManage.pvflag')"
|
||||
name="pvFlag"
|
||||
v-bind="modalStateFrom.validateInfos.pvFlag"
|
||||
>
|
||||
<a-select v-model:value="modalState.from.pvFlag" default-value="PNF">
|
||||
<a-select-opt-group :label="t('views.configManage.neManage.pnf')">
|
||||
<a-select-option value="PNF">PNF</a-select-option>
|
||||
</a-select-opt-group>
|
||||
<a-select-opt-group :label="t('views.configManage.neManage.vnf')">
|
||||
<a-select-option value="VNF">VNF</a-select-option>
|
||||
</a-select-opt-group>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.neManage.neId')"
|
||||
name="neId"
|
||||
v-bind="modalStateFrom.validateInfos.neId"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.neId"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="32"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.neManage.neName')"
|
||||
name="neName"
|
||||
v-bind="modalStateFrom.validateInfos.neName"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.neName"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="64"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.neManage.ip')"
|
||||
name="ip"
|
||||
v-bind="modalStateFrom.validateInfos.ip"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.ip"
|
||||
allow-clear
|
||||
:disabled="true"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="128"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
<div>
|
||||
{{ t('views.ne.neInfo.ipAddr') }}
|
||||
</div>
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.neManage.port')"
|
||||
name="port"
|
||||
v-bind="modalStateFrom.validateInfos.port"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="modalState.from.port"
|
||||
style="width: 100%"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
placeholder="<=65535"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
<div>{{ t('views.configManage.neManage.portTip') }}</div>
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.configManage.neManage.uid')"
|
||||
name="rmUid"
|
||||
v-bind="modalStateFrom.validateInfos.rmUid"
|
||||
:label-col="{ span: 3 }"
|
||||
:labelWrap="true"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.rmUid"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="40"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
<div>
|
||||
{{ t('views.ne.neInfo.rmUID') }}
|
||||
</div>
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.neManage.mac')"
|
||||
name="neAddress"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.neAddress"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="64"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
<div>{{ t('views.configManage.neManage.macTip') }}</div>
|
||||
</template>
|
||||
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.configManage.neManage.dn')" name="dn">
|
||||
<a-input
|
||||
v-model:value="modalState.from.dn"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="255"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.neManage.vendorName')"
|
||||
name="vendorName"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.vendorName"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="64"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.neManage.province')"
|
||||
name="province"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.province"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="32"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item :wrapper-col="{ span: 14, offset: 3 }">
|
||||
<a-button
|
||||
type="primary"
|
||||
html-type="submit"
|
||||
@click="fnNeInfo()"
|
||||
:loading="modalState.confirmLoading"
|
||||
>
|
||||
检查信息
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
59
src/views/tool/neQuickSetup/hooks/useStep.ts
Normal file
59
src/views/tool/neQuickSetup/hooks/useStep.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { reactive } from 'vue';
|
||||
|
||||
/**步骤信息状态类型 */
|
||||
type StepStateType = {
|
||||
/**当前选中 */
|
||||
current: number;
|
||||
/**步骤项 */
|
||||
steps: {
|
||||
title: string;
|
||||
description: string;
|
||||
}[];
|
||||
/**步骤下一步 */
|
||||
stepNext: boolean;
|
||||
/**步骤信息状态 */
|
||||
states: any[];
|
||||
};
|
||||
|
||||
/**步骤信息状态 */
|
||||
export const stepState: StepStateType = reactive({
|
||||
current: 0,
|
||||
steps: [
|
||||
{
|
||||
title: '环境检查',
|
||||
description: '服务器检查,触发免密脚本',
|
||||
},
|
||||
{
|
||||
title: '网元信息',
|
||||
description: '记录下所安装网元基础信息',
|
||||
},
|
||||
{
|
||||
title: '网元安装',
|
||||
description: '安装包上传执行安装启动服务等待10秒停止服务',
|
||||
},
|
||||
{
|
||||
title: '网元配置',
|
||||
description: '修改网元的配置文件',
|
||||
},
|
||||
{
|
||||
title: '网元激活',
|
||||
description: '获取激活码和上传授权文件,启动验证激活码',
|
||||
},
|
||||
{
|
||||
title: '完成安装',
|
||||
description: '获取网元服务状态',
|
||||
},
|
||||
],
|
||||
stepNext: false,
|
||||
states: [],
|
||||
});
|
||||
|
||||
export function fnStepNext() {
|
||||
stepState.current++;
|
||||
stepState.stepNext = false;
|
||||
}
|
||||
|
||||
export function fnStepPrev() {
|
||||
stepState.current--;
|
||||
stepState.stepNext = true;
|
||||
}
|
||||
85
src/views/tool/neQuickSetup/index.vue
Normal file
85
src/views/tool/neQuickSetup/index.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<script lang="ts" setup>
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { message } from 'ant-design-vue/lib';
|
||||
import StepCheck from './components/StepCheck.vue';
|
||||
import StepNeInfo from './components/StepNeInfo.vue';
|
||||
import StepInstall from './components/StepInstall.vue';
|
||||
import StepConfig from './components/StepConfig.vue';
|
||||
import StepActivate from './components/StepActivate.vue';
|
||||
import StepFinish from './components/StepFinish.vue';
|
||||
import { stepState, fnStepPrev, fnStepNext } from './hooks/useStep';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<!-- 步骤进度 -->
|
||||
<a-card :bordered="false" :body-style="{ marginBottom: '24px' }">
|
||||
<a-steps :current="stepState.current" direction="horizontal">
|
||||
<a-step
|
||||
v-for="s in stepState.steps"
|
||||
:key="s.title"
|
||||
:title="s.title"
|
||||
:description="s.description"
|
||||
:disabled="true"
|
||||
/>
|
||||
</a-steps>
|
||||
</a-card>
|
||||
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:body-style="{ padding: '12px', minHeight: '450px' }"
|
||||
>
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<div>
|
||||
{{ stepState.steps[stepState.current].title }}
|
||||
Check, NeInfo, Install, Config, Activate, Finish
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button
|
||||
v-if="stepState.current > 0"
|
||||
style="margin-left: 8px"
|
||||
@click="fnStepPrev()"
|
||||
>
|
||||
上一步
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="stepState.current < stepState.steps.length - 1"
|
||||
type="primary"
|
||||
:disabled="!stepState.stepNext"
|
||||
@click="fnStepNext()"
|
||||
>
|
||||
下一步
|
||||
</a-button>
|
||||
<a-button
|
||||
v-if="stepState.current == stepState.steps.length - 1"
|
||||
type="primary"
|
||||
@click="message.success('Processing complete!')"
|
||||
>
|
||||
完成
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 步骤页面 -->
|
||||
<StepCheck v-if="stepState.current === 0"></StepCheck>
|
||||
<StepNeInfo v-if="stepState.current === 1"></StepNeInfo>
|
||||
<StepInstall v-if="stepState.current === 2"></StepInstall>
|
||||
<StepConfig v-if="stepState.current === 3"></StepConfig>
|
||||
<StepActivate v-if="stepState.current === 4"></StepActivate>
|
||||
<StepFinish v-if="stepState.current === 5"></StepFinish>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.pane-box {
|
||||
padding: 16px;
|
||||
height: calc(100vh - 320px);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
</style>
|
||||
395
src/views/tool/terminal/index.vue
Normal file
395
src/views/tool/terminal/index.vue
Normal file
@@ -0,0 +1,395 @@
|
||||
<script lang="ts" setup>
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { Modal } from 'ant-design-vue/lib';
|
||||
import TerminalSSH from '@/components/TerminalSSH/index.vue';
|
||||
import TerminalTelnet from '@/components/TerminalTelnet/index.vue';
|
||||
import { reactive, toRaw } from 'vue';
|
||||
import { parseDuration } from '@/utils/date-utils';
|
||||
import { listNeHost } from '@/api/ne/neHost';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { useRouter } from 'vue-router';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
|
||||
/**主机对象信息状态类型 */
|
||||
type HostStateType = {
|
||||
/**显示主机列表 */
|
||||
show: boolean;
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**查询参数 */
|
||||
params: {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
};
|
||||
/**数据总数 */
|
||||
total: number;
|
||||
data: Record<string, any>[];
|
||||
};
|
||||
|
||||
/**主机对象信息状态 */
|
||||
const hostState: HostStateType = reactive({
|
||||
show: false,
|
||||
loading: false,
|
||||
params: {
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
},
|
||||
total: 0,
|
||||
data: [],
|
||||
});
|
||||
|
||||
/**查询主机信息列表, pageNum初始页数 */
|
||||
function fnGetHostList(pageNum?: number) {
|
||||
if (hostState.loading) return;
|
||||
hostState.loading = true;
|
||||
if (pageNum) {
|
||||
hostState.params.pageNum = pageNum;
|
||||
}
|
||||
// 超过总数不请求
|
||||
if (hostState.data.length >= hostState.total && hostState.total !== 0) {
|
||||
hostState.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
listNeHost(toRaw(hostState.params)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||
hostState.total = res.total;
|
||||
hostState.data = hostState.data.concat(res.rows);
|
||||
// 页数+
|
||||
if (hostState.data.length < hostState.total) {
|
||||
hostState.params.pageNum += 1;
|
||||
}
|
||||
}
|
||||
hostState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**选择主机 */
|
||||
function fnSelectHost() {
|
||||
if (hostState.loading) return;
|
||||
hostState.show = true;
|
||||
fnGetHostList(1);
|
||||
}
|
||||
|
||||
/**连接主机 */
|
||||
function fnConnectHost(data: Record<string, any>) {
|
||||
const id = `${Date.now()}`;
|
||||
tabState.panes.push({
|
||||
id,
|
||||
status: false,
|
||||
host: data,
|
||||
});
|
||||
tabState.activeKey = id;
|
||||
}
|
||||
|
||||
/**标签对象信息状态类型 */
|
||||
type TabStateType = {
|
||||
/**激活选中 */
|
||||
activeKey: string;
|
||||
/**页签数据 */
|
||||
panes: {
|
||||
id: string;
|
||||
status: boolean;
|
||||
host: Record<string, any>;
|
||||
connectStamp?: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
/**标签对象信息状态 */
|
||||
const tabState: TabStateType = reactive({
|
||||
activeKey: '0',
|
||||
panes: [
|
||||
{
|
||||
id: '0',
|
||||
host: {
|
||||
hostId: '0',
|
||||
title: t('views.tool.terminal.start'),
|
||||
type: '0',
|
||||
},
|
||||
status: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* 终端连接状态
|
||||
* @param data 主机连接结果
|
||||
*/
|
||||
function fnTerminalConnect(data: Record<string, any>) {
|
||||
const { id, timeStamp } = data;
|
||||
const seconds = timeStamp / 1000;
|
||||
// 获取当前项下标
|
||||
const tab = tabState.panes.find(item => item.id === id);
|
||||
if (tab) {
|
||||
tab.status = true;
|
||||
tab.connectStamp = parseDuration(seconds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 终端关闭状态
|
||||
* @param data 主机连接结果
|
||||
*/
|
||||
function fnTerminalClose(data: Record<string, any>) {
|
||||
const { id } = data;
|
||||
// 获取当前项下标
|
||||
const tab = tabState.panes.find(item => item.id === id);
|
||||
if (tab) {
|
||||
tab.status = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 标签更多菜单项
|
||||
* @param key 菜单key
|
||||
*/
|
||||
function fnTabMenu(key: string | number) {
|
||||
// 跳转主机创建
|
||||
if (key === 'new') {
|
||||
router.push({ name: 'NeHost_2135' });
|
||||
}
|
||||
// 刷新当前
|
||||
if (key === 'reload') {
|
||||
const tabIndex = tabState.panes.findIndex(
|
||||
item => item.id === tabState.activeKey
|
||||
);
|
||||
if (tabIndex) {
|
||||
const tab = tabState.panes[tabIndex];
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.tool.terminal.reloadTip', {
|
||||
num: `${tab.host.hostType} - ${tab.host.title}`,
|
||||
}),
|
||||
onOk() {
|
||||
tabState.panes.splice(tabIndex, 1);
|
||||
tab.host && fnConnectHost(tab.host);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
// 关闭当前
|
||||
if (key === 'current') {
|
||||
fnTabClose(tabState.activeKey);
|
||||
}
|
||||
// 关闭其他
|
||||
if (key === 'other') {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.tool.terminal.otherTip'),
|
||||
onOk() {
|
||||
hostState.show = false;
|
||||
tabState.panes = tabState.panes.filter(
|
||||
tab => tab.id === '0' || tab.id === tabState.activeKey
|
||||
);
|
||||
tabState.activeKey = tabState.activeKey;
|
||||
},
|
||||
});
|
||||
}
|
||||
// 关闭全部
|
||||
if (key === 'all') {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.tool.terminal.allTip'),
|
||||
onOk() {
|
||||
hostState.show = false;
|
||||
tabState.panes.splice(1);
|
||||
tabState.activeKey = '0';
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导航标签关闭
|
||||
* @param id 标签的key
|
||||
*/
|
||||
function fnTabClose(id: string) {
|
||||
// 获取当前项下标
|
||||
const tabIndex = tabState.panes.findIndex(tab => tab.id === id);
|
||||
if (tabIndex === -1) return;
|
||||
const item = tabState.panes[tabIndex];
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.tool.terminal.closeTip', {
|
||||
num: `${item.host.hostType} - ${item.host.title}`,
|
||||
}),
|
||||
onOk() {
|
||||
tabState.panes.splice(tabIndex, 1);
|
||||
// 激活前一项标签
|
||||
tabState.activeKey = tabState.panes[tabIndex - 1].id;
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '12px' }">
|
||||
<a-tabs
|
||||
class="terminal-tabs"
|
||||
hide-add
|
||||
tab-position="top"
|
||||
type="editable-card"
|
||||
:tab-bar-gutter="8"
|
||||
:tab-bar-style="{ margin: '0' }"
|
||||
v-model:activeKey="tabState.activeKey"
|
||||
@edit="(hostId:any) => fnTabClose(hostId as string)"
|
||||
>
|
||||
<a-tab-pane
|
||||
v-for="pane in tabState.panes"
|
||||
:key="pane.id"
|
||||
:closable="tabState.panes.length > 1"
|
||||
>
|
||||
<template #tab>
|
||||
<a-badge
|
||||
:status="pane.status ? 'success' : 'error'"
|
||||
:text="pane.host.title"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<div class="pane-box">
|
||||
<!-- 非开始页的ssh主机 -->
|
||||
<TerminalSSH
|
||||
v-if="pane.id !== '0' && pane.host.hostType === 'ssh'"
|
||||
:id="pane.id"
|
||||
:hostId="pane.host.hostId"
|
||||
@connect="fnTerminalConnect"
|
||||
@close="fnTerminalClose"
|
||||
>
|
||||
</TerminalSSH>
|
||||
|
||||
<!-- 非开始页的telnet主机 -->
|
||||
<TerminalTelnet
|
||||
v-if="pane.id !== '0' && pane.host.hostType === 'telnet'"
|
||||
:id="pane.id"
|
||||
:hostId="pane.host.hostId"
|
||||
init-cmd="help"
|
||||
:disable="true"
|
||||
@connect="fnTerminalConnect"
|
||||
@close="fnTerminalClose"
|
||||
>
|
||||
</TerminalTelnet>
|
||||
|
||||
<!-- 开始页 -->
|
||||
<div v-if="pane.id === '0'">
|
||||
<!-- 提示选择主机 -->
|
||||
<a-result
|
||||
:title="t('views.tool.terminal.hostSelectTitle')"
|
||||
v-show="tabState.activeKey === '0' && !hostState.show"
|
||||
>
|
||||
<template #extra>
|
||||
<a-button
|
||||
key="hostState"
|
||||
type="primary"
|
||||
@click="fnSelectHost()"
|
||||
>
|
||||
{{ t('views.tool.terminal.hostSelectShow') }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-result>
|
||||
|
||||
<!-- 主机列表 -->
|
||||
<a-list
|
||||
v-show="tabState.activeKey === '0' && hostState.show"
|
||||
:header="t('views.tool.terminal.hostSelectHeader')"
|
||||
:grid="{ gutter: 16, column: 4, lg: 4, md: 2, xs: 1 }"
|
||||
:data-source="hostState.data"
|
||||
row-key="hostId"
|
||||
>
|
||||
<!-- 插槽-加载更多 -->
|
||||
<template #loadMore>
|
||||
<div style="text-align: center">
|
||||
<a-button
|
||||
@click="fnGetHostList()"
|
||||
:loading="hostState.loading"
|
||||
>
|
||||
{{
|
||||
t('views.tool.terminal.hostSelectMore', {
|
||||
num: hostState.total - hostState.data.length,
|
||||
})
|
||||
}}
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 插槽-渲染项 -->
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<a-card size="small" :title="item.title">
|
||||
<template #extra>
|
||||
<a-button
|
||||
type="primary"
|
||||
shape="round"
|
||||
size="small"
|
||||
@click="fnConnectHost(item)"
|
||||
>
|
||||
<template #icon><LinkOutlined /></template>
|
||||
{{ item.hostType.toUpperCase() }}
|
||||
</a-button>
|
||||
</template>
|
||||
<div>{{ `${item.addr}:${item.port}` }}</div>
|
||||
</a-card>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</div>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
|
||||
<template #rightExtra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>
|
||||
{{ t('views.tool.terminal.new') }}
|
||||
</template>
|
||||
<a-button type="default" shape="circle" @click="fnTabMenu('new')">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<!-- 非开始页的更多操作 -->
|
||||
<div v-show="tabState.activeKey !== '0'">
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>
|
||||
{{ t('views.tool.terminal.more') }}
|
||||
</template>
|
||||
<a-dropdown
|
||||
:trigger="['click', 'hover']"
|
||||
placement="bottomRight"
|
||||
>
|
||||
<a-button type="ghost" shape="circle">
|
||||
<template #icon><EllipsisOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu @click="({ key }:any) => fnTabMenu(key)">
|
||||
<a-menu-item key="reload">
|
||||
{{ t('views.tool.terminal.reload') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="current">
|
||||
{{ t('views.tool.terminal.current') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="other">
|
||||
{{ t('views.tool.terminal.other') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="all">
|
||||
{{ t('views.tool.terminal.all') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.pane-box {
|
||||
padding: 16px;
|
||||
height: calc(100vh - 320px);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -4,7 +4,7 @@ import { message, Modal } from 'ant-design-vue/lib';
|
||||
import { ColumnsType } from 'ant-design-vue/lib/table';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { dumpStart, dumpStop, traceUPF } from '@/api/traceManage/pcap';
|
||||
import { listNe } from '@/api/ne/ne';
|
||||
import { listAllNeInfo } from '@/api/ne/neInfo';
|
||||
import { getNeFile } from '@/api/tool/neFile';
|
||||
import saveAs from 'file-saver';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
@@ -127,7 +127,7 @@ function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
function fnGetList() {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
listNe({
|
||||
listAllNeInfo({
|
||||
bandStatus: false,
|
||||
}).then(res => {
|
||||
if (
|
||||
|
||||
@@ -14,20 +14,20 @@ export default defineConfig(({ mode }) => {
|
||||
base: env.VITE_HISTORY_BASE_URL,
|
||||
// 本地开发服务配置
|
||||
server: {
|
||||
port: 3131, // 端口
|
||||
port: 33020, // 端口
|
||||
host: true, // 暴露到网络地址
|
||||
open: false, // 完成后自动跳转浏览器打开
|
||||
proxy: {
|
||||
// https://cn.vitejs.dev/config/#server-proxy
|
||||
[env.VITE_API_BASE_URL]: {
|
||||
// target: 'http://192.168.2.166:3030',
|
||||
target: 'http://192.168.5.58:3040',
|
||||
target: 'http://192.168.5.58:33040',
|
||||
changeOrigin: true,
|
||||
rewrite: p => p.replace(env.VITE_API_BASE_URL, ''),
|
||||
},
|
||||
// 代理 websockets
|
||||
'/socket': {
|
||||
target: 'ws://192.168.5.58:3040',
|
||||
target: 'ws://192.168.5.58:33040',
|
||||
changeOrigin: true,
|
||||
rewrite: p => p.replace('/socket', ''),
|
||||
ws: true,
|
||||
|
||||
Reference in New Issue
Block a user