1 Commits

Author SHA1 Message Date
TsMask
90395e7464 ref: 重构获取网元状态信息 2025-10-27 15:16:30 +08:00
30 changed files with 686 additions and 3383 deletions

View File

@@ -15,7 +15,7 @@
const protocol = window.location.protocol const protocol = window.location.protocol
let wsprotocol = "ws:" let wsprotocol = "ws:"
const hostname = window.location.hostname const hostname = window.location.hostname
let host = `${hostname}:33080`; let host = `${hostname}:33030`;
if (protocol === 'https:') { if (protocol === 'https:') {
host = `${hostname}:33443`; host = `${hostname}:33443`;
wsprotocol = "wss:" wsprotocol = "wss:"

View File

@@ -1,2 +1,2 @@
imsi,msisdn,sess_rules,pcc_rules,hdr_enrich,rfsp,sar,qos_audio,qos_video #imsi,msisdn,sess_rules,pcc_rules,hdr_enrich,rfsp,sar,qos_audio,qos_video,online,offline
001012082101039,1234,internet|ims_sig,internet|ims_sig,321321,255,321312,32131,32131 460996650000580,62357000580,internet|ims_sig,internet|ims_sig,dnn,1,def_sar,qos_audio,qos_video,0,0

View File

@@ -1,2 +1,2 @@
001011100001157,1234567890ABCDEF1234567890ABCDEF,0,8000,11111111111111111111111111111111 460996650000580,1234567890ABCDEF1234567890ABCDEF,0,8000
001011100001158,1234567890ABCDEF1234567890ABCDEF,0,8000,11111111111111111111111111111111 460996650000581,1234567890ABCDEF1234567890ABCDEF,0,8000

View File

@@ -1,2 +1,2 @@
001011100001157,62357000583,def_ambr,def_nssai,def_arfb,def_sar,0,3,def_snssai,1-000001&internet&ims,1,64,24,65,def_eps,1,010200000000,- 460996650000580,62357000580,def_ambr,def_nssai,def_arfb,def_sar,0,3,def_snssai,1-000001&content&ims,1,64,24,65,def_eps,1,010200000000,-
001011100001158,62357000585,def_ambr,def_nssai,def_arfb,def_sar,0,3,def_snssai,1-000001&internet&ims,1,64,24,65,def_eps,1,010200000000,- 460996650000581,62357000581,def_ambr,def_nssai,def_arfb,def_sar,0,3,def_snssai,1-000001&content&ims,1,64,24,65,def_eps,1,010200000000,-

View File

@@ -66,14 +66,14 @@ router.beforeEach(async (to, from, next) => {
await appStore.fnSysConf(); await appStore.fnSysConf();
} }
// // 需要系统引导跳转 // 需要系统引导跳转
// if (appStore.bootloader && to.path !== '/quick-start') { if (appStore.bootloader && to.path !== '/quick-start') {
// next({ name: 'QuickStart' }); next({ name: 'QuickStart' });
// } }
// // 不重复引导 // 不重复引导
// if (!appStore.bootloader && to.path === '/quick-start') { if (!appStore.bootloader && to.path === '/quick-start') {
// next({ name: 'Index' }); next({ name: 'Index' });
// } }
let token = getAccessToken(); let token = getAccessToken();

View File

@@ -22,7 +22,7 @@ type AppStore = {
/**版本号 */ /**版本号 */
version: string; version: string;
/**系统引导使用 */ /**系统引导使用 */
// bootloader: boolean; bootloader: boolean;
/**服务版本 */ /**服务版本 */
serverVersion: string; serverVersion: string;
// 用户登录认证 // 用户登录认证
@@ -59,7 +59,7 @@ const useAppStore = defineStore('app', {
appVersion: import.meta.env.VITE_APP_VERSION, appVersion: import.meta.env.VITE_APP_VERSION,
version: '-', version: '-',
// bootloader: false, bootloader: false,
serverVersion: '-', serverVersion: '-',
loginAuth: true, loginAuth: true,
cryptoApi: true, cryptoApi: true,
@@ -91,12 +91,12 @@ const useAppStore = defineStore('app', {
if (res.code === RESULT_CODE_SUCCESS && res.data) { if (res.code === RESULT_CODE_SUCCESS && res.data) {
this.version = res.data.version; this.version = res.data.version;
this.serverVersion = res.data.serverVersion; this.serverVersion = res.data.serverVersion;
// this.bootloader = res.data.bootloader === 'true'; this.bootloader = res.data.bootloader === 'true';
// // 引导时 // 引导时
// if (this.bootloader) { if (this.bootloader) {
// delAccessToken(); delAccessToken();
// delRefreshToken(); delRefreshToken();
// } }
this.loginAuth = res.data.loginAuth !== 'false'; this.loginAuth = res.data.loginAuth !== 'false';
this.cryptoApi = res.data.cryptoApi !== 'false'; this.cryptoApi = res.data.cryptoApi !== 'false';
sessionSet(CACHE_SESSION_CRYPTO_API, res.data.cryptoApi); sessionSet(CACHE_SESSION_CRYPTO_API, res.data.cryptoApi);

View File

@@ -90,7 +90,7 @@ function fnFinish() {
:rules="[ :rules="[
{ {
required: true, required: true,
min: 5, min: 6,
max: 26, max: 26,
message: t('views.account.settings.oldPasswordTip'), message: t('views.account.settings.oldPasswordTip'),
}, },

View File

@@ -63,7 +63,7 @@ let tableColumns: ColumnsType = [
align: 'left', align: 'left',
customRender(opt) { customRender(opt) {
if (opt.value?.refreshTime) { if (opt.value?.refreshTime) {
return parseDateToStr(opt.value?.refreshTime, 'HH:mm:ss'); return parseDateToStr(opt.value?.refreshTime);
} }
return '-'; return '-';
}, },
@@ -94,11 +94,8 @@ let tableColumns: ColumnsType = [
}, },
{ {
title: t('views.index.ipAddress'), title: t('views.index.ipAddress'),
dataIndex: 'serverState', dataIndex: 'ip',
align: 'left', align: 'left',
customRender(opt) {
return opt.value?.neIP || '-';
},
}, },
]; ];
@@ -250,8 +247,9 @@ function fnTableSelectedRowKeys(keys: (string | number)[]) {
serverState.value = Object.assign( serverState.value = Object.assign(
{ {
// cpuUse: `NE:${nfCpuP}%; SYS:${sysCpuP}%`, // cpuUse: `NE:${nfCpuP}%; SYS:${sysCpuP}%`,
// memoryUse: `Total: ${totalMemInMB}MB; NE: ${nfUsedMemInMB}MB; SYS: ${sysMemUsageInMB}MB`, // memoryUse: `Total: ${totalMemInMB}MB; NE: ${nfUsedMemInMB}MB; SYS: ${sysMemUsageInMB}MB`,
neIP: row.ip,
}, },
neState neState
); );
@@ -315,16 +313,13 @@ onBeforeUnmount(() => {
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :lg="14" :md="16" :xs="24"> <a-col :lg="14" :md="16" :xs="24">
<!-- 表格列表 --> <!-- 表格列表 -->
<a-table <a-table class="table" row-key="id" size="small" :columns="tableColumns" :data-source="tableState.data"
class="table" :loading="tableState.loading" :pagination="false" :scroll="{ x: true }" :row-selection="{
row-key="id" type: 'radio',
size="small" columnWidth: '48px',
:columns="tableColumns" selectedRowKeys: tableState.selectedRowKeys,
:data-source="tableState.data" onChange: fnTableSelectedRowKeys,
:loading="tableState.loading" }">
:pagination="false"
:scroll="{ x: true }"
>
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'"> <template v-if="column.key === 'status'">
<DictTag :options="dict.neInfoStatus" :value="record.status" /> <DictTag :options="dict.neInfoStatus" :value="record.status" />
@@ -333,25 +328,12 @@ onBeforeUnmount(() => {
</a-table> </a-table>
</a-col> </a-col>
<a-col :lg="10" :md="8" :xs="24"> <a-col :lg="10" :md="8" :xs="24">
<a-card <a-card :title="t('views.index.runStatus')" style="margin-bottom: 16px" size="small">
:title="t('views.index.runStatus')"
style="margin-bottom: 16px"
size="small"
>
<div style="width: 100%; min-height: 200px" ref="statusBar"></div> <div style="width: 100%; min-height: 200px" ref="statusBar"></div>
</a-card> </a-card>
<a-card <a-card :loading="tableState.loading" :title="`${t('views.index.mark')} - ${serverState.neName || 'OMC'}`"
:loading="tableState.loading" style="margin-top: 16px" size="small">
:title="`${t('views.index.mark')} - ${serverState.neName || 'OMC'}`" <a-descriptions bordered :column="1" :label-style="{ width: '160px' }">
style="margin-top: 16px"
size="small"
v-if="false"
>
<a-descriptions
bordered
:column="1"
:label-style="{ width: '160px' }"
>
<a-descriptions-item :label="t('views.index.hostName')"> <a-descriptions-item :label="t('views.index.hostName')">
{{ serverState.hostname }} {{ serverState.hostname }}
</a-descriptions-item> </a-descriptions-item>

View File

@@ -247,42 +247,7 @@ function fnChangeData(data: any[], itemID: string) {
// console.log(info.neState.cpu.sysCpuUsage); // console.log(info.neState.cpu.sysCpuUsage);
// console.log(info.neState.mem); // console.log(info.neState.mem);
// console.log(info.neState.disk); // console.log(info.neState.disk);
let sysCpuUsage = 0; const { nfCpuUsage, sysCpuUsage, sysMemUsage, sysDiskUsage } = info.neState
let nfCpuUsage = 0;
if (info.neState.cpu) {
nfCpuUsage = info.neState.cpu.nfCpuUsage;
const nfCpu = +(info.neState.cpu.nfCpuUsage / 100);
nfCpuUsage = +nfCpu.toFixed(2);
if (nfCpuUsage > 100) {
nfCpuUsage = 100;
}
sysCpuUsage = info.neState.cpu.sysCpuUsage;
let sysCpu = +(info.neState.cpu.sysCpuUsage / 100);
sysCpuUsage = +sysCpu.toFixed(2);
if (sysCpuUsage > 100) {
sysCpuUsage = 100;
}
}
let sysMemUsage = 0;
if (info.neState.mem) {
const men = info.neState.mem.sysMemUsage;
sysMemUsage = +(men / 100).toFixed(2);
if (sysMemUsage > 100) {
sysMemUsage = 100;
}
}
let sysDiskUsage = 0;
if (info.neState.disk && Array.isArray(info.neState.disk.partitionInfo)) {
let disks: any[] = info.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);
}
}
category.value[0].value = sysDiskUsage; category.value[0].value = sysDiskUsage;
category.value[1].value = sysMemUsage; category.value[1].value = sysMemUsage;

View File

@@ -74,8 +74,7 @@ export function neStateParse(neType: string, data: Record<string, any>) {
// 更新网元状态 // 更新网元状态
const newNeState = Object.assign(node.neState, data, { const newNeState = Object.assign(node.neState, data, {
refreshTime: parseDateToStr(data.refreshTime, 'HH:mm:ss'), refreshTime: parseDateToStr(data.refreshTime, 'MM-DD HH:mm:ss'),
online: !!data.cpu,
}); });
// 通过 ID 查询节点实例 // 通过 ID 查询节点实例

View File

@@ -679,6 +679,7 @@ onBeforeUnmount(() => {
.toDeep :deep(.ant-select-selection-item) { .toDeep :deep(.ant-select-selection-item) {
color: #fff; color: #fff;
} }
.toDeep-text { .toDeep-text {
color: #4c9bfd !important; color: #4c9bfd !important;
font-size: 0.844rem !important; font-size: 0.844rem !important;

View File

@@ -71,9 +71,8 @@ const optionData: any = {
label: { label: {
normal: { normal: {
formatter: () => { formatter: () => {
return `${t('views.dashboard.overview.resources.neCpu')}\n${ return `${t('views.dashboard.overview.resources.neCpu')}\n${resourceData.value.neCpu
resourceData.value.neCpu }%`;
}%`;
}, },
textStyle: { textStyle: {
fontSize: 12, fontSize: 12,
@@ -103,9 +102,8 @@ const optionData: any = {
label: { label: {
normal: { normal: {
formatter: () => { formatter: () => {
return `${t('views.dashboard.overview.resources.sysCpu')}\n${ return `${t('views.dashboard.overview.resources.sysCpu')}\n${resourceData.value.sysCpu
resourceData.value.sysCpu }%`;
}%`;
}, },
textStyle: { textStyle: {
fontSize: 12, fontSize: 12,
@@ -135,9 +133,8 @@ const optionData: any = {
label: { label: {
normal: { normal: {
formatter: () => { formatter: () => {
return `${t('views.dashboard.overview.resources.sysMem')}\n${ return `${t('views.dashboard.overview.resources.sysMem')}\n${resourceData.value.sysMem
resourceData.value.sysMem }%`;
}%`;
}, },
textStyle: { textStyle: {
fontSize: 12, fontSize: 12,
@@ -167,9 +164,8 @@ const optionData: any = {
label: { label: {
normal: { normal: {
formatter: () => { formatter: () => {
return `${t('views.dashboard.overview.resources.sysDisk')}\n${ return `${t('views.dashboard.overview.resources.sysDisk')}\n${resourceData.value.sysDisk
resourceData.value.sysDisk }%`;
}%`;
}, },
textStyle: { textStyle: {
fontSize: 12, fontSize: 12,
@@ -213,45 +209,7 @@ function fnChangeData(data: any[], itemID: string) {
let info = data.find((item: any) => item.id === neType); let info = data.find((item: any) => item.id === neType);
if (!info || !info.neStateMap[neID]?.online) return; if (!info || !info.neStateMap[neID]?.online) return;
let sysCpuUsage = 0; const { nfCpuUsage, sysCpuUsage, sysMemUsage, sysDiskUsage } = info.neStateMap[neID]
let nfCpuUsage = 0;
if (info.neStateMap[neID].cpu) {
nfCpuUsage = info.neStateMap[neID].cpu.nfCpuUsage;
const nfCpu = +(info.neStateMap[neID].cpu.nfCpuUsage / 100);
nfCpuUsage = +nfCpu.toFixed(2);
if (nfCpuUsage > 100) {
nfCpuUsage = 100;
}
sysCpuUsage = info.neStateMap[neID].cpu.sysCpuUsage;
let sysCpu = +(info.neStateMap[neID].cpu.sysCpuUsage / 100);
sysCpuUsage = +sysCpu.toFixed(2);
if (sysCpuUsage > 100) {
sysCpuUsage = 100;
}
}
let sysMemUsage = 0;
if (info.neStateMap[neID].mem) {
const men = info.neStateMap[neID].mem.sysMemUsage;
sysMemUsage = +(men / 100).toFixed(2);
if (sysMemUsage > 100) {
sysMemUsage = 100;
}
}
let sysDiskUsage = 0;
if (
info.neStateMap[neID].disk &&
Array.isArray(info.neStateMap[neID].disk.partitionInfo)
) {
let disks: any[] = info.neStateMap[neID].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);
}
}
resourceData.value = { resourceData.value = {
neCpu: nfCpuUsage, neCpu: nfCpuUsage,
@@ -269,9 +227,8 @@ function fnChangeData(data: any[], itemID: string) {
label: { label: {
normal: { normal: {
formatter: () => { formatter: () => {
return `${t('views.dashboard.overview.resources.neCpu')}\n${ return `${t('views.dashboard.overview.resources.neCpu')}\n${resourceData.value.neCpu
resourceData.value.neCpu }%`;
}%`;
}, },
}, },
}, },
@@ -282,9 +239,8 @@ function fnChangeData(data: any[], itemID: string) {
label: { label: {
normal: { normal: {
formatter: () => { formatter: () => {
return `${t('views.dashboard.overview.resources.sysCpu')}\n${ return `${t('views.dashboard.overview.resources.sysCpu')}\n${resourceData.value.sysCpu
resourceData.value.sysCpu }%`;
}%`;
}, },
}, },
}, },
@@ -295,9 +251,8 @@ function fnChangeData(data: any[], itemID: string) {
label: { label: {
normal: { normal: {
formatter: () => { formatter: () => {
return `${t('views.dashboard.overview.resources.sysMem')}\n${ return `${t('views.dashboard.overview.resources.sysMem')}\n${resourceData.value.sysMem
resourceData.value.sysMem }%`;
}%`;
}, },
}, },
}, },
@@ -308,9 +263,8 @@ function fnChangeData(data: any[], itemID: string) {
label: { label: {
normal: { normal: {
formatter: () => { formatter: () => {
return `${t('views.dashboard.overview.resources.sysDisk')}\n${ return `${t('views.dashboard.overview.resources.sysDisk')}\n${resourceData.value.sysDisk
resourceData.value.sysDisk }%`;
}%`;
}, },
}, },
}, },

View File

@@ -38,18 +38,16 @@ export const graphG6 = ref<any>(null);
export const graphNodeClickID = ref<string>('UPF_001'); export const graphNodeClickID = ref<string>('UPF_001');
/**图节点网元信息状态 */ /**图节点网元信息状态 */
export const graphNodeState = computed(() =>{ export const graphNodeState = computed(() => {
return graphState.data.nodes.map((item: any) => ({ return graphState.data.nodes.map((item: any) => ({
id: item.id, id: item.id,
label: item.label, label: item.label,
neInfo: item.neInfo, neInfo: item.neInfo,
neState: item.neState, neState: item.neState,
neInfoList:item.neInfoList, neInfoList: item.neInfoList,
neStateMap: item.neStateMap, neStateMap: item.neStateMap,
})) }));
} });
);
/**图节点网元状态数量 */ /**图节点网元状态数量 */
export const graphNodeStateNum = computed(() => { export const graphNodeStateNum = computed(() => {
@@ -72,8 +70,12 @@ export const graphNodeStateNum = computed(() => {
export const neStateRequestMap = ref<Map<string, boolean>>(new Map()); export const neStateRequestMap = ref<Map<string, boolean>>(new Map());
/**neStateParse 网元状态 数据解析 */ /**neStateParse 网元状态 数据解析 */
export function neStateParse(neType: string, data: Record<string, any>,neId: string) { export function neStateParse(
// console.log('neStateParse',neType, data, neId); neType: string,
data: Record<string, any>,
neId: string
) {
// console.log('neStateParse',neType, data, neId);
const { combos, edges, nodes } = graphState.data; const { combos, edges, nodes } = graphState.data;
@@ -82,25 +84,24 @@ export function neStateParse(neType: string, data: Record<string, any>,neId: str
if (!node) return; if (!node) return;
// 初始化状态映射 // 初始化状态映射
if (!node.neStateMap) node.neStateMap = {}; if (!node.neStateMap) node.neStateMap = {};
// 更新网元状态 // 更新网元状态
const newNeState :any = { const newNeState: any = {
...data, // 先展开data对象 ...data, // 先展开data对象
refreshTime: parseDateToStr(data.refreshTime, 'HH:mm:ss'), refreshTime: parseDateToStr(data.refreshTime, 'MM-DD HH:mm:ss'),
online: !!data.cpu, neId: neId,
neId: neId
}; };
// 如果是001更新节点状态。neInfo为主要的网元信息 // 如果是001更新节点状态。neInfo为主要的网元信息
if (node.neInfo && node.neInfo.neId === neId) { if (node.neInfo && node.neInfo.neId === neId) {
Object.assign(node.neState, newNeState); Object.assign(node.neState, newNeState);
} }
//console.log(node.neState) //console.log(node.neState)
// 无论是否为主要网元,都更新状态映射 // 无论是否为主要网元,都更新状态映射
node.neStateMap[neId] = {...newNeState}; node.neStateMap[neId] = { ...newNeState };
// 通过 ID 查询节点实例 // 通过 ID 查询节点实例
const item = graphG6.value.findById(node.id); const item = graphG6.value.findById(node.id);
if (item) { if (item) {
// 检查当前节点下所有网元的状态 // 检查当前节点下所有网元的状态
@@ -109,32 +110,33 @@ export function neStateParse(neType: string, data: Record<string, any>,neId: str
let stateColor = '#52c41a'; // 默认绿色(所有网元都正常) let stateColor = '#52c41a'; // 默认绿色(所有网元都正常)
if (allStates.some((state: any) => !state.online)) { if (allStates.some((state: any) => !state.online)) {
// 如果有任何一个网元不正常 // 如果有任何一个网元不正常
stateColor = allStates.every((state: any) => !state.online) ? '#f5222d' : '#faad14'; // 红色(全部不正常)或黄色(部分不正常) stateColor = allStates.every((state: any) => !state.online)
? '#f5222d'
: '#faad14'; // 红色(全部不正常)或黄色(部分不正常)
} }
// 图片类型不能填充 // 图片类型不能填充
if (node.type && node.type.startsWith('image')) { if (node.type && node.type.startsWith('image')) {
// 更新节点 // 更新节点
if (node.label !== newNeState.neType) { if (node.label !== newNeState.neType) {
graphG6.value.updateItem(item, {
label: newNeState.neType,
});
}
// 设置状态
graphG6.value.setItemState(item, 'top-right-dot', stateColor);
} else {
// 更新节点
graphG6.value.updateItem(item, { graphG6.value.updateItem(item, {
label: newNeState.neType, label: newNeState.neType,
style: {
fill: stateColor, // 填充色
stroke: stateColor, // 填充色
},
}); });
// 设置状态
graphG6.value.setItemState(item, 'stroke', newNeState.online);
} }
// 设置状态
graphG6.value.setItemState(item, 'top-right-dot', stateColor);
} else {
// 更新节点
graphG6.value.updateItem(item, {
label: newNeState.neType,
style: {
fill: stateColor, // 填充色
stroke: stateColor, // 填充色
},
});
// 设置状态
graphG6.value.setItemState(item, 'stroke', newNeState.online);
}
} }
// 设置边状态 // 设置边状态

View File

@@ -165,7 +165,7 @@ onMounted(() => {
:rules="[ :rules="[
{ {
required: true, required: true,
min: 5, min: 6,
max: 26, max: 26,
message: t('valid.passwordPlease'), message: t('valid.passwordPlease'),
}, },

View File

@@ -87,7 +87,7 @@ function fnFinish() {
:rules="[ :rules="[
{ {
required: true, required: true,
min: 5, min: 6,
max: 26, max: 26,
message: t('valid.passwordPlease'), message: t('valid.passwordPlease'),
}, },

View File

@@ -87,7 +87,7 @@ function fnFinish() {
:rules="[ :rules="[
{ {
required: true, required: true,
min: 5, min: 6,
max: 26, max: 26,
message: t('valid.passwordPlease'), message: t('valid.passwordPlease'),
}, },

View File

@@ -143,11 +143,10 @@ const graphNodeTooltip = new Tooltip({
" "
> >
<div><strong>${t('views.monitor.topology.state')}</strong><span> <div><strong>${t('views.monitor.topology.state')}</strong><span>
${ ${neState.online
neState.online ? t('views.monitor.topology.normalcy')
? t('views.monitor.topology.normalcy') : t('views.monitor.topology.exceptions')
: t('views.monitor.topology.exceptions') }
}
</span></div> </span></div>
<div><strong>${t('views.monitor.topology.refreshTime')}</strong><span> <div><strong>${t('views.monitor.topology.refreshTime')}</strong><span>
${neState.refreshTime ?? '--'} ${neState.refreshTime ?? '--'}
@@ -399,11 +398,10 @@ function wsMessage(res: Record<string, any>) {
const [neType, neId] = requestId.split('_'); const [neType, neId] = requestId.split('_');
const { combos, edges, nodes } = graphState.data; const { combos, edges, nodes } = graphState.data;
const node = nodes.find((item: Record<string, any>) => item.id === neType); const node = nodes.find((item: Record<string, any>) => item.id === neType);
console.log(data)
// 更新网元状态 // 更新网元状态
const newNeState = Object.assign(node.neState, data, { const newNeState = Object.assign(node.neState, data, {
refreshTime: parseDateToStr(data.refreshTime, 'HH:mm:ss'), refreshTime: parseDateToStr(data.refreshTime, 'MM-DD HH:mm:ss'),
online: !!data.cpu,
}); });
// 通过 ID 查询节点实例 // 通过 ID 查询节点实例
@@ -505,11 +503,7 @@ onBeforeUnmount(() => {
<template> <template>
<PageContainer> <PageContainer>
<a-card <a-card :bordered="false" :body-style="{ marginBottom: '24px' }" size="small">
:bordered="false"
:body-style="{ marginBottom: '24px' }"
size="small"
>
<!-- 插槽-卡片左侧侧 --> <!-- 插槽-卡片左侧侧 -->
<template #title> <template #title>
<a-space :size="8" align="center"> <a-space :size="8" align="center">
@@ -521,12 +515,10 @@ onBeforeUnmount(() => {
</template> </template>
<!-- 插槽-卡片右侧 --> <!-- 插槽-卡片右侧 -->
<template #extra> <template #extra>
<a-button <a-button type="default" size="small" @click.prevent="fnGraphDataLoad(true)">
type="default" <template #icon>
size="small" <ReloadOutlined />
@click.prevent="fnGraphDataLoad(true)" </template>
>
<template #icon><ReloadOutlined /></template>
{{ t('common.reloadText') }} {{ t('common.reloadText') }}
</a-button> </a-button>
</template> </template>

View File

@@ -636,10 +636,10 @@ onMounted(() => {
</a-form-item> </a-form-item>
<!-- 主机连接配置 --> <!-- 主机连接配置 -->
<a-divider orientation="left" v-if="false"> <a-divider orientation="left">
{{ t('views.ne.neInfo.hostConfig') }} {{ t('views.ne.neInfo.hostConfig') }}
</a-divider> </a-divider>
<a-collapse class="collapse" ghost v-if="false"> <a-collapse class="collapse" ghost>
<a-collapse-panel <a-collapse-panel
v-for="host in modalState.from.hosts.filter( v-for="host in modalState.from.hosts.filter(
(s:any) => !(s.hostType === 'telnet' && modalState.from.neType === 'OMC') (s:any) => !(s.hostType === 'telnet' && modalState.from.neType === 'OMC')

View File

@@ -5,7 +5,6 @@ import {
toRaw, toRaw,
defineAsyncComponent, defineAsyncComponent,
ref, ref,
computed,
} from 'vue'; } from 'vue';
import { PageContainer } from 'antdv-pro-layout'; import { PageContainer } from 'antdv-pro-layout';
import { message, Modal } from 'ant-design-vue/es'; import { message, Modal } from 'ant-design-vue/es';
@@ -272,8 +271,6 @@ function reloadRowInfo(row: Record<string, any>) {
item.status = '0'; item.status = '0';
} }
Object.assign(item.serverState, res.data); Object.assign(item.serverState, res.data);
const resouresUsage = parseResouresUsage(item.serverState);
Reflect.set(item, 'resoures', resouresUsage);
} }
}) })
.finally(() => { .finally(() => {
@@ -401,23 +398,7 @@ function fnGetList(pageNum?: number) {
} }
const { total, rows } = res.data; const { total, rows } = res.data;
tablePagination.total = total; tablePagination.total = total;
// 遍历处理资源情况数值 tableState.data = rows
tableState.data = rows.map((item: any) => {
let resouresUsage = {
sysDiskUsage: 0,
sysMemUsage: 0,
sysCpuUsage: 0,
nfCpuUsage: 0,
};
const neState = item.serverState;
if (neState) {
resouresUsage = parseResouresUsage(neState);
} else {
item.serverState = { online: false };
}
Reflect.set(item, 'resoures', resouresUsage);
return item;
});
} }
tableState.loading = false; tableState.loading = false;
}) })
@@ -442,33 +423,29 @@ onMounted(() => {
<template> <template>
<PageContainer> <PageContainer>
<a-card <a-card v-show="tableState.seached" :bordered="false" :body-style="{ marginBottom: '24px', paddingBottom: 0 }">
v-show="tableState.seached"
:bordered="false"
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
>
<!-- 表格搜索栏 --> <!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal"> <a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24"> <a-col :lg="6" :md="12" :xs="24">
<a-form-item :label="t('views.ne.common.neType')" name="neType "> <a-form-item :label="t('views.ne.common.neType')" name="neType ">
<a-auto-complete <a-auto-complete v-model:value="queryParams.neType" :options="neListStore.getNeSelectOtions" allow-clear
v-model:value="queryParams.neType" :placeholder="t('common.inputPlease')" />
:options="neListStore.getNeSelectOtions"
allow-clear
:placeholder="t('common.inputPlease')"
/>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="6" :md="12" :xs="24"> <a-col :lg="6" :md="12" :xs="24">
<a-form-item> <a-form-item>
<a-space :size="8"> <a-space :size="8">
<a-button type="primary" @click.prevent="fnGetList(1)"> <a-button type="primary" @click.prevent="fnGetList(1)">
<template #icon><SearchOutlined /></template> <template #icon>
<SearchOutlined />
</template>
{{ t('common.search') }} {{ t('common.search') }}
</a-button> </a-button>
<a-button type="default" @click.prevent="fnQueryReset"> <a-button type="default" @click.prevent="fnQueryReset">
<template #icon><ClearOutlined /></template> <template #icon>
<ClearOutlined />
</template>
{{ t('common.reset') }} {{ t('common.reset') }}
</a-button> </a-button>
</a-space> </a-space>
@@ -482,32 +459,25 @@ onMounted(() => {
<!-- 插槽-卡片左侧侧 --> <!-- 插槽-卡片左侧侧 -->
<template #title> <template #title>
<a-space :size="8" align="center"> <a-space :size="8" align="center">
<a-button <a-button type="primary" @click.prevent="fnModalVisibleByEdit()" v-perms:has="['ne:neInfo:add']">
type="primary" <template #icon>
@click.prevent="fnModalVisibleByEdit()" <PlusOutlined />
v-perms:has="['ne:neInfo:add']" </template>
>
<template #icon><PlusOutlined /></template>
{{ t('common.addText') }} {{ t('common.addText') }}
</a-button> </a-button>
<a-button <a-button type="default" :loading="modalState.confirmLoading" @click.prevent="fnRecordMore('quickOAM', {})"
type="default" v-perms:has="['ne:neInfo:oam']">
:loading="modalState.confirmLoading" <template #icon>
@click.prevent="fnRecordMore('quickOAM', {})" <SettingOutlined />
v-perms:has="['ne:neInfo:oam']" </template>
>
<template #icon><SettingOutlined /></template>
{{ t('views.ne.neInfo.quickOam.title') }} {{ t('views.ne.neInfo.quickOam.title') }}
</a-button> </a-button>
<a-button <a-button type="default" danger :disabled="tableState.selectedRowKeys.length <= 0"
type="default" :loading="modalState.confirmLoading" @click.prevent="fnRecordDelete('0')"
danger v-perms:has="['ne:neInfo:delete']">
:disabled="tableState.selectedRowKeys.length <= 0" <template #icon>
:loading="modalState.confirmLoading" <DeleteOutlined />
@click.prevent="fnRecordDelete('0')" </template>
v-perms:has="['ne:neInfo:delete']"
>
<template #icon><DeleteOutlined /></template>
{{ t('common.deleteText') }} {{ t('common.deleteText') }}
</a-button> </a-button>
</a-space> </a-space>
@@ -518,30 +488,27 @@ onMounted(() => {
<a-space :size="8" align="center"> <a-space :size="8" align="center">
<a-tooltip> <a-tooltip>
<template #title>{{ t('common.searchBarText') }}</template> <template #title>{{ t('common.searchBarText') }}</template>
<a-switch <a-switch v-model:checked="tableState.seached" :checked-children="t('common.switch.show')"
v-model:checked="tableState.seached" :un-checked-children="t('common.switch.hide')" size="small" />
:checked-children="t('common.switch.show')"
:un-checked-children="t('common.switch.hide')"
size="small"
/>
</a-tooltip> </a-tooltip>
<a-tooltip> <a-tooltip>
<template #title>{{ t('common.reloadText') }}</template> <template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()"> <a-button type="text" @click.prevent="fnGetList()">
<template #icon><ReloadOutlined /></template> <template #icon>
<ReloadOutlined />
</template>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
<a-tooltip> <a-tooltip>
<template #title>{{ t('common.sizeText') }}</template> <template #title>{{ t('common.sizeText') }}</template>
<a-dropdown trigger="click" placement="bottomRight"> <a-dropdown trigger="click" placement="bottomRight">
<a-button type="text"> <a-button type="text">
<template #icon><ColumnHeightOutlined /></template> <template #icon>
<ColumnHeightOutlined />
</template>
</a-button> </a-button>
<template #overlay> <template #overlay>
<a-menu <a-menu :selected-keys="[tableState.size as string]" @click="fnTableSize">
:selected-keys="[tableState.size as string]"
@click="fnTableSize"
>
<a-menu-item key="default"> <a-menu-item key="default">
{{ t('common.size.default') }} {{ t('common.size.default') }}
</a-menu-item> </a-menu-item>
@@ -559,22 +526,14 @@ onMounted(() => {
</template> </template>
<!-- 表格列表 --> <!-- 表格列表 -->
<a-table <a-table class="table" row-key="id" :columns="tableColumns" :loading="tableState.loading"
class="table" :data-source="tableState.data" :size="tableState.size" :pagination="tablePagination"
row-key="id" :scroll="{ x: tableColumns.length * 120 }" :row-selection="{
:columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: tableColumns.length * 120 }"
:row-selection="{
type: 'checkbox', type: 'checkbox',
columnWidth: '48px', columnWidth: '48px',
selectedRowKeys: tableState.selectedRowKeys, selectedRowKeys: tableState.selectedRowKeys,
onChange: fnTableSelectedRowKeys, onChange: fnTableSelectedRowKeys,
}" }">
>
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'"> <template v-if="column.key === 'status'">
<DictTag :options="dict.neInfoStatus" :value="record.status" /> <DictTag :options="dict.neInfoStatus" :value="record.status" />
@@ -583,49 +542,42 @@ onMounted(() => {
<a-space :size="8" align="center"> <a-space :size="8" align="center">
<a-tooltip> <a-tooltip>
<template #title>{{ t('common.editText') }}</template> <template #title>{{ t('common.editText') }}</template>
<a-button <a-button type="link" @click.prevent="fnModalVisibleByEdit(record)" v-perms:has="['ne:neInfo:edit']">
type="link" <template #icon>
@click.prevent="fnModalVisibleByEdit(record)" <FormOutlined />
v-perms:has="['ne:neInfo:edit']" </template>
>
<template #icon><FormOutlined /></template>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
<a-tooltip> <a-tooltip>
<template #title> <template #title>
{{ t('views.ne.common.restart') }} {{ t('views.ne.common.restart') }}
</template> </template>
<a-button <a-button type="link" @click.prevent="fnRecordMore('restart', record)"
type="link" v-perms:has="['ne:neInfo:restart']">
@click.prevent="fnRecordMore('restart', record)" <template #icon>
v-perms:has="['ne:neInfo:restart']" <UndoOutlined />
> </template>
<template #icon><UndoOutlined /></template>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
<a-tooltip placement="left"> <a-tooltip placement="left">
<template #title>{{ t('common.moreText') }}</template> <template #title>{{ t('common.moreText') }}</template>
<a-dropdown placement="bottomRight" trigger="click"> <a-dropdown placement="bottomRight" trigger="click">
<a-button type="link"> <a-button type="link">
<template #icon><EllipsisOutlined /> </template> <template #icon>
<EllipsisOutlined />
</template>
</a-button> </a-button>
<template #overlay> <template #overlay>
<a-menu @click="({ key }:any) => fnRecordMore(key, record)"> <a-menu @click="({ key }: any) => fnRecordMore(key, record)">
<a-menu-item key="log"> <a-menu-item key="log">
<FileTextOutlined /> <FileTextOutlined />
{{ t('views.ne.common.log') }} {{ t('views.ne.common.log') }}
</a-menu-item> </a-menu-item>
<a-menu-item <a-menu-item key="start" v-if="hasPermissions(['ne:neInfo:start'])">
key="start"
v-if="hasPermissions(['ne:neInfo:start'])"
>
<ThunderboltOutlined /> <ThunderboltOutlined />
{{ t('views.ne.common.start') }} {{ t('views.ne.common.start') }}
</a-menu-item> </a-menu-item>
<a-menu-item <a-menu-item key="stop" v-if="hasPermissions(['ne:neInfo:stop'])">
key="stop"
v-if="hasPermissions(['ne:neInfo:stop'])"
>
<CloseSquareOutlined /> <CloseSquareOutlined />
{{ t('views.ne.common.stop') }} {{ t('views.ne.common.stop') }}
</a-menu-item> </a-menu-item>
@@ -633,35 +585,23 @@ onMounted(() => {
<SyncOutlined /> <SyncOutlined />
{{ t('views.ne.common.reload') }} {{ t('views.ne.common.reload') }}
</a-menu-item> </a-menu-item>
<a-menu-item <a-menu-item key="delete" v-if="hasPermissions(['ne:neInfo:delete'])">
key="delete"
v-if="hasPermissions(['ne:neInfo:delete'])"
>
<DeleteOutlined /> <DeleteOutlined />
{{ t('common.deleteText') }} {{ t('common.deleteText') }}
</a-menu-item> </a-menu-item>
<a-menu-item <a-menu-item key="oam" v-if="
key="oam" !['OMC'].includes(record.neType) ||
v-if=" hasPermissions(['ne:neInfo:oam'])
!['OMC'].includes(record.neType) || ">
hasPermissions(['ne:neInfo:oam'])
"
>
<FileTextOutlined /> <FileTextOutlined />
{{ t('views.ne.common.oam') }} {{ t('views.ne.common.oam') }}
</a-menu-item> </a-menu-item>
<!-- 配置备份 --> <!-- 配置备份 -->
<a-menu-item <a-menu-item key="backConfExport" v-if="hasPermissions(['ne:neInfo:export'])">
key="backConfExport"
v-if="hasPermissions(['ne:neInfo:export'])"
>
<ExportOutlined /> <ExportOutlined />
{{ t('views.ne.neInfo.backConf.export') }} {{ t('views.ne.neInfo.backConf.export') }}
</a-menu-item> </a-menu-item>
<a-menu-item <a-menu-item key="backConfImport" v-if="hasPermissions(['ne:neInfo:import'])">
key="backConfImport"
v-if="hasPermissions(['ne:neInfo:import'])"
>
<ImportOutlined /> <ImportOutlined />
{{ t('views.ne.neInfo.backConf.import') }} {{ t('views.ne.neInfo.backConf.import') }}
</a-menu-item> </a-menu-item>
@@ -701,59 +641,39 @@ onMounted(() => {
</a-divider> </a-divider>
<div> <div>
<span>{{ t('views.ne.neInfo.neCpu') }}</span> <span>{{ t('views.ne.neInfo.neCpu') }}</span>
<a-progress <a-progress status="normal" :stroke-color="record.serverState.nfCpuUsage < 30
status="normal" ? '#52c41a'
:stroke-color=" : record.serverState.nfCpuUsage > 70
record.resoures.nfCpuUsage < 30 ? '#ff4d4f'
? '#52c41a' : '#1890ff'
: record.resoures.nfCpuUsage > 70 " :percent="record.serverState.nfCpuUsage" />
? '#ff4d4f'
: '#1890ff'
"
:percent="record.resoures.nfCpuUsage"
/>
</div> </div>
<div> <div>
<span>{{ t('views.ne.neInfo.sysCpu') }}</span> <span>{{ t('views.ne.neInfo.sysCpu') }}</span>
<a-progress <a-progress status="normal" :stroke-color="record.serverState.sysCpuUsage < 30
status="normal" ? '#52c41a'
:stroke-color=" : record.serverState.sysCpuUsage > 70
record.resoures.sysCpuUsage < 30 ? '#ff4d4f'
? '#52c41a' : '#1890ff'
: record.resoures.sysCpuUsage > 70 " :percent="record.serverState.sysCpuUsage" />
? '#ff4d4f'
: '#1890ff'
"
:percent="record.resoures.sysCpuUsage"
/>
</div> </div>
<div> <div>
<span>{{ t('views.ne.neInfo.sysMem') }}</span> <span>{{ t('views.ne.neInfo.sysMem') }}</span>
<a-progress <a-progress status="normal" :stroke-color="record.serverState.sysMemUsage < 30
status="normal" ? '#52c41a'
:stroke-color=" : record.serverState.sysMemUsage > 70
record.resoures.sysMemUsage < 30 ? '#ff4d4f'
? '#52c41a' : '#1890ff'
: record.resoures.sysMemUsage > 70 " :percent="record.serverState.sysMemUsage" />
? '#ff4d4f'
: '#1890ff'
"
:percent="record.resoures.sysMemUsage"
/>
</div> </div>
<div> <div>
<span>{{ t('views.ne.neInfo.sysDisk') }}</span> <span>{{ t('views.ne.neInfo.sysDisk') }}</span>
<a-progress <a-progress status="normal" :stroke-color="record.serverState.sysDiskUsage < 30
status="normal" ? '#52c41a'
:stroke-color=" : record.serverState.sysDiskUsage > 70
record.resoures.sysDiskUsage < 30 ? '#ff4d4f'
? '#52c41a' : '#1890ff'
: record.resoures.sysDiskUsage > 70 " :percent="record.serverState.sysDiskUsage" />
? '#ff4d4f'
: '#1890ff'
"
:percent="record.resoures.sysDiskUsage"
/>
</div> </div>
</a-col> </a-col>
</a-row> </a-row>
@@ -762,35 +682,19 @@ onMounted(() => {
</a-card> </a-card>
<!-- 新增框或修改框 --> <!-- 新增框或修改框 -->
<EditModal <EditModal v-model:open="modalState.openByEdit" :edit-id="modalState.editId" @ok="fnModalEditOk"
v-model:open="modalState.openByEdit" @cancel="fnModalEditCancel"></EditModal>
:edit-id="modalState.editId"
@ok="fnModalEditOk"
@cancel="fnModalEditCancel"
></EditModal>
<!-- OAM编辑框 --> <!-- OAM编辑框 -->
<OAMModal <OAMModal v-model:open="modalState.openByOAM" :ne-id="modalState.neId" :ne-type="modalState.neType"
v-model:open="modalState.openByOAM" @cancel="fnModalEditCancel"></OAMModal>
:ne-id="modalState.neId"
:ne-type="modalState.neType"
@cancel="fnModalEditCancel"
></OAMModal>
<!-- 配置文件备份框 --> <!-- 配置文件备份框 -->
<BackConfModal <BackConfModal ref="backConf" v-model:open="modalState.openByBackConf" :ne-id="modalState.neId"
ref="backConf" :ne-type="modalState.neType" @cancel="fnModalEditCancel"></BackConfModal>
v-model:open="modalState.openByBackConf"
:ne-id="modalState.neId"
:ne-type="modalState.neType"
@cancel="fnModalEditCancel"
></BackConfModal>
<!-- 快速OAM配置框 --> <!-- 快速OAM配置框 -->
<QuickOAMModal <QuickOAMModal v-model:open="modalState.openByQuickOAM" @cancel="fnModalEditCancel"></QuickOAMModal>
v-model:open="modalState.openByQuickOAM"
@cancel="fnModalEditCancel"
></QuickOAMModal>
</PageContainer> </PageContainer>
</template> </template>

View File

@@ -45,8 +45,8 @@ let fromState = ref({
ue_pool: '10.2.1.0/24', ue_pool: '10.2.1.0/24',
// 非指定属性 // 非指定属性
mmes1_ip: '192.168.8.220/20', mmes1_ip: '192.168.8.220/20',
mmes10_ip: '127.0.0.221/24', mmes10_ip: '172.16.5.221/24',
mmes11_ip: '127.0.0.220/24', mmes11_ip: '172.16.5.220/24',
ims_sip_ip: '192.168.8.110', ims_sip_ip: '192.168.8.110',
upf_type: 'LightUPF', upf_type: 'LightUPF',
upf_driver_type: 'vmxnet3', upf_driver_type: 'vmxnet3',
@@ -58,22 +58,22 @@ let fromState = ref({
upfn6_mac: '00:00:00:00:00:00', upfn6_mac: '00:00:00:00:00:00',
}, },
sbi: { sbi: {
omc_ip: '127.0.0.100', omc_ip: '172.16.5.100',
ims_ip: '127.0.0.110', ims_ip: '172.16.5.110',
amf_ip: '127.0.0.120', amf_ip: '172.16.5.120',
ausf_ip: '127.0.0.130', ausf_ip: '172.16.5.130',
udm_ip: '127.0.0.140', udm_ip: '172.16.5.140',
db_ip: '127.0.0.1', db_ip: '0.0.0.0',
smf_ip: '127.0.0.150', smf_ip: '172.16.5.150',
pcf_ip: '127.0.0.160', pcf_ip: '172.16.5.160',
nssf_ip: '127.0.0.170', nssf_ip: '172.16.5.170',
nrf_ip: '127.0.0.180', nrf_ip: '172.16.5.180',
upf_ip: '127.0.0.190', upf_ip: '172.16.5.190',
lmf_ip: '127.0.0.200', lmf_ip: '172.16.5.200',
nef_ip: '127.0.0.210', nef_ip: '172.16.5.210',
mme_ip: '127.0.0.220', mme_ip: '172.16.5.220',
n3iwf_ip: '127.0.0.230', n3iwf_ip: '172.16.5.230',
smsc_ip: '127.0.0.240', smsc_ip: '172.16.5.240',
}, },
}); });

View File

@@ -920,7 +920,7 @@ onMounted(() => {
v-perms:has="['neData:udm-auth:export-dec']" v-perms:has="['neData:udm-auth:export-dec']"
> >
<template #icon><ExportOutlined /></template> <template #icon><ExportOutlined /></template>
{{ t('views.neUser.auth.export') }} Export Dec
</a-button> </a-button>
<a-button <a-button

View File

@@ -24,7 +24,6 @@ import {
resetUDMSub, resetUDMSub,
updateUDMSub, updateUDMSub,
} from '@/api/neData/udm_sub'; } from '@/api/neData/udm_sub';
import { getNeConfigData, addNeConfigData } from '@/api/ne/neConfig';
import { uploadFile } from '@/api/tool/file'; import { uploadFile } from '@/api/tool/file';
import { getNeViewFile } from '@/api/tool/neFile'; import { getNeViewFile } from '@/api/tool/neFile';
import { parseDateToStr } from '@/utils/date-utils'; import { parseDateToStr } from '@/utils/date-utils';
@@ -113,42 +112,39 @@ let tableColumns = ref<ColumnsType>([
{ {
title: 'Subscribed AMBR', title: 'Subscribed AMBR',
dataIndex: 'ambr', dataIndex: 'ambr',
align: 'left', align: 'center',
resizable: true, width: 100,
width: 150,
minWidth: 150,
maxWidth: 300,
}, },
{ {
title: 'Subscribed SNSSAIs', title: 'Subscribed SNSSAIs',
dataIndex: 'nssai', dataIndex: 'nssai',
align: 'left', align: 'center',
width: 100, width: 100,
}, },
{ {
title: 'Forbidden Areas', title: 'Forbidden Areas',
dataIndex: 'arfb', dataIndex: 'arfb',
align: 'left', align: 'center',
width: 100, width: 100,
}, },
{ {
title: 'Service Area Restrict', title: 'Service Area Restrict',
dataIndex: 'sar', dataIndex: 'sar',
align: 'left', align: 'center',
width: 100, width: 100,
}, },
{ {
title: '5G', title: '5G',
dataIndex: 'cn', dataIndex: 'cn',
key: 'cnFlag', key: 'cnFlag',
align: 'left', align: 'center',
width: 100, width: 100,
}, },
{ {
title: '4G', title: '4G',
dataIndex: 'epsFlag', dataIndex: 'epsFlag',
key: 'epsFlag', key: 'epsFlag',
align: 'left', align: 'center',
width: 100, width: 100,
}, },
{ {
@@ -252,10 +248,6 @@ type ModalStateType = {
confirmLoading: boolean; confirmLoading: boolean;
/**更新加载数据按钮 loading */ /**更新加载数据按钮 loading */
loadDataLoading: boolean; loadDataLoading: boolean;
/**5G Subscribed UE AMBR 模板对象 */
ambr: Record<string, any>;
/**已有AMBE数据 */
ambrData: Record<string, any>[];
}; };
/**对话框对象信息状态 */ /**对话框对象信息状态 */
@@ -307,36 +299,6 @@ let modalState: ModalStateType = reactive({
}, },
confirmLoading: false, confirmLoading: false,
loadDataLoading: false, loadDataLoading: false,
// 5G Subscribed UE AMBR
ambr: {
uplink: 1,
uplinkUnit: 'Gbps',
downlink: 2,
downlinkUnit: 'Gbps',
options: [
{
label: 'bps',
value: 'bps',
},
{
label: 'Kbps',
value: 'Kbps',
},
{
label: 'Mbps',
value: 'Mbps',
},
{
label: 'Gbps',
value: 'Gbps',
},
{
label: 'Tbps',
value: 'Tbps',
},
],
},
ambrData: [],
}); });
/**表单中多选的OPTION */ /**表单中多选的OPTION */
@@ -435,23 +397,7 @@ function fnModalVisibleByBatch() {
* 对话框弹出显示为 新增或者修改 * 对话框弹出显示为 新增或者修改
* @param noticeId 网元id, 不传为新增 * @param noticeId 网元id, 不传为新增
*/ */
async function fnModalVisibleByEdit(imsi?: string) { function fnModalVisibleByEdit(imsi?: string) {
const neId = queryParams.neId || '-';
// 获取AMBR数据
const ambrRes = await getNeConfigData({
neType: 'UDM',
neId: neId,
paramName: 'subsUEAmbr',
});
if (ambrRes.code === RESULT_CODE_SUCCESS) {
modalState.ambr.uplink = 1;
modalState.ambr.uplinkUnit = 'Gbps';
modalState.ambr.downlink = 2;
modalState.ambr.downlinkUnit = 'Gbps';
modalState.ambrData = ambrRes.data;
}
if (!imsi) { if (!imsi) {
modalStateFrom.resetFields(); modalStateFrom.resetFields();
modalState.title = t('common.addText') + t('views.neUser.sub.subInfo'); modalState.title = t('common.addText') + t('views.neUser.sub.subInfo');
@@ -460,68 +406,60 @@ async function fnModalVisibleByEdit(imsi?: string) {
if (modalState.confirmLoading) return; if (modalState.confirmLoading) return;
const hide = message.loading(t('common.loading'), 0); const hide = message.loading(t('common.loading'), 0);
modalState.confirmLoading = true; modalState.confirmLoading = true;
// 获取IMSI数据 const neId = queryParams.neId || '-';
const res = await getUDMSub(neId, imsi); getUDMSub(neId, imsi)
if (res.code === RESULT_CODE_SUCCESS) { .then(res => {
transformFormData(res.data.smData); if (res.code === RESULT_CODE_SUCCESS) {
let ardAll = parseInt(res.data.ard).toString(2).padStart(8, '0'); transformFormData(res.data.smData);
let hplAll = parseInt(res.data.hplmnOdb).toString(2).padStart(8, '0'); let ardAll = parseInt(res.data.ard).toString(2).padStart(8, '0');
let odbAll = parseInt(res.data.epsOdb).toString(2).padStart(9, '0'); let hplAll = parseInt(res.data.hplmnOdb).toString(2).padStart(8, '0');
const ardArray: any[] = []; let odbAll = parseInt(res.data.epsOdb).toString(2).padStart(9, '0');
const hplmnArray: any[] = []; const ardArray: any[] = [];
const epsOdbArray: any[] = []; const hplmnArray: any[] = [];
for (let i = 0; i < ardAll.length; i++) { const epsOdbArray: any[] = [];
if (PrefixZero(ardAll, ardAll.length).charAt(i) === '1') { for (let i = 0; i < ardAll.length; i++) {
ardArray.push(i); if (PrefixZero(ardAll, ardAll.length).charAt(i) === '1') {
} ardArray.push(i);
} }
}
for (let i = 0; i < hplAll.length; i++) { for (let i = 0; i < hplAll.length; i++) {
if (PrefixZero(hplAll, hplAll.length).charAt(i) === '1') { if (PrefixZero(hplAll, hplAll.length).charAt(i) === '1') {
hplmnArray.push(i); hplmnArray.push(i);
} }
} }
for (let i = 0; i < odbAll.length; i++) { for (let i = 0; i < odbAll.length; i++) {
if (PrefixZero(odbAll, odbAll.length).charAt(i) === '1') { if (PrefixZero(odbAll, odbAll.length).charAt(i) === '1') {
epsOdbArray.push(i); epsOdbArray.push(i);
} }
} }
res.data.ard = ardArray; res.data.ard = ardArray;
res.data.hplmnOdb = hplmnArray; res.data.hplmnOdb = hplmnArray;
res.data.epsOdb = epsOdbArray; res.data.epsOdb = epsOdbArray;
// 4G APN Context List // 4G APN Context List
const apnContextStr = res.data.apnContext; const apnContextStr = res.data.apnContext;
const apnContextArr = []; const apnContextArr = [];
for (let i = 0; i < apnContextStr.length; i += 2) { for (let i = 0; i < apnContextStr.length; i += 2) {
const num = Number(`${apnContextStr[i]}${apnContextStr[i + 1]}`); const num = Number(`${apnContextStr[i]}${apnContextStr[i + 1]}`);
apnContextArr.push(num); apnContextArr.push(num);
} }
modalState.from = Object.assign(modalState.from, res.data, { modalState.from = Object.assign(modalState.from, res.data, {
apnContext: apnContextArr, apnContext: apnContextArr,
});
modalState.title =
t('common.editText') + t('views.neUser.sub.subInfo');
modalState.openByEdit = true;
} else {
message.error(t('common.getInfoFail'), 2);
}
})
.finally(() => {
hide();
modalState.confirmLoading = false;
}); });
// AMBR数据
const ambrItem = modalState.ambrData.find(
item => item.name === modalState.from.ambr
);
if (ambrItem) {
const uplinkItme = ambrItem.uplink.split(' ');
modalState.ambr.uplink = uplinkItme[0] || 1;
modalState.ambr.uplinkUnit = uplinkItme[1] || 'Gbps';
const downlinkItme = ambrItem.downlink.split(' ');
modalState.ambr.downlink = downlinkItme[0] || 2;
modalState.ambr.downlinkUnit = downlinkItme[1] || 'Gbps';
}
modalState.title = t('common.editText') + t('views.neUser.sub.subInfo');
modalState.openByEdit = true;
} else {
message.error(t('common.getInfoFail'), 2);
}
hide();
modalState.confirmLoading = false;
} }
} }
@@ -687,7 +625,7 @@ function fnModalOk() {
modalStateFrom modalStateFrom
.validate() .validate()
.then(async e => { .then(e => {
modalState.confirmLoading = true; modalState.confirmLoading = true;
let ardArr = [0, 0, 0, 0, 0, 0, 0, 0]; let ardArr = [0, 0, 0, 0, 0, 0, 0, 0];
let hplmnArr = [0, 0, 0, 0, 0, 0, 0, 0]; let hplmnArr = [0, 0, 0, 0, 0, 0, 0, 0];
@@ -719,107 +657,58 @@ function fnModalOk() {
from.regTimer = `${from.regTimer}`; from.regTimer = `${from.regTimer}`;
from.ueUsageType = `${from.ueUsageType}`; from.ueUsageType = `${from.ueUsageType}`;
from.neId = queryParams.neId || '-'; from.neId = queryParams.neId || '-';
const result = from.id
// 组合检查ambr ? updateUDMSub(from)
from.ambr = await fnAMBRName(); : from.num === 1
? addUDMSub(from)
let res = null; : batchAddUDMSub(from, from.num);
if (from.id) {
res = await updateUDMSub(from);
} else {
if (from.num === 1) {
res = await addUDMSub(from);
} else {
res = await batchAddUDMSub(from, from.num);
}
}
if (!res) return;
const hide = message.loading(t('common.loading'), 0); const hide = message.loading(t('common.loading'), 0);
if (res.code === RESULT_CODE_SUCCESS) { result
if (from.num === 1) { .then(res => {
message.success({ if (res.code === RESULT_CODE_SUCCESS) {
content: t('common.msgSuccess', { msg: modalState.title }), if (from.num === 1) {
duration: 3, message.success({
}); content: t('common.msgSuccess', { msg: modalState.title }),
fnGetList(1); duration: 3,
} else { });
const timerS = Math.ceil(+from.num / 800) + 1; fnGetList(1);
notification.success({ } else {
message: modalState.title, const timerS = Math.ceil(+from.num / 800) + 1;
description: t('common.operateOk'), notification.success({
duration: timerS, message: modalState.title,
}); description: t('common.operateOk'),
setTimeout(() => { duration: timerS,
fnGetList(1); });
}, timerS * 1000); setTimeout(() => {
} fnGetList(1);
} else { }, timerS * 1000);
if (from.num === 1) { }
message.error({ } else {
content: `${res.msg}`, if (from.num === 1) {
duration: 3, message.error({
}); content: `${res.msg}`,
} else { duration: 3,
notification.error({ });
message: modalState.title, } else {
description: res.msg, notification.error({
duration: 3, message: modalState.title,
}); description: res.msg,
} duration: 3,
} });
}
hide(); }
fnModalCancel(); })
modalState.confirmLoading = false; .finally(() => {
hide();
fnModalCancel();
modalState.confirmLoading = false;
});
}) })
.catch(e => { .catch(e => {
message.error(t('common.errorFields', { num: e.errorFields.length }), 3); message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
}); });
} }
/**
* 检查ambr是否存在不存在则新增
*/
async function fnAMBRName() {
const ambrItem = modalState.ambrData.find(
item =>
item.downlink ===
`${modalState.ambr.downlink} ${modalState.ambr.downlinkUnit}` &&
item.uplink === `${modalState.ambr.uplink} ${modalState.ambr.uplinkUnit}`
);
if (ambrItem) {
return ambrItem.name;
} else {
// 新增
const ambrData = modalState.ambrData.sort((a: any, b: any) => {
return b.index - a.index;
});
if (ambrData.length > 0) {
const neId = queryParams.neId || '-';
const loc = ambrData[0].index + 1;
const name = `ambr_${modalState.ambr.uplink}${modalState.ambr.uplinkUnit}_${modalState.ambr.downlink}${modalState.ambr.downlinkUnit}`;
const ambrRes = await addNeConfigData({
neType: 'UDM',
neId: neId,
paramName: 'subsUEAmbr',
paramData: {
index: loc,
name: name,
uplink: `${modalState.ambr.uplink} ${modalState.ambr.uplinkUnit}`,
downlink: `${modalState.ambr.downlink} ${modalState.ambr.downlinkUnit}`,
},
loc: `${loc}`,
});
if (ambrRes.code === RESULT_CODE_SUCCESS) {
return name;
}
} else {
return 'def_ambr';
}
}
}
/**对话框内批量添加表单属性和校验规则 */ /**对话框内批量添加表单属性和校验规则 */
const modalStateBatchDelFrom = Form.useForm( const modalStateBatchDelFrom = Form.useForm(
modalState.BatchDelForm, modalState.BatchDelForm,
@@ -1820,7 +1709,7 @@ onMounted(() => {
</a-select> </a-select>
</a-form-item> </a-form-item>
</a-col> </a-col>
<!-- <a-col :lg="24" :md="24" :xs="24"> <a-col :lg="24" :md="24" :xs="24">
<a-form-item <a-form-item
label="5G Subscribed UE AMBR Template" label="5G Subscribed UE AMBR Template"
name="ambr" name="ambr"
@@ -1843,41 +1732,7 @@ onMounted(() => {
</template> </template>
</a-input> </a-input>
</a-form-item> </a-form-item>
</a-col> -->
<a-col :lg="24" :md="24" :xs="24">
<a-form-item label="5G Subscribed UE AMBR" name="ambr">
<a-row :gutter="8">
<a-col :span="12">
<a-input v-model:value="modalState.ambr.uplink">
<template #addonBefore>Uplink</template>
<template #addonAfter>
<a-select
v-model:value="modalState.ambr.uplinkUnit"
style="width: 100px"
:options="modalState.ambr.options"
>
</a-select>
</template>
</a-input>
</a-col>
<a-col :span="12">
<a-input v-model:value="modalState.ambr.downlink">
<template #addonBefore>Downlink</template>
<template #addonAfter>
<a-select
v-model:value="modalState.ambr.downlinkUnit"
style="width: 100px"
:options="modalState.ambr.options"
>
</a-select>
</template>
</a-input>
</a-col>
</a-row>
</a-form-item>
</a-col> </a-col>
<a-col :lg="24" :md="24" :xs="24"> <a-col :lg="24" :md="24" :xs="24">
<a-form-item <a-form-item
label="5G Subscribed SNSSAIs Template" label="5G Subscribed SNSSAIs Template"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -169,7 +169,7 @@ onMounted(() => {
:rules="[ :rules="[
{ {
required: true, required: true,
min: 5, min: 6,
max: 26, max: 26,
validator: fnEqualToPassword, validator: fnEqualToPassword,
}, },

View File

@@ -4,6 +4,7 @@ import { Modal } from 'ant-design-vue/es';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { delAccessToken, delRefreshToken } from '@/plugins/auth-token'; import { delAccessToken, delRefreshToken } from '@/plugins/auth-token';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import useAppStore from '@/store/modules/app';
import useI18n from '@/hooks/useI18n'; import useI18n from '@/hooks/useI18n';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { bootloaderDone } from '@/api/system/quick-start/bootloader'; import { bootloaderDone } from '@/api/system/quick-start/bootloader';
@@ -55,9 +56,8 @@ function fnGuideDone() {
bootloaderDone() bootloaderDone()
.then(res => { .then(res => {
if (res.code === RESULT_CODE_SUCCESS) { if (res.code === RESULT_CODE_SUCCESS) {
// useAppStore().bootloader = false; useAppStore().bootloader = false;
delAccessToken(); delAccessToken();
delRefreshToken();
} }
}) })
.finally(() => { .finally(() => {

View File

@@ -834,6 +834,46 @@ onMounted(() => {
> >
<!-- 表格搜索栏 --> <!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal"> <a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16">
<a-col :lg="18" :md="12" :xs="24">
<a-form-item
:label="t('views.system.user.className')"
name="deptId"
>
<a-tree-select
v-model:value="queryParams.deptId"
show-search
tree-default-expand-all
:tree-data="deptTreeData"
:field-names="{
children: 'children',
label: 'label',
value: 'id',
}"
tree-node-label-prop="label"
tree-node-filter-prop="label"
style="width: 100%"
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
:placeholder="t('common.selectPlease')"
>
</a-tree-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-row :gutter="16"> <a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24"> <a-col :lg="6" :md="12" :xs="24">
<a-form-item <a-form-item
@@ -897,52 +937,11 @@ onMounted(() => {
bordered bordered
:show-time="{ format: 'HH:mm:ss' }" :show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss"
value-format="x"
style="width: 100%" style="width: 100%"
></a-range-picker> ></a-range-picker>
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
<a-row :gutter="16">
<a-col :lg="18" :md="12" :xs="24" v-if="false">
<a-form-item
:label="t('views.system.user.className')"
name="deptId"
>
<a-tree-select
v-model:value="queryParams.deptId"
show-search
tree-default-expand-all
:tree-data="deptTreeData"
:field-names="{
children: 'children',
label: 'label',
value: 'id',
}"
tree-node-label-prop="label"
tree-node-filter-prop="label"
style="width: 100%"
:dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
:placeholder="t('common.selectPlease')"
>
</a-tree-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-form>
</a-card> </a-card>
@@ -972,7 +971,6 @@ onMounted(() => {
type="dashed" type="dashed"
@click.prevent="fnModalUploadImportOpen()" @click.prevent="fnModalUploadImportOpen()"
v-perms:has="['system:user:import']" v-perms:has="['system:user:import']"
v-if="false"
> >
<template #icon><ImportOutlined /></template> <template #icon><ImportOutlined /></template>
{{ t('common.import') }} {{ t('common.import') }}
@@ -981,7 +979,6 @@ onMounted(() => {
type="dashed" type="dashed"
@click.prevent="fnExportList()" @click.prevent="fnExportList()"
v-perms:has="['system:user:export']" v-perms:has="['system:user:export']"
v-if="false"
> >
<template #icon><ExportOutlined /></template> <template #icon><ExportOutlined /></template>
{{ t('common.export') }} {{ t('common.export') }}
@@ -1258,7 +1255,6 @@ onMounted(() => {
name="deptId" name="deptId"
:label-col="{ span: 3 }" :label-col="{ span: 3 }"
:label-wrap="true" :label-wrap="true"
v-if="false"
> >
<a-tree-select <a-tree-select
:value="modalState.from.deptId" :value="modalState.from.deptId"
@@ -1278,7 +1274,7 @@ onMounted(() => {
</a-form-item> </a-form-item>
<a-row> <a-row>
<a-col :lg="12" :md="12" :xs="24" v-if="false"> <a-col :lg="12" :md="12" :xs="24">
<a-form-item <a-form-item
:label="t('views.system.user.userWork')" :label="t('views.system.user.userWork')"
name="postIds" name="postIds"
@@ -1411,7 +1407,6 @@ onMounted(() => {
:md="12" :md="12"
:xs="24" :xs="24"
v-perms:has="['system:user:editPost']" v-perms:has="['system:user:editPost']"
v-if="false"
> >
<a-form-item <a-form-item
:label="t('views.system.user.userWork')" :label="t('views.system.user.userWork')"
@@ -1524,7 +1519,6 @@ onMounted(() => {
:label="t('views.system.user.fromClass')" :label="t('views.system.user.fromClass')"
name="deptId" name="deptId"
:label-col="{ span: 3 }" :label-col="{ span: 3 }"
v-if="false"
> >
<a-tree-select <a-tree-select
v-model:value="modalState.from.deptId" v-model:value="modalState.from.deptId"

View File

@@ -1,339 +0,0 @@
<script lang="ts" setup>
import { PageContainer } from 'antdv-pro-layout';
import { Modal } from 'ant-design-vue/es';
import { useFullscreen } from '@vueuse/core';
import { defineAsyncComponent, reactive, ref } from 'vue';
import { parseDuration } from '@/utils/date-utils';
import useI18n from '@/hooks/useI18n';
const { t } = useI18n();
const TerminalSSH = defineAsyncComponent(
() => import('@/components/TerminalSSH/index.vue')
);
const TerminalTelnet = defineAsyncComponent(
() => import('@/components/TerminalTelnet/index.vue')
);
const TerminalRedis = defineAsyncComponent(
() => import('@/components/TerminalRedis/index.vue')
);
const HostList = defineAsyncComponent(
() => import('./components/hostList.vue')
);
// 全屏
const terminalCard = ref<HTMLElement | null>(null);
const { isFullscreen, toggle } = useFullscreen(terminalCard);
/**主机对象信息状态类型 */
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: [],
});
/**连接主机 */
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: {
id: 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 === '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) {
if (isFullscreen.value) toggle();
// 获取当前项下标
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.toUpperCase()} - ${item.host.title}`,
}),
onOk() {
tabState.panes.splice(tabIndex, 1);
// 激活前一项标签
tabState.activeKey = tabState.panes[tabIndex - 1].id;
},
});
}
</script>
<template>
<PageContainer>
<a-card
:bordered="false"
size="small"
:body-style="{ padding: '12px' }"
ref="terminalCard"
>
<a-tabs
class="terminal-tabs"
hide-add
size="small"
tab-position="top"
type="editable-card"
:tab-bar-gutter="8"
:tab-bar-style="{ margin: '0' }"
v-model:activeKey="tabState.activeKey"
@edit="(id:any) => fnTabClose(id)"
>
<a-tab-pane
v-for="pane in tabState.panes"
:key="pane.id"
:closable="pane.id !== '0'"
>
<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.id"
@connect="fnTerminalConnect"
@close="fnTerminalClose"
>
</TerminalSSH>
<!-- 非开始页的telnet主机 -->
<TerminalTelnet
v-if="pane.id !== '0' && pane.host.hostType === 'telnet'"
:id="pane.id"
:hostId="pane.host.id"
init-cmd="help"
:disable="true"
@connect="fnTerminalConnect"
@close="fnTerminalClose"
>
</TerminalTelnet>
<!-- 非开始页的redis主机 -->
<TerminalRedis
v-if="pane.id !== '0' && pane.host.hostType === 'redis'"
:id="pane.id"
:hostId="pane.host.id"
@connect="fnTerminalConnect"
@close="fnTerminalClose"
>
</TerminalRedis>
<!-- 开始页 -->
<div v-if="pane.id === '0'">
<!-- 主机列表 -->
<HostList
v-show="tabState.activeKey === '0'"
@modal="() => (isFullscreen ? toggle() : null)"
@link="fnConnectHost"
></HostList>
</div>
</div>
</a-tab-pane>
<template #rightExtra>
<a-space :size="8" align="center">
<a-tooltip placement="topRight">
<template #title>
{{ t('loayouts.rightContent.fullscreen') }}
</template>
<a-button
type="default"
shape="circle"
size="small"
style="color: inherit"
@click="toggle"
>
<template #icon>
<FullscreenExitOutlined v-if="isFullscreen" />
<FullscreenOutlined v-else />
</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" placement="bottomRight">
<a-button type="ghost" shape="circle" size="small">
<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 {
height: calc(100vh - 200px);
overflow-x: hidden;
}
</style>

View File

@@ -1,76 +1,203 @@
<script lang="ts" setup> <script lang="ts" setup>
import { PageContainer } from 'antdv-pro-layout'; import { PageContainer } from 'antdv-pro-layout';
import { listNeHost } from '@/api/ne/neHost'; import { Modal } from 'ant-design-vue/es';
import { defineAsyncComponent, onMounted, reactive } from 'vue'; import { useFullscreen } from '@vueuse/core';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { defineAsyncComponent, reactive, ref } from 'vue';
import { parseDuration } from '@/utils/date-utils';
import useI18n from '@/hooks/useI18n';
const { t } = useI18n();
const TerminalSSH = defineAsyncComponent( const TerminalSSH = defineAsyncComponent(
() => import('@/components/TerminalSSH/index.vue') () => import('@/components/TerminalSSH/index.vue')
); );
const TerminalTelnet = defineAsyncComponent(
() => import('@/components/TerminalTelnet/index.vue')
);
const TerminalRedis = defineAsyncComponent(
() => import('@/components/TerminalRedis/index.vue')
);
const HostList = defineAsyncComponent(
() => import('./components/hostList.vue')
);
// 全屏
const terminalCard = ref<HTMLElement | null>(null);
const { isFullscreen, toggle } = useFullscreen(terminalCard);
/**主机对象信息状态类型 */ /**主机对象信息状态类型 */
type HostStateType = { type HostStateType = {
/**显示主机列表 */
show: boolean;
/**加载等待 */ /**加载等待 */
loading: boolean; loading: boolean;
/**查询参数 */ /**查询参数 */
params: { params: {
/**主机类型 */ pageNum: number;
hostType: 'ssh'; pageSize: number;
/**分组 */
groupId: '1';
/**名称 */
title: 'OMC';
/**当前页数 */
pageNum: 1;
/**每页条数 */
pageSize: 10;
sortField: 'createTime';
sortOrder: 'desc';
}; };
/**OMC主机 */ /**数据总数 */
host: Record<string, any>; total: number;
data: Record<string, any>[];
}; };
/**主机对象信息状态 */ /**主机对象信息状态 */
const hostState: HostStateType = reactive({ const hostState: HostStateType = reactive({
show: false,
loading: false, loading: false,
params: { params: {
/**主机类型 */
hostType: 'ssh',
/**分组 */
groupId: '1',
/**名称 */
title: 'OMC',
/**当前页数 */
pageNum: 1, pageNum: 1,
/**每页条数 */ pageSize: 20,
pageSize: 10,
sortField: 'createTime',
sortOrder: 'desc',
}, },
host: {}, total: 0,
data: [],
}); });
/**查询信息列表, pageNum初始页数 */ /**连接主机 */
function fnGetList() { function fnConnectHost(data: Record<string, any>) {
hostState.loading = true; const id = `${Date.now()}`;
listNeHost(hostState.params) tabState.panes.push({
.then(res => { id,
if (res.code === RESULT_CODE_SUCCESS) { status: false,
const { total, rows } = res.data; host: data,
if (total > 0) { });
hostState.host = rows[0]; tabState.activeKey = id;
}
}
})
.finally(() => {
hostState.loading = false;
});
} }
onMounted(() => { /**标签对象信息状态类型 */
// 获取列表数据 type TabStateType = {
fnGetList(); /**激活选中 */
activeKey: string;
/**页签数据 */
panes: {
id: string;
status: boolean;
host: Record<string, any>;
connectStamp?: string;
}[];
};
/**标签对象信息状态 */
const tabState: TabStateType = reactive({
activeKey: '0',
panes: [
{
id: '0',
host: {
id: 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 === '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) {
if (isFullscreen.value) toggle();
// 获取当前项下标
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.toUpperCase()} - ${item.host.title}`,
}),
onOk() {
tabState.panes.splice(tabIndex, 1);
// 激活前一项标签
tabState.activeKey = tabState.panes[tabIndex - 1].id;
},
});
}
</script> </script>
<template> <template>
@@ -78,13 +205,136 @@ onMounted(() => {
<a-card <a-card
:bordered="false" :bordered="false"
size="small" size="small"
:body-style="{ padding: '12px', height: 'calc(100vh - 200px)' }" :body-style="{ padding: '12px' }"
:loading="hostState.loading" ref="terminalCard"
> >
<TerminalSSH :id="hostState.host.title" :hostId="hostState.host.id"> <a-tabs
</TerminalSSH> class="terminal-tabs"
hide-add
size="small"
tab-position="top"
type="editable-card"
:tab-bar-gutter="8"
:tab-bar-style="{ margin: '0' }"
v-model:activeKey="tabState.activeKey"
@edit="(id:any) => fnTabClose(id)"
>
<a-tab-pane
v-for="pane in tabState.panes"
:key="pane.id"
:closable="pane.id !== '0'"
>
<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.id"
@connect="fnTerminalConnect"
@close="fnTerminalClose"
>
</TerminalSSH>
<!-- 非开始页的telnet主机 -->
<TerminalTelnet
v-if="pane.id !== '0' && pane.host.hostType === 'telnet'"
:id="pane.id"
:hostId="pane.host.id"
init-cmd="help"
:disable="true"
@connect="fnTerminalConnect"
@close="fnTerminalClose"
>
</TerminalTelnet>
<!-- 非开始页的redis主机 -->
<TerminalRedis
v-if="pane.id !== '0' && pane.host.hostType === 'redis'"
:id="pane.id"
:hostId="pane.host.id"
init-cmd="PING"
@connect="fnTerminalConnect"
@close="fnTerminalClose"
>
</TerminalRedis>
<!-- 开始页 -->
<div v-if="pane.id === '0'">
<!-- 主机列表 -->
<HostList
v-show="tabState.activeKey === '0'"
@modal="() => (isFullscreen ? toggle() : null)"
@link="fnConnectHost"
></HostList>
</div>
</div>
</a-tab-pane>
<template #rightExtra>
<a-space :size="8" align="center">
<a-tooltip placement="topRight">
<template #title>
{{ t('loayouts.rightContent.fullscreen') }}
</template>
<a-button
type="default"
shape="circle"
size="small"
style="color: inherit"
@click="toggle"
>
<template #icon>
<FullscreenExitOutlined v-if="isFullscreen" />
<FullscreenOutlined v-else />
</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" placement="bottomRight">
<a-button type="ghost" shape="circle" size="small">
<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> </a-card>
</PageContainer> </PageContainer>
</template> </template>
<style lang="less" scoped></style> <style lang="less" scoped>
.pane-box {
height: calc(100vh - 200px);
overflow-x: hidden;
}
</style>