feat: 网元快速安装页面模块化

This commit is contained in:
TsMask
2024-03-09 18:10:09 +08:00
parent dce068fcb1
commit e7442bf750
6 changed files with 884 additions and 76 deletions

View File

@@ -0,0 +1,180 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw, watch } from 'vue';
import { message, Form } from 'ant-design-vue/lib';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import useNeInfoStore from '@/store/modules/neinfo';
import { getNeInfo, addNeInfo, updateNeInfo } from '@/api/ne/neInfo';
import { NE_TYPE_LIST } from '@/constants/ne-constants';
import { testNeHost } from '@/api/ne/neHost';
import useDictStore from '@/store/modules/dict';
const { getDict } = useDictStore();
const { t } = useI18n();
const emit = defineEmits(['ok', 'cancel', 'update:visible']);
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
editId: {
type: String,
default: '',
},
});
/**字典数据 */
let dict: {
/**认证模式 */
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;
});
}
onMounted(() => {
// 初始字典数据
Promise.allSettled([getDict('ne_host_authMode')]).then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.neHostAuthMode = resArr[0].value;
}
});
});
</script>
<template>
<a-form
name="modalStateFrom"
layout="horizontal"
:label-col="{ span: 3 }"
:labelWrap="true"
>
<a-form-item label="激活码" name="comment">
<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">
<a-upload
name="file"
accept=".rpm,.deb"
list-type="text"
:max-count="1"
:show-upload-list="true"
>
<a-button type="default">
{{ t('views.configManage.softwareManage.selectFile') }}
</a-button>
</a-upload>
</a-form-item>
<a-form-item
name="test"
label="启动验证"
:label-col="{ span: 3 }"
:label-wrap="true"
>
<div style="align-items: center">
<a-button
type="primary"
shape="round"
@click="fnModalTest({})"
:loading="checkState.confirmLoading"
>
<template #icon><LinkOutlined /></template>
启动验证
</a-button>
</div>
</a-form-item>
---- 启动日志结果
<a-form-item
name="info"
label="启动日志"
:label-col="{ span: 3 }"
:label-wrap="true"
>
<div style="align-items: center">-----</div>
</a-form-item>
</a-form>
</template>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,189 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw, watch } from 'vue';
import { message, Form } from 'ant-design-vue/lib';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import useNeInfoStore from '@/store/modules/neinfo';
import { getNeInfo, addNeInfo, updateNeInfo } from '@/api/ne/neInfo';
import { NE_TYPE_LIST } from '@/constants/ne-constants';
import { testNeHost } from '@/api/ne/neHost';
import useDictStore from '@/store/modules/dict';
const { getDict } = useDictStore();
const { t } = useI18n();
const emit = defineEmits(['ok', 'cancel', 'update:visible']);
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
editId: {
type: String,
default: '',
},
});
/**字典数据 */
let dict: {
/**认证模式 */
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[];
/**设置选中的树节点 */
selectedKeys: string[];
/**表单数据 */
treeData: any[];
/**等待 loading */
loading: boolean;
};
/**文件树对象信息状态 */
let fileTreeState: FileTreeState = reactive({
expandedKeys: ['0-0', '0-1'],
selectedKeys: [],
treeData: [
{
title: 'parent 0',
key: '0-0',
children: [
{
title: 'leaf 0-0',
key: '0-0-0',
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,
});
onMounted(() => {
// 初始字典数据
Promise.allSettled([getDict('ne_host_authMode')]).then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.neHostAuthMode = resArr[0].value;
}
});
});
</script>
<template>
<a-row :gutter="8">
<a-col :lg="6" :md="6" :xs="24">
<a-directory-tree
v-model:expandedKeys="fileTreeState.expandedKeys"
v-model:selectedKeys="fileTreeState.selectedKeys"
:tree-data="fileTreeState.treeData"
></a-directory-tree>
</a-col>
<a-col :lg="18" :md="18" :xs="24">
<CodemirrorEdite
:value="JSON.stringify(fileTreeState.selectedKeys)"
:disabled="false"
:editor-style="{ height: '500px !important' }"
:placeholder="t('views.mmlManage.cmdAwait')"
></CodemirrorEdite>
</a-col>
</a-row>
</template>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,48 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw, watch } from 'vue';
import { message, Form } from 'ant-design-vue/lib';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import useNeInfoStore from '@/store/modules/neinfo';
import { getNeInfo, addNeInfo, updateNeInfo } from '@/api/ne/neInfo';
import { NE_TYPE_LIST } from '@/constants/ne-constants';
import { testNeHost } from '@/api/ne/neHost';
import useDictStore from '@/store/modules/dict';
const { getDict } = useDictStore();
const { t } = useI18n();
const emit = defineEmits(['ok', 'cancel', 'update:visible']);
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
editId: {
type: String,
default: '',
},
});
onMounted(() => {});
</script>
<template>
<a-result title="网元状态">
<template #extra>
<div>版本1.x</div>
<div>sn0000</div>
</template>
</a-result>
</template>
<style lang="less" scoped>
.collapse :deep(.ant-collapse-item) > .ant-collapse-header {
padding-left: 0;
padding-right: 0;
}
.collapse-header {
flex: 1;
display: flex;
flex-direction: row;
justify-content: space-between;
}
</style>

View File

@@ -0,0 +1,327 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw, watch } from 'vue';
import { message, Form } from 'ant-design-vue/lib';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import useNeInfoStore from '@/store/modules/neinfo';
import { getNeInfo, addNeInfo, updateNeInfo } from '@/api/ne/neInfo';
import { NE_TYPE_LIST } from '@/constants/ne-constants';
import { testNeHost } from '@/api/ne/neHost';
import useDictStore from '@/store/modules/dict';
import { ColumnsType } from 'ant-design-vue/lib/table';
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: {
/**认证模式 */
neHostAuthMode: DictType[];
} = reactive({
neHostAuthMode: [],
});
/**表格字段列 */
let tableColumns: ColumnsType = [
{
title: t('common.rowId'),
dataIndex: 'id',
width: '50',
},
{
title: 'age',
dataIndex: 'age',
width: '100',
},
{
title: 'address',
dataIndex: 'address',
width: '100',
},
{
title: t('common.operate'),
key: 'id',
align: 'left',
},
];
/**表格状态类型 */
type TabeStateType = {
/**加载等待 */
loading: boolean;
/**记录数据 */
data: object[];
/**勾选记录 */
selectedRowKeys: (string | number)[];
};
/**表格状态 */
let tableState: TabeStateType = reactive({
loading: false,
data: [],
selectedRowKeys: [],
});
/**表格多选 */
function fnTableSelectedRowKeys(keys: (string | number)[]) {
tableState.selectedRowKeys = keys;
}
/**安装对象信息状态类型 */
type InstallStateType = {
/**表单数据 */
from: Record<string, any>;
/**确定按钮 loading */
confirmLoading: boolean;
};
/**安装对象信息状态 */
let installState: InstallStateType = reactive({
from: {
optionType: 'upload',
hostType: 'ssh',
groupId: '1',
title: 'SSH_NE_22',
addr: '',
port: 22,
user: 'user',
authMode: '0',
password: 'user',
privateKey: '',
passPhrase: '',
remark: '',
},
confirmLoading: false,
});
/**表单属性和校验规则 */
const installStateFrom = Form.useForm(
installState.from,
reactive({
neType: [
{
required: true,
min: 1,
max: 32,
message: t('views.configManage.softwareManage.neTypePlease'),
},
],
version: [
{
required: true,
min: 1,
max: 64,
message: t('views.configManage.softwareManage.versionPlease'),
},
],
file: [
{
required: true,
message: t('views.configManage.softwareManage.updateFilePlease'),
},
],
})
);
/**
* 对话框弹出测试连接
*/
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;
});
}
onMounted(() => {
// 初始字典数据
Promise.allSettled([getDict('ne_host_authMode')]).then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.neHostAuthMode = resArr[0].value;
}
});
});
</script>
<template>
<a-form
name="installStateFrom"
layout="horizontal"
:label-col="{ span: 3 }"
:label-wrap="true"
>
<a-form-item label="软件来源" name="optionType">
<a-radio-group
v-model:value="installState.from.optionType"
button-style="solid"
>
<a-radio-button value="upload">新上传</a-radio-button>
<a-radio-button value="option">已上传</a-radio-button>
</a-radio-group>
</a-form-item>
<!-- 重新上传 -->
<template v-if="installState.from.optionType === 'upload'">
<a-form-item
:label="t('views.configManage.softwareManage.neType')"
name="neType"
v-bind="installStateFrom.validateInfos.neType"
>
<a-auto-complete
v-model:value="installState.from.neType"
:options="NE_TYPE_LIST.map(v => ({ value: v }))"
>
<a-input
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="32"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.configManage.neManage.neTypeTip') }}
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-auto-complete>
</a-form-item>
<a-form-item
:label="t('views.configManage.softwareManage.version')"
name="version"
v-bind="installStateFrom.validateInfos.version"
>
<a-input
v-model:value="installState.from.version"
allow-clear
:placeholder="t('views.configManage.softwareManage.versionPlease')"
></a-input>
</a-form-item>
<a-form-item
:label="t('views.configManage.softwareManage.updateComment')"
name="comment"
v-bind="installStateFrom.validateInfos.comment"
>
<a-textarea
v-model:value="installState.from.comment"
:auto-size="{ minRows: 4, maxRows: 6 }"
:maxlength="200"
:show-count="true"
:placeholder="
t('views.configManage.softwareManage.updateCommentPlease')
"
/>
</a-form-item>
<a-form-item
:label="t('views.configManage.softwareManage.updateFile')"
name="file"
v-bind="installStateFrom.validateInfos.file"
>
<a-upload
name="file"
v-model:file-list="installState.from.fileList"
accept=".rpm,.deb"
list-type="text"
:max-count="1"
:show-upload-list="true"
>
<a-button type="default" :loading="installState.confirmLoading">
{{ t('views.configManage.softwareManage.selectFile') }}
</a-button>
</a-upload>
</a-form-item>
</template>
<!-- 选择已上传 -->
<template v-if="installState.from.optionType === 'option'">
<a-form-item label="选择记录" name="option">
<a-table
class="table"
row-key="id"
:columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
size="small"
:scroll="{ y: 'calc(100vh - 480px)' }"
:row-selection="{
type: 'checkbox',
columnWidth: '48px',
selectedRowKeys: tableState.selectedRowKeys,
onChange: fnTableSelectedRowKeys,
}"
></a-table>
</a-form-item>
</template>
<a-form-item
name="test"
label="进行安装"
:label-col="{ span: 3 }"
:label-wrap="true"
>
<div style="align-items: center">
<a-button
type="primary"
shape="round"
@click="fnModalTest({})"
:loading="installState.confirmLoading"
>
<template #icon><LinkOutlined /></template>
安装
</a-button>
</div>
</a-form-item>
--- 安装前预输入
<a-form-item
name="info"
label="安装前预输入"
:label-col="{ span: 3 }"
:label-wrap="true"
>
<div style="align-items: center">-----</div>
</a-form-item>
---- 安装进行日志结果
<a-form-item
name="info"
label="安装日志"
:label-col="{ span: 3 }"
:label-wrap="true"
>
<div style="align-items: center">-----</div>
</a-form-item>
</a-form>
</template>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,61 @@
import { reactive } from 'vue';
/**步骤信息状态类型 */
type StepStateType = {
/**当前选中 */
current: number;
/**步骤项 */
steps: {
title: string;
description: string;
}[];
/**步骤下一步 */
stepNext: boolean;
/**步骤信息状态 */
states: any[];
};
/**步骤信息状态 */
export const stepState: StepStateType = reactive({
current: 0,
steps: [
{
title: '环境检查',
description: '服务器检查,触发免密脚本',
},
{
title: '网元安装',
description: '安装包上传执行安装启动服务等待10秒停止服务',
},
{
title: '网元配置',
description: '修改网元的配置文件',
},
{
title: '网元激活',
description: '获取激活码和上传授权文件,启动验证激活码',
},
{
title: '完成安装',
description: '获取网元服务状态',
},
],
stepNext: false,
states: [],
});
export function fnStepNext(e?: any) {
console.log(e);
if (e) {
stepState.states[stepState.current] = e;
stepState.stepNext = true;
return;
}
stepState.current++;
// stepState.stepNext = false;
}
export function fnStepPrev() {
stepState.current--;
// stepState.stepNext = true;
}

View File

@@ -1,96 +1,99 @@
<script lang="ts" setup>
import { PageContainer } from 'antdv-pro-layout';
import { Modal } from 'ant-design-vue/lib';
import TerminalSSH from '@/components/TerminalSSH/index.vue';
import TerminalTelnet from '@/components/TerminalTelnet/index.vue';
import { Modal, message } from 'ant-design-vue/lib';
import StepCheck from './components/StepCheck.vue';
import StepInstall from './components/StepInstall.vue';
import StepConfig from './components/StepConfig.vue';
import StepActivate from './components/StepActivate.vue';
import StepFinish from './components/StepFinish.vue';
import { reactive, toRaw } from 'vue';
import { parseDuration } from '@/utils/date-utils';
import { listNeHost } from '@/api/ne/neHost';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { useRouter } from 'vue-router';
import { stepState, fnStepPrev, fnStepNext } from './hooks/useStep';
import useI18n from '@/hooks/useI18n';
const { t } = useI18n();
const router = useRouter();
/**安装步骤信息状态类型 */
type SetupStateType = {
/**步骤下标 */
stepIndex: number;
/**加载等待 */
loading: boolean;
/**查询参数 */
params: {
pageNum: number;
pageSize: number;
};
/**数据总数 */
total: number;
data: Record<string, any>[];
};
/**主机对象信息状态 */
const setupState: SetupStateType = reactive({
stepIndex: 0,
loading: false,
params: {
pageNum: 1,
pageSize: 20,
},
total: 0,
data: [],
});
/**标签对象信息状态类型 */
type TabStateType = {
/**激活选中 */
activeKey: string;
/**页签数据 */
panes: {
id: string;
status: boolean;
host: Record<string, any>;
connectStamp?: string;
}[];
};
/**标签对象信息状态 */
const tabState: TabStateType = reactive({
activeKey: '0',
panes: [
{
id: '0',
host: {
hostId: '0',
title: t('views.tool.terminal.start'),
type: '0',
},
status: true,
},
],
});
</script>
<template>
<PageContainer>
<a-card
:bordered="false"
:body-style="{ marginBottom: '24px' }"
>
<a-steps v-model:current="setupState.stepIndex">
<a-step title="Step 1" description="This is a description." />
<a-step title="Step 2" description="This is a description." />
<a-step title="Step 3" description="This is a description." />
<a-step title="Step 3" description="This is a description." />
<!-- 步骤进度 -->
<a-card :bordered="false" :body-style="{ marginBottom: '24px' }">
<a-steps :current="stepState.current" direction="horizontal">
<a-step
v-for="s in stepState.steps"
:key="s.title"
:title="s.title"
:description="s.description"
:disabled="true"
/>
</a-steps>
</a-card>
<a-card :bordered="false" :body-style="{ padding: '12px' }">
<a-card
:bordered="false"
:body-style="{ padding: '12px', minHeight: '450px' }"
>
<!-- 插槽-卡片左侧侧 -->
<template #title>
<div>
{{ stepState.steps[stepState.current].title }}
Check, Install, Config, Activate, Finish
</div>
</template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-space :size="8" align="center">
<a-button
v-if="stepState.current > 0"
style="margin-left: 8px"
@click="fnStepPrev()"
>
上一步
</a-button>
<a-button
v-if="stepState.current < stepState.steps.length - 1"
type="primary"
:disabled="!stepState.stepNext"
@click="fnStepNext()"
>
下一步
</a-button>
<a-button
v-if="stepState.current == stepState.steps.length - 1"
type="primary"
@click="message.success('Processing complete!')"
>
完成
</a-button>
</a-space>
</template>
<!-- 步骤页面 -->
<StepCheck
v-if="stepState.current === 0"
:state="stepState.states[stepState.current]"
@next="e => fnStepNext(e)"
></StepCheck>
<StepInstall
v-if="stepState.current === 1"
@next="fnStepNext"
></StepInstall>
<StepConfig
v-if="stepState.current === 2"
@next="fnStepNext"
></StepConfig>
<StepActivate
v-if="stepState.current === 3"
@next="fnStepNext"
></StepActivate>
<StepFinish
v-if="stepState.current === 4"
@next="fnStepNext"
></StepFinish>
</a-card>
</PageContainer>
</template>