feat: 网元安装步骤页面

This commit is contained in:
TsMask
2024-04-01 15:58:06 +08:00
parent 3d40e0856e
commit 00e8b7acbb
6 changed files with 749 additions and 419 deletions

View File

@@ -1,115 +1,226 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, onMounted, toRaw, watch } from 'vue'; import { reactive, onMounted, toRaw, ref, onUnmounted } from 'vue';
import { message, Form } from 'ant-design-vue/lib'; import { message, Modal, Upload } from 'ant-design-vue/lib';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import useNeInfoStore from '@/store/modules/neinfo'; import { useClipboard } from '@vueuse/core';
import { getNeInfo, addNeInfo, updateNeInfo } from '@/api/ne/neInfo'; import { FileType } from 'ant-design-vue/lib/upload/interface';
import { NE_TYPE_LIST } from '@/constants/ne-constants'; import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
import { testNeHost } from '@/api/ne/neHost'; import { uploadFile } from '@/api/tool/file';
import useDictStore from '@/store/modules/dict'; import {
const { getDict } = useDictStore(); 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(); const { t } = useI18n();
const emit = defineEmits(['ok', 'cancel', 'update:visible']);
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
editId: {
type: String,
default: '',
},
});
/**字典数据 */ /**授权激活对象信息状态类型 */
let dict: { type LicenseStateType = {
/**认证模式 */ /**步骤 */
neHostAuthMode: DictType[]; setp: 'license' | 'verify';
} = reactive({ /**主机ID */
neHostAuthMode: [], hostId: string;
});
/**检查对象信息状态类型 */
type CheckStateType = {
/**标题 */
title: string;
/**服务器信息 */
info: Record<string, any>;
/**表单数据 */ /**表单数据 */
from: Record<string, any>; from: {
neType: string;
neId: string;
activationRequestCode: string;
licensePath: string;
reload: boolean;
};
/**确定按钮 loading */ /**确定按钮 loading */
confirmLoading: boolean; confirmLoading: boolean;
/**上传文件 */
uploadFiles: any[];
}; };
/**检查对象信息状态 */ /**授权激活对象信息状态 */
let checkState: CheckStateType = reactive({ const licenseState: LicenseStateType = reactive({
title: '检查服务器', setp: 'license',
info: { hostId: '',
hostId: undefined,
hostType: 'ssh',
groupId: '1',
title: 'SSH_NE_22',
addr: '',
port: 22,
user: 'user',
authMode: '0',
password: 'user',
privateKey: '',
passPhrase: '',
remark: '',
},
from: { from: {
hostId: undefined, neType: '',
hostType: 'ssh', neId: '',
groupId: '1', activationRequestCode: '',
title: 'SSH_NE_22', licensePath: '',
addr: '', reload: true,
port: 22,
user: 'user',
authMode: '0',
password: 'user',
privateKey: '',
passPhrase: '',
remark: '',
}, },
confirmLoading: false, confirmLoading: false,
uploadFiles: [],
}); });
/** /**表单上传前检查或转换压缩 */
* 对话框弹出测试连接 function fnBeforeUploadFile(file: FileType) {
*/ if (licenseState.confirmLoading) return false;
function fnModalTest(row: Record<string, any>) { if (!file.name.endsWith('.ini')) {
if (checkState.confirmLoading) return; message.error('只支持上传文件格式 .ini', 3);
checkState.confirmLoading = true; 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); const hide = message.loading(t('common.loading'), 0);
testNeHost(row) licenseState.confirmLoading = true;
let formData = new FormData();
formData.append('file', up.file);
formData.append('subPath', 'license');
uploadFile(formData)
.then(res => { .then(res => {
if (res.code === RESULT_CODE_SUCCESS) { if (res.code === RESULT_CODE_SUCCESS) {
message.success({ message.success('上传成功', 3);
content: `${row.addr}:${row.port} ${t('views.ne.neHost.testOk')}`, // 改为完成状态
duration: 2, const file = licenseState.uploadFiles[0];
}); file.percent = 100;
file.status = 'done';
// 预置到表单
const { fileName } = res.data;
licenseState.from.licensePath = fileName;
} else { } else {
message.error({ message.error(res.msg, 3);
content: `${row.addr}:${row.port} ${res.msg}`,
duration: 2,
});
} }
}) })
.finally(() => { .finally(() => {
hide(); hide();
checkState.confirmLoading = false; licenseState.confirmLoading = false;
}); });
} }
onMounted(() => { /**复制授权申请码 */
// 初始字典数据 function fnCopyCode() {
Promise.allSettled([getDict('ne_host_authMode')]).then(resArr => { const code = licenseState.from.activationRequestCode;
if (resArr[0].status === 'fulfilled') { if (!code) return;
dict.neHostAuthMode = resArr[0].value; 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> </script>
@@ -117,64 +228,119 @@ onMounted(() => {
<a-form <a-form
name="modalStateFrom" name="modalStateFrom"
layout="horizontal" layout="horizontal"
autocomplete="off"
:validate-on-rule-change="false"
:validateTrigger="[]"
:label-col="{ span: 3 }" :label-col="{ span: 3 }"
:labelWrap="true" :wrapper-col="{ span: 12 }"
:label-wrap="true"
> >
<a-form-item label="激活码" name="comment"> <div>---- 授权申请</div>
<a-textarea
:auto-size="{ minRows: 4, maxRows: 6 }"
:maxlength="200"
:show-count="true"
:placeholder="
t('views.configManage.softwareManage.updateCommentPlease')
"
/>
</a-form-item>
<a-form-item label="授权文件" name="file"> <template v-if="licenseState.setp === 'license'">
<a-upload <a-form-item label="授权申请码" name="comment" :required="true">
name="file" <a-input-group compact>
accept=".rpm,.deb" <a-input
list-type="text" v-model:value="licenseState.from.activationRequestCode"
:max-count="1" :disabled="true"
:show-upload-list="true" style="width: calc(100% - 200px)"
> />
<a-button type="default"> <a-tooltip title="复制">
{{ t('views.configManage.softwareManage.selectFile') }} <a-button type="default" @click="fnCopyCode()">
</a-button> <template #icon><CopyOutlined /></template>
</a-upload> </a-button>
</a-form-item> </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 <a-form-item label="授权激活文件" name="file" :required="true">
name="test" <a-upload
label="启动验证" name="file"
:label-col="{ span: 3 }" v-model:file-list="licenseState.uploadFiles"
:label-wrap="true" accept=".ini"
> list-type="text"
<div style="align-items: center"> :max-count="1"
<a-button :show-upload-list="{
type="primary" showPreviewIcon: false,
shape="round" showRemoveIcon: false,
@click="fnModalTest({})" showDownloadIcon: false,
:loading="checkState.confirmLoading" }"
:before-upload="fnBeforeUploadFile"
:custom-request="fnUploadFile"
:disabled="licenseState.confirmLoading"
> >
<template #icon><LinkOutlined /></template> <a-button type="default"> 上传文件 </a-button>
启动验证 </a-upload>
</a-button> </a-form-item>
</div>
</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>
<a-form-item <div>---- 校验信息</div>
name="info"
label="启动日志" <template v-if="licenseState.setp === 'verify'">
:label-col="{ span: 3 }" <a-form-item
:label-wrap="true" name="info"
> label="巡检信息"
<div style="align-items: center">-----</div> :label-col="{ span: 3 }"
</a-form-item> :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> </a-form>
</template> </template>
<style lang="less" scoped></style> <style lang="less" scoped>
.verify-msg {
height: 200px;
overflow-y: auto;
}
</style>

View File

@@ -1,110 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, onMounted, toRaw, watch } from 'vue'; import { reactive, onMounted, onBeforeMount } from 'vue';
import { message, Form } from 'ant-design-vue/lib'; import { message } from 'ant-design-vue/lib';
import useI18n from '@/hooks/useI18n'; import {
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; RESULT_CODE_ERROR,
import useNeInfoStore from '@/store/modules/neinfo'; RESULT_CODE_SUCCESS,
import { getNeInfo, addNeInfo, updateNeInfo } from '@/api/ne/neInfo'; } from '@/constants/result-constants';
import { NE_TYPE_LIST } from '@/constants/ne-constants'; import { stepState } from '../hooks/useStep';
import { testNeHost } from '@/api/ne/neHost'; import { getConfigFile, saveConfigFile } from '@/api/ne/neInfo';
import useDictStore from '@/store/modules/dict'; import { parseDateToStr } from '@/utils/date-utils';
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: { type ConfigState = {
/**认证模式 */
neHostAuthMode: DictType[];
} = reactive({
neHostAuthMode: [],
});
/**检查对象信息状态类型 */
type CheckStateType = {
/**标题 */
title: string;
/**服务器信息 */
info: Record<string, any>;
/**表单数据 */
from: Record<string, any>;
/**确定按钮 loading */
confirmLoading: boolean;
};
/**检查对象信息状态 */
let checkState: CheckStateType = reactive({
title: '检查服务器',
info: {
hostId: undefined,
hostType: 'ssh',
groupId: '1',
title: 'SSH_NE_22',
addr: '',
port: 22,
user: 'user',
authMode: '0',
password: 'user',
privateKey: '',
passPhrase: '',
remark: '',
},
from: {
hostId: undefined,
hostType: 'ssh',
groupId: '1',
title: 'SSH_NE_22',
addr: '',
port: 22,
user: 'user',
authMode: '0',
password: 'user',
privateKey: '',
passPhrase: '',
remark: '',
},
confirmLoading: false,
});
/**
* 对话框弹出测试连接
*/
function fnModalTest(row: Record<string, any>) {
if (checkState.confirmLoading) return;
checkState.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();
checkState.confirmLoading = false;
});
}
/**文件树对象信息状态类型 */
type FileTreeState = {
/**展开指定的树节点 */ /**展开指定的树节点 */
expandedKeys: string[]; expandedKeys: string[];
/**设置选中的树节点 */ /**设置选中的树节点 */
@@ -112,78 +18,210 @@ type FileTreeState = {
/**表单数据 */ /**表单数据 */
treeData: any[]; treeData: any[];
/**等待 loading */ /**等待 loading */
loading: boolean; treeLoading: boolean;
/**选中的树节点 */
selected: { title: string; key: string };
/**设置选中的数据内容 */
keyTextData: string;
/**设置选中等待 loading */
keyTextLoading: boolean;
}; };
/**文件树对象信息状态 */ /**配置对象信息状态 */
let fileTreeState: FileTreeState = reactive({ let configState: ConfigState = reactive({
expandedKeys: ['0-0', '0-1'], expandedKeys: ['000'],
selectedKeys: [], selectedKeys: ['oam_manager.yaml'],
treeData: [ treeData: [
{ {
title: 'parent 0', title: 'NE',
key: '0-0', key: '000',
children: [ children: [
{ {
title: 'leaf 0-0', title: 'oam_manager',
key: '0-0-0', key: 'oam_manager.yaml',
isLeaf: true,
},
{
title: 'leaf 0-1',
key: '0-0-1',
isLeaf: true,
},
],
},
{
title: 'parent 1',
key: '0-1',
children: [
{
title: 'leaf 1-0',
key: '0-1-0',
isLeaf: true,
},
{
title: 'leaf 1-1',
key: '0-1-1',
isLeaf: true,
}, },
], ],
}, },
], ],
loading: false, 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(() => { onMounted(() => {
// 初始字典数据 fnGetConfigFile();
Promise.allSettled([getDict('ne_host_authMode')]).then(resArr => { });
if (resArr[0].status === 'fulfilled') {
dict.neHostAuthMode = resArr[0].value; onBeforeMount(() => {
} clearTimeout(saveState.timer);
});
}); });
</script> </script>
<template> <template>
<a-row :gutter="8"> <a-row :gutter="8">
<a-col :lg="6" :md="6" :xs="24"> <a-col :lg="6" :md="6" :xs="24">
<a-directory-tree <a-spin :spinning="configState.treeLoading">
v-model:expandedKeys="fileTreeState.expandedKeys" <a-directory-tree
v-model:selectedKeys="fileTreeState.selectedKeys" v-model:expandedKeys="configState.expandedKeys"
:tree-data="fileTreeState.treeData" v-model:selectedKeys="configState.selectedKeys"
></a-directory-tree> @select="fnSelectConfigNode"
:tree-data="configState.treeData"
></a-directory-tree>
</a-spin>
</a-col> </a-col>
<a-col :lg="18" :md="18" :xs="24"> <a-col :lg="18" :md="18" :xs="24">
<CodemirrorEdite <a-spin :spinning="configState.treeLoading || configState.keyTextLoading">
:value="JSON.stringify(fileTreeState.selectedKeys)" <CodemirrorEdite
:disabled="false" v-model:value="configState.keyTextData"
:editor-style="{ height: '500px !important' }" :disabled="configState.keyTextLoading"
:placeholder="t('views.mmlManage.cmdAwait')" lang="yaml"
></CodemirrorEdite> style="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-col>
</a-row> </a-row>
</template> </template>
<style lang="less" scoped></style> <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>

View File

@@ -1,37 +1,171 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, onMounted, toRaw, watch } from 'vue'; import { reactive, onMounted } from 'vue';
import { message, Form } from 'ant-design-vue/lib';
import useI18n from '@/hooks/useI18n'; import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import useNeInfoStore from '@/store/modules/neinfo'; import { stateNeInfo } from '@/api/ne/neInfo';
import { getNeInfo, addNeInfo, updateNeInfo } from '@/api/ne/neInfo'; import { stepState } from '../hooks/useStep';
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 { t } = useI18n();
const emit = defineEmits(['ok', 'cancel', 'update:visible']);
const props = defineProps({ /**状态数据 */
visible: { const state = reactive({
type: Boolean, data: {} as Record<string, any>,
default: false, resoures: {} as Record<string, any>,
},
editId: {
type: String,
default: '',
},
}); });
onMounted(() => {}); 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> </script>
<template> <template>
<a-result title="网元状态"> <div style="width: 46%; padding-left: 32px; padding-bottom: 16px">
<template #extra> <a-divider orientation="left">
<div>版本1.x</div> {{ t('views.ne.neInfo.info') }}
<div>sn0000</div> </a-divider>
</template> <div>
</a-result> <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> </template>
<style lang="less" scoped> <style lang="less" scoped>

View File

@@ -1,21 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, onMounted, toRaw, watch, ref } from 'vue'; import { reactive, onMounted, toRaw, ref } from 'vue';
import { message, Form, Upload } from 'ant-design-vue/lib'; import { message, Form, Upload } from 'ant-design-vue/lib';
import useI18n from '@/hooks/useI18n'; import useI18n from '@/hooks/useI18n';
import { import {
RESULT_CODE_ERROR, RESULT_CODE_ERROR,
RESULT_CODE_SUCCESS, RESULT_CODE_SUCCESS,
} from '@/constants/result-constants'; } 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 { NE_TYPE_LIST } from '@/constants/ne-constants';
import TerminalSSH from '@/components/TerminalSSH/index.vue'; import TerminalSSH from '@/components/TerminalSSH/index.vue';
import { ColumnsType } from 'ant-design-vue/lib/table'; import { ColumnsType } from 'ant-design-vue/lib/table';
import { import { checkInstallNeSoftware, listNeSoftware } from '@/api/ne/neSoftware';
addNeSoftware,
installCheckNeSoftware,
listNeSoftware,
} from '@/api/ne/neSoftware';
import { parseDateToStr } from '@/utils/date-utils'; import { parseDateToStr } from '@/utils/date-utils';
import { FileType } from 'ant-design-vue/lib/upload/interface'; import { FileType } from 'ant-design-vue/lib/upload/interface';
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface'; import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
@@ -131,10 +125,10 @@ function fnTableSelectedRowKeys(
// 选择的表单数据填充 // 选择的表单数据填充
const row = selectedRows[0]; const row = selectedRows[0];
installState.from.neType = row.neType; installState.from.neType = row.neType;
installState.from.fileName = row.fileName; installState.from.name = row.name;
installState.from.path = row.path; installState.from.path = row.path;
installState.from.version = row.version; installState.from.version = row.version;
installState.from.comment = row.comment; installState.from.description = row.description;
} }
/**查询列表, pageNum初始页数 */ /**查询列表, pageNum初始页数 */
@@ -164,8 +158,6 @@ type InstallStateType = {
setp: 'pkg' | 'preinput' | 'ssh'; setp: 'pkg' | 'preinput' | 'ssh';
/**安装步骤命令 */ /**安装步骤命令 */
setpSSHArr: string[]; setpSSHArr: string[];
/**安装步骤命令-当前执行 */
setpSSHIdx: number;
/**主机ID */ /**主机ID */
hostId: string; hostId: string;
/**文件操作类型 上传 or 选择 */ /**文件操作类型 上传 or 选择 */
@@ -174,34 +166,41 @@ type InstallStateType = {
from: { from: {
/**网元类型 */ /**网元类型 */
neType: string; neType: string;
fileName: string; name: string;
path: string; path: string;
version: string; version: string;
/**备注 */ description: string;
comment: string;
}; };
/**确定按钮 loading */ /**确定按钮 loading */
confirmLoading: boolean; confirmLoading: boolean;
/**上传文件 */ /**上传文件 */
uploadFiles: any[]; uploadFiles: any[];
/**预输入 */
preinput: Record<string, any>;
}; };
/**安装对象信息状态 */ /**安装对象信息状态 */
let installState: InstallStateType = reactive({ let installState: InstallStateType = reactive({
setp: 'pkg', setp: 'pkg',
setpSSHArr: [], setpSSHArr: [],
setpSSHIdx: 0,
hostId: '', hostId: '',
optionType: 'upload', optionType: 'upload',
from: { from: {
neType: '', neType: '',
fileName: '', name: '',
path: '', path: '',
version: '', version: '',
comment: '', description: '',
}, },
confirmLoading: false, confirmLoading: false,
uploadFiles: [], uploadFiles: [],
preinput: {
// IMS
pubIP: '192.168.5.57',
mcc: '001',
mnc: '01',
priIP: '172.16.16.51',
},
}); });
/** /**
@@ -219,10 +218,10 @@ function fnNeTypeChange(v: any) {
*/ */
function fnOptionTypeChange() { function fnOptionTypeChange() {
if (installState.optionType === 'upload') { if (installState.optionType === 'upload') {
installState.from.fileName = ''; installState.from.name = '';
installState.from.path = ''; installState.from.path = '';
installState.from.version = ''; installState.from.version = '';
installState.from.comment = ''; installState.from.description = '';
} }
if (installState.optionType === 'option') { if (installState.optionType === 'option') {
tableState.queryParams.neType = installState.from.neType; tableState.queryParams.neType = installState.from.neType;
@@ -304,7 +303,7 @@ function fnUploadFile(up: UploadRequestOption) {
file.status = 'done'; file.status = 'done';
// 预置到表单 // 预置到表单
const { fileName, originalFileName } = res.data; const { fileName, originalFileName } = res.data;
installState.from.fileName = originalFileName; installState.from.name = originalFileName;
installState.from.path = fileName; installState.from.path = fileName;
} else { } else {
message.error(res.msg, 3); message.error(res.msg, 3);
@@ -316,63 +315,29 @@ function fnUploadFile(up: UploadRequestOption) {
}); });
} }
/**软件包上传安装 */ /**软件包运行检查 */
function fnInstallUpload() { function fnRunCheck() {
if (installState.confirmLoading) return; if (installState.confirmLoading) return;
const form = toRaw(installState.from); const hide = message.loading(t('common.loading'), 0);
installStateFrom installStateFrom
.validate() .validate()
.then(() => { .then(() => {
const form = toRaw(installState.from);
Object.assign(form, { hostId: installState.hostId });
installState.confirmLoading = true; installState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0); return checkInstallNeSoftware(form);
// 新增软件包
addNeSoftware(form)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
// 安装网元
Object.assign(form, { hostId: installState.hostId });
return installCheckNeSoftware(form);
} else {
message.error(res.msg, 3);
}
})
.then(res => {
if (!res) return;
if (res.code === RESULT_CODE_SUCCESS) {
installState.setpSSHArr = res.data;
installState.setpSSHIdx = 0;
installState.setp = 'ssh';
} else {
message.error(res.msg, 3);
}
})
.finally(() => {
hide();
installState.confirmLoading = false;
});
}) })
.catch(e => {
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
});
}
/**软件包选择安装 */
function fnInstallOptions() {
if (installState.confirmLoading) return;
const form = toRaw(installState.from);
Object.assign(form, { hostId: installState.hostId });
installState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
installCheckNeSoftware(form)
.then(res => { .then(res => {
if (res.code === RESULT_CODE_SUCCESS) { if (res.code === RESULT_CODE_SUCCESS) {
installState.setpSSHArr = res.data; installState.setpSSHArr = res.data;
installState.setpSSHIdx = 0;
installState.setp = 'preinput'; installState.setp = 'preinput';
} else { } else {
message.error(res.msg, 3); message.error(res.msg, 3);
} }
}) })
.catch(e => {
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
})
.finally(() => { .finally(() => {
hide(); hide();
installState.confirmLoading = false; installState.confirmLoading = false;
@@ -385,13 +350,23 @@ function fnInstallPreinput() {
// IMS // IMS
if (installState.from.neType === 'IMS') { if (installState.from.neType === 'IMS') {
const modipplmn = installState.setpSSHArr[1]; const modipplmn = installState.setpSSHArr[1];
modipplmn.replace('{PUBIP}', '192.168.5.57'); if (modipplmn.includes('modipplmn.sh')) {
modipplmn.replace('{MCC}', '001'); installState.setpSSHArr[1] = modipplmn
modipplmn.replace('{MNC}', '01'); .replace('{PUBIP}', installState.preinput.pubIP)
.replace('{MCC}', installState.preinput.mcc)
.replace('{MNC}', installState.preinput.mnc);
}
const modintraip = installState.setpSSHArr[2]; const modintraip = installState.setpSSHArr[2];
modintraip.replace('{PRIIP}', '192.168.5.57'); if (modintraip.includes('modintraip.sh')) {
installState.setpSSHArr[2] = modintraip.replace(
'{PRIIP}',
installState.preinput.priIP
);
}
} }
installState.setpSSHIdx = 0; // 其他
//
installState.setp = 'ssh'; installState.setp = 'ssh';
} }
@@ -436,17 +411,16 @@ function fnTerminalMessage(res: Record<string, any>) {
// IMS预输入 // IMS预输入
if (data.includes('(P/I/S-CSCF Config)? <y/n>')) { if (data.includes('(P/I/S-CSCF Config)? <y/n>')) {
installTerminal.value.send('y'); installTerminal.value.send(installState.preinput.pisCSCF);
return; return;
} }
// 命令结束后继续输入命令 // 命令结束后继续输入命令
if (data.endsWith('$ ')) { if (data.endsWith('$ ')) {
console.log('结束'); console.log('结束');
const cmdStr = installState.setpSSHArr[installState.setpSSHIdx]; const cmdStr = installState.setpSSHArr.shift();
if (cmdStr) { if (cmdStr) {
installTerminal.value.send(cmdStr); installTerminal.value.send(cmdStr);
installState.setpSSHIdx += 1;
} }
return; return;
} }
@@ -462,14 +436,13 @@ function fnTerminalClose(data: Record<string, any>) {
/**终端重新安装 */ /**终端重新安装 */
function fnTerminalReset() { function fnTerminalReset() {
installState.setpSSHIdx = 0;
installState.setp = 'pkg'; installState.setp = 'pkg';
installState.optionType = 'option'; installState.optionType = 'option';
} }
onMounted(() => { onMounted(() => {
// 读取上一步的网元信息 // 读取步骤:网元信息
const stepPrevFrom = stepState.states[stepState.current - 1].from; const stepPrevFrom = stepState.states[1].from;
installState.from.neType = stepPrevFrom.neType; installState.from.neType = stepPrevFrom.neType;
installState.hostId = stepPrevFrom.hostIds.split(',')[0]; installState.hostId = stepPrevFrom.hostIds.split(',')[0];
}); });
@@ -546,7 +519,7 @@ onMounted(() => {
v-bind="installStateFrom.validateInfos.comment" v-bind="installStateFrom.validateInfos.comment"
> >
<a-textarea <a-textarea
v-model:value="installState.from.comment" v-model:value="installState.from.description"
:auto-size="{ minRows: 1, maxRows: 4 }" :auto-size="{ minRows: 1, maxRows: 4 }"
:maxlength="500" :maxlength="500"
:show-count="true" :show-count="true"
@@ -620,21 +593,10 @@ onMounted(() => {
</template> </template>
<a-form-item :wrapper-col="{ span: 14, offset: 3 }"> <a-form-item :wrapper-col="{ span: 14, offset: 3 }">
<template v-if="installState.optionType === 'upload'">
<a-button
type="primary"
html-type="submit"
@click="fnInstallUpload()"
:loading="installState.confirmLoading"
>
安装检查
</a-button>
</template>
<a-button <a-button
v-if="installState.optionType === 'option'"
type="primary" type="primary"
html-type="submit" html-type="submit"
@click="fnInstallOptions()" @click="fnRunCheck()"
:loading="installState.confirmLoading" :loading="installState.confirmLoading"
> >
安装检查 安装检查
@@ -654,6 +616,45 @@ onMounted(() => {
<div style="align-items: center">-----</div> <div style="align-items: center">-----</div>
</a-form-item> </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-form-item :wrapper-col="{ span: 14, offset: 3 }">
<a-button <a-button
type="primary" type="primary"

View File

@@ -203,24 +203,15 @@ function fnNeInfo() {
} }
onMounted(() => { onMounted(() => {
// 读取上一步的主机信息 // 读取步骤:环境检查
const statePerv = stepState.states[stepState.current - 1]; const stepPrevFrom = stepState.states[0].from;
modalState.from.ip = statePerv.from.addr; modalState.from.ip = stepPrevFrom.addr;
Object.assign(modalState.from.hosts[0], statePerv.from); Object.assign(modalState.from.hosts[0], stepPrevFrom);
Object.assign(modalState.from.hosts[1], { Object.assign(modalState.from.hosts[1], {
addr: modalState.from.ip, addr: stepPrevFrom.addr,
user: 'admin', user: 'admin',
password: 'admin', password: 'admin',
}); });
// 状态还原
const state = stepState.states[stepState.current];
if (state) {
if (state.from) {
const from = toRaw(state.from);
Object.assign(modalState.from, from);
}
}
}); });
</script> </script>

View File

@@ -33,7 +33,7 @@ import { stepState, fnStepPrev, fnStepNext } from './hooks/useStep';
<template #title> <template #title>
<div> <div>
{{ stepState.steps[stepState.current].title }} {{ stepState.steps[stepState.current].title }}
Check, Install, Config, Activate, Finish Check, NeInfo, Install, Config, Activate, Finish
</div> </div>
</template> </template>